Logging errors
sentry.io
sentry.io
What if the user passes something unexpected. Instead of an integer count. It passes a string “abc”.
1 2 3 4 5 6 |
<Pagination itemsCount={count} pageSize={pageSize} currentPage = {currentPage} onPageChange={this.handlePageChange} /> |
In Pagination functional component, the string divides by integer. And thus, pagesCount becomes NaN. NaN gets calculated further and nothing comes of it.
npm i prop-types@15.6.2
pagination.jsx
1 2 3 4 5 6 7 8 9 10 11 |
import PropTypes from 'prop-types'; .. .. // camel notation Pagination.propTypes = { itemsCount: PropTypes.number.isRequired, pageSize: PropTypes.number.isRequired, currentPage: PropTypes.number.isRequired, onPageChange: PropTypes.func.isRequired, } |
Now let’s put a string, instead of an integer.
movies.jsx
1 2 3 4 5 6 7 8 |
.... ... <Pagination itemsCount="abc"//{count} pageSize={pageSize} currentPage = {currentPage} onPageChange={this.handlePageChange} /> |
In the console, we’ll get a warning:
So the concept is that we should add a property showingArr to our state.
showingArr holds the items to be shown according to what pagination page we’re on.
For example, if we’re on page 1, it should show items 1-4
If its page 2, 5-8
page 3, 9-12, and so on.
What we’re interested in is the first and last item #.
The last item # is always the current page * 4.
So if we’re on page 1, the last item is 4, page 2, last item is 8…etc.
so last item is currentPage * 4
The first item will always be last time – 4
Thus, using them, we paginate the movies array like so:
1 2 3 4 5 6 7 8 |
#paginateMovies = (page=1) => { const { pageSize, movies } = this.state; let max = page * pageSize; let min = max - pageSize; let showingArr = [...movies]; // slice returns shallow copy return showingArr.slice(min, max); } |
We create a private function paginateMovies.
In the beginning all the state will be set up when the virtual DOM is being built. When it finishes, it will call render which shows the default state, then in componentDidMount, we will set our state’s showingArr to the correct array. Hence in our componentDidMount():
1 2 3 |
componentDidMount() { this.setState({showingArr: this.#paginateMovies()}); } |
the setState will trigger a second render, which then will show the paginated items.
full source (movies.jsx)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
import React, { Component } from 'react'; import { getMovies } from '../../src/services/fakeMovieService'; import Pagination from './common/pagination'; class Movies extends Component { state = { movies: getMovies(), pageSize: 4, currentPage: 1, showingArr: [], } #paginatedMovies = (page=1) => { const { pageSize, movies } = this.state; let max = page * pageSize; let min = max - pageSize; let showingArr = [...movies]; // slice returns shallow copy return showingArr.slice(min, max); } componentDidMount() { this.setState({showingArr: this.#paginatedMovies()}); } handleDelete = movie => { const movies = this.state.movies.filter(m => m._id !== movie._id); this.setState({movies}); }; handleLike = movie => { } handlePageChange = page => { console.log('you have clicked on page: ', page); // causes new rendering. // whenever we render, currently, we render all movies // what we need to do is to render only certain # of pages // in accordance to our currentPage // we render according to our currentPage // if its currentPage is 1, [1,2,3,4] // if currentPage is [5, 6, 7, 8] this.setState({ currentPage: page, showingArr: this.#paginatedMovies(page) }) } render() { const { length: count} = this.state.movies; const { pageSize, currentPage, showingArr } = this.state; if (count === 0) return <p>No Movies in DB</p>; return ( <React.Fragment> <p>Showing {count} movies in the database.</p> <table className="table"> <thead> <tr> <th>Title</th> <th>Genre</th> <th>Stock</th> <th>Rate</th> <th></th> </tr> </thead> <tbody> {showingArr.map(movie => { return ( <tr key={movie._id}> <td>{movie.title}</td> <td>{movie.genre.name}</td> <td>{movie.numberInStock}</td> <td>{movie.dailyRentalRate}</td> <td><button onClick={() => this.handleDelete(movie)} className="btn btn-danger btn-sm">delete</button></td> </tr> ); })} </tbody> </table> <Pagination itemsCount={count} pageSize={pageSize} currentPage = {currentPage} onPageChange={this.handlePageChange} /> </React.Fragment>) } } export default Movies; |
There are two common practices for React project file structure.
One common way to structure projects is locate CSS, JS, and tests together, grouped by feature or route.
common/
├─ Avatar.js
├─ Avatar.css
├─ APIUtils.js
└─ APIUtils.test.js
feed/
├─ index.js
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
└─ FeedAPI.js
profile/
├─ index.js
├─ Profile.js
├─ ProfileHeader.js
├─ ProfileHeader.css
└─ ProfileAPI.js
Another popular way to structure projects is to group similar files together.
api/
├─ APIUtils.js
├─ APIUtils.test.js
├─ ProfileAPI.js
└─ UserAPI.js
components/
├─ Avatar.js
├─ Avatar.css
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
├─ Profile.js
├─ ProfileHeader.js
└─ ProfileHeader.css
React’s reconciliation algorithm assumes that without any information to the contrary, if a custom component appears in the same place on subsequent renders, it’s the same component as before, so reuses the previous instance rather than creating a new one.
1 2 3 4 |
@setTitle('Profile') class Profile extends React.Component { //.... } |
title is a string that will be set as a document title.
It is a parameter passed in from above (‘Profile’).
WrappedComponent is the Profile component displayed above.
Hence, @setTitle(title)(wrappedComponent) is what’s happening. We want to decorate the class Profile by returning a class
of our own with specific attributes and functionalities.
1 2 3 4 5 6 7 8 9 10 11 |
const setTitle = (title) => (WrappedComponent) => { return class extends React.Component { componentDidMount() { document.title = title } render() { return <WrappedComponent {...this.props} /> } } } |
In some cases you want to render different components depending on some state. JSX does not render false or undefined, so you can use conditional short-circuiting to render a given part of your component only if a certain condition is true.
1 2 3 4 5 6 7 8 |
const MyComponent = ({ name, address }) => ( <div> <h2>{name}</h2> {address && <p>{address}</p> } </div> ) |
If you need an if-else condition then use ternary operator.
1 2 3 4 5 6 7 8 9 |
const MyComponent = ({ name, address }) => ( <div> <h2>{name}</h2> {address ? <p>{address}</p> : <p>{'Address is not available'}</p> } </div> ) |
ref – https://stackoverflow.com/questions/52168047/is-setstate-inside-componentdidmount-considered-an-anti-pattern
You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state. Use this pattern with caution because it often causes performance issues. In most cases, you should be able to assign the initial state in the constructor() instead. It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position.
It is recommended to avoid async initialization in componentWillMount() lifecycle method.
componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore setting state in this method will not trigger a re-render. Avoid introducing any side-effects or subscriptions in this method. We need to make sure async calls for component initialization happened in componentDidMount() instead of componentWillMount().
1 2 3 4 5 6 7 8 |
componentDidMount() { axios.get(`api/todos`) .then((result) => { this.setState({ messages: [...result.data] }) }) } |
The style attribute accepts a JavaScript object with camelCased properties rather than a CSS string. This is consistent with the DOM style JavaScript property, is more efficient, and prevents XSS security holes.
1 2 3 4 5 6 7 8 |
const divStyle = { color: 'blue', backgroundImage: 'url(' + imgUrl + ')' }; function HelloWorldComponent() { return <div style={divStyle}>Hello World!</div> } |
Let’s see what happens under the hood.
At this point, we call setState to tell React that the state of this component will change.
React will then schedule an async call to the render method. Whether its five ms, or ten ms, we don’t know when it happens, but it will happen in the future.
The render function will return a new React element. That element comes from the JSX code.
1 2 3 4 5 6 7 8 9 10 |
render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button onClick={this.handleIncrement} className={this.names()}> click me </button> </div> ); } |
As each render function produces a new React element, when our state changes, the incoming React element will be different than the previous React element. React will diff them, and then apply the change(s) to the real DOM.
For example:
So when we update the count to 1 in our state object, the data we access in formatCount() within the span tag gets updated.
As we know, each element (button, div, span..etc) is an element in our virtual DOM tree. They get their data from the state object.
And when that state object gets updated as the user plays around with the UI, the current virtual DOM gets scheduled (pushed onto a scheduler) to be rendered asynchronously.
React will diff them and see that the incoming virtual DOM have changed compared to the previous virtual DOM. In our case, our span’s data has been modified.
It will notice that our span is modified because that’s where we have used formatCount() to retrieve the updated count property from the state object.
So it will reach out to the real DOM, and update that span, in order to match what we have in the virtual DOM.
Go to the web page, Inspect Elements, and click on the ‘Increment’ button.
You’ll see that as you push the increment button, it calls
1 |
this.setState({ count: this.state.count + 1 }); |
which updates the span. It updates the span because it depending on this.state.count it will update the class name to badge-primary or badge-info, and also update the count integer. Thus, in your Inspect Element, nothing else in the DOM is affected. Only that span element.
Explanation
render() function is the point of entry where the tree of React elements are created. When a state or prop within the component is updated, the render() will return a different tree of React elements. If you use setState() within the component, React immediately detects the state change and re-renders the component.
React then figures out how to efficiently update the UI to match the most recent tree changes.
This is when React updates its virtual DOM first and updates only the object that have changed in the real DOM.
React follows a batch update mechanism to update the real DOM. Hence, leading to increased performance. This means that updates to the real DOM are sent in batches, instead of sending updates for every single change in state.
The repainting of the UI is the most expensive part, and React efficiently ensures that the real DOM receives only batched updates to repaint the UI.
Explanation
When you use React, at a single point in time you can think of the render() function as creating a tree of React elements. On the next state or props update, that render() function will return a different tree of React elements. React then needs to figure out how to efficiently update the UI to match the most recent tree.
There are some generic solutions to this algorithmic problem of generating the minimum number of operations to transform one tree into another. However, the state of the art algorithms have a complexity in the order of O(n3) where n is the number of elements in the tree.
If we used this in React, displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive. Instead, React implements a heuristic O(n) algorithm based on two assumptions:
– Two elements of different types will produce different trees.
– The developer can hint at which child elements may be stable across different renders with a key prop.
When diffing two trees, React first compares the two root elements. The behavior is different depending on the types of the root elements.
Elements Of Different Types
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. Going from a tag to img tag or from article tag to Comment tag or from button tag to div tag – any of those will lead to a full rebuild.
When tearing down a tree, old DOM nodes are destroyed. Component instances receive componentWillUnmount(). When building up a new tree, new DOM nodes are inserted into the DOM. Component instances receive componentWillMount() and then componentDidMount(). Any state associated with the old tree is lost.
Any components below the root will also get unmounted and have their state destroyed. For example, when diffing:
1 2 3 4 5 6 7 |
<div> <Counter /> </div> <span> <Counter /> </span> |
This will destroy the old Counter and remount a new one.
DOM Elements Of The Same Type
When comparing two React DOM elements of the same type, React looks at the attributes of both, keeps the same underlying DOM node, and only updates the changed attributes. For example:
1 2 |
<div className="before" title="stuff" /> <div className="after" title="stuff" /> |
By comparing these two elements, React knows to only modify the className on the underlying DOM node.
When updating style, React also knows to update only the properties that changed. For example:
1 2 |
<div style={{color: 'red', fontWeight: 'bold'}} /> <div style={{color: 'green', fontWeight: 'bold'}} /> |
When converting between these two elements, React knows to only modify the color style, not the fontWeight.