This is somewhat of an in-depth question about React Hooks:
There is some writing about, the setState() returned from React Hook useState(), when we call it, it "sets an internal index to 0", so that next time, when that component is re-rendered, useState() know which is the first useState(), and which is the second useState(), etc.
Is that how it works? Because, what if Main Component has 2 sub-components, and sub-component 1 calls setState(), vs sub-component 2 calls setState(), then I don't think Main Component is called to re-rendered, and each setState() in sub-component 1 or 2 just set the internal index to 0? Then how can useState() know which is which?
To see that it is the case, we can see https://codesandbox.io/s/wizardly-wind-3f210?file=/src/App.js
So if we click on "Count 1", we can see in the console log that only Count 1 is re-rendered, and likewise, "Count 2" as well.
So then, it looks like the two sub-components can work independently (But how?)
However, if that is true, if we reverse the order of sub-component 1 and 2 if count 0 is odd or even, every thing should work fine, and the React Hooks doc does not say we can't do that.
This is how they are swapped:
{count0 % 2 === 0 ? (
<div>
<Count1 />
<Count2 />
</div>
) : (
<div>
<Count2 />
<Count1 />
</div>
)}
However, we can see in this demo: https://codesandbox.io/s/amazing-wescoff-ih3n3?file=/src/App.js
If we click on "Count 1" to increment the count to 6, and then now we click on "Count 0" to make it even and odd and even and odd, the two counters switch positions, and they are reset to 0.
So how does it work? It does not seem like it is as simple as setting the internal index to 0 when setState() is called. Or is it true that sub-component 1 knows its setState() should set the internal index to 1, and sub-component 2's setState() knows it should set the internal index to 2? Is that how it works? But then in that case, if we switch the two sub components, they should simply show swapped values instead of showing 0. How does it really work?
In a nutshell: React has a handful of 'native' hooks that are internally wired to React's bookkeeping. But React keeps a separate book for each and every component instance you happen to create. So you can't confuse React be switching the order of elements. You could mess up the bookkeeping though by changing the order of hooks used inside one single component, hence the "rules of hooks".
So by doing something like this you could probably produce a mess:
const HorribleExperiment = () => {
var firstState, secondState;
if (Math.random() > 0.5) {
const firstState = useState('first');
const secondState = useState('second');
} else {
const secondState = useState('second');
const firstState = useState('first');
}
const firstValue = firstState[0];
const setFirst = firstState[1];
const secondValue = secondState[0];
const setsecond = secondState[1];
return (
<div>
<div>the first value is {firstValue}</div>
<div>the second value is {secondValue}</div>
<input onChange={e => setFirst(e.target.value)} />
<input onChange={e => setSecond(e.target.value)} />
</div>
);
}
The rules of hooks regarding the order of hooks are applicable within a single component. A renderer is responsible for keeping track of hook calls, and since it also responsible for rendering components, it can track which hook calls correspond to which component instance.
The example isn't specific to hooks and their order in any way. It would be the same with class components as well. When a parent component is re-rendered, child components are remounted because they appear to be different component hierarchy, so their states are initial states on each remount.
If the order changes, they need to be tracked with a key:
{count0 % 2 === 0 ? (
<div>
<Count1 key={1} />
<Count2 key={2} />
</div>
) : (
<div>
<Count2 key={2} />
<Count1 key={1} />
</div>
)}
Related
I have sample code below:
function App() {
console.log("render");
const [val, setVal] = React.useState(0);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => setVal(12)}>Update with same value</button>
</div>
);
}
When I click a button multiple times, the console log 3 times with 'render' message. For me, it should be 2 times only:
1 for first render
2 for the update from val 0 to 12 (when click button)
and since this time, it should not re-render because the same value (12) is updated to val.
But why it appears 3 times? That mean it still re-render one more time despite the same value was updated.
Anyone who know please explain this, thanks in advance.
P/S: I've figured out that it's only cause an extra re-render when the value changed then has been updated with the same
function App() {
console.log("render");
const [val, setVal] = useState(4);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => {
setVal(val => val + 1)
}}>Update</button>
<button onClick={() => {
setVal(val => val)
}}>Update with same value</button>
</div>
);
}
When first click on 2nd button, no re-render call, but if you click the 1st button then 2nd button, 2nd button cause 1 extra re-render
This thread may help you : React: Re-Rendering on Setting State - Hooks vs. this.setState
Also, you can check the second paragraph over here which says:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
React can’t guess the ouput of render() won’t change: it has to render() again and compare the results with the previous render().
Then the magic happens: if there are no differences, the DOM is not updated; if there are differences, it tries to only create/destroy elements as needed, because that’s the expensive part, not running render() — well it should not be.
Changing the state normally triggers a call to render() (not necessarily DOM modifications) — but if you want control over that behavior, define shouldComponentUpdate.
Note: That goes for non-hook components. However, I didn’t know the behavior of hooks was slightly different from that of a regular component: it seems that you’re right in expecting setState not to trigger a render when the value is unchanged — see Yash Joshi's answer.
I have sample code below:
function App() {
console.log("render");
const [val, setVal] = React.useState(0);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => setVal(12)}>Update with same value</button>
</div>
);
}
When I click a button multiple times, the console log 3 times with 'render' message. For me, it should be 2 times only:
1 for first render
2 for the update from val 0 to 12 (when click button)
and since this time, it should not re-render because the same value (12) is updated to val.
But why it appears 3 times? That mean it still re-render one more time despite the same value was updated.
Anyone who know please explain this, thanks in advance.
P/S: I've figured out that it's only cause an extra re-render when the value changed then has been updated with the same
function App() {
console.log("render");
const [val, setVal] = useState(4);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => {
setVal(val => val + 1)
}}>Update</button>
<button onClick={() => {
setVal(val => val)
}}>Update with same value</button>
</div>
);
}
When first click on 2nd button, no re-render call, but if you click the 1st button then 2nd button, 2nd button cause 1 extra re-render
This thread may help you : React: Re-Rendering on Setting State - Hooks vs. this.setState
Also, you can check the second paragraph over here which says:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
React can’t guess the ouput of render() won’t change: it has to render() again and compare the results with the previous render().
Then the magic happens: if there are no differences, the DOM is not updated; if there are differences, it tries to only create/destroy elements as needed, because that’s the expensive part, not running render() — well it should not be.
Changing the state normally triggers a call to render() (not necessarily DOM modifications) — but if you want control over that behavior, define shouldComponentUpdate.
Note: That goes for non-hook components. However, I didn’t know the behavior of hooks was slightly different from that of a regular component: it seems that you’re right in expecting setState not to trigger a render when the value is unchanged — see Yash Joshi's answer.
I understand that, within your React component, it's always good to render a component rather than use a function that renders some JSX.
What I mean is,
Doing this :
export default function App() {
const [count, setCount] = useState(0);
const someRenderFunction = () => <p>Hey</p>;
return (
<div className="App">
{someRenderFunction()}
<button
onClick={() => {
setCount((c) => c + 1);
}}
>
Increment{" "}
</button>
<p>{count}</p>
</div>
);
}
is NOT encouraged. The render function should be exported into it's own Component, like this :
const HeyComponent = () => <p>Hey</p>
export default function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<HeyComponent />
<button
onClick={() => {
setCount((c) => c + 1);
}}
>
Increment{" "}
</button>
<p>{count}</p>
</div>
);
}
But I never really understood the benefits of this refactor. I tried placing checking the re-render behaviours, unmount behaviours. Still didn't see any difference.
Can anyone explain in detail why this refactor is necessary/beneficial ?
This is all about components, so components should be reusable and should follow the DRY principle, in your case that seems to be so simple and just as you said will prevent the someRenderFunction() to be re-rendered if there aren't any changes to that component in the virtual dom so the best practices are to use <X /> syntax always or for some cases const Y = <X /> also is more readable. testing is another reason to create more components and make them more decoupled. imagine here you need to pass props to your someRenderFunction() so you will lose the jsx feature for passing props as <X prop={PROP}/>.
The actual difference between the two is that the function returning JSX is not a component, so it does not have its own state. When you use useState in the function, the state change will cause the App to be re-rendered.
Two reasons why the second way is better than the first way are:
The function which defines the <p>Hey</p> JSX (named someRenderFunction in the first way, and HeyComponent in the second) is moved outside of the component App in the second way. As of now, there's no benefit, but if you later wanted to extend someRenderFunction/HeyComponent to have normal component lifecycle that can be hooked into, moving it outside of App would be essential. For example, if you wanted the function/component to keep track of its own state, the first way would cause someRenderFunction's state to get reset every time App renders. More info on this problem can be found here: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unstable-nested-components.md, and here: https://stackoverflow.com/a/64211013/693855.
HeyComponent would be seen as a valid component whereas someRenderFunction would not, because component names need to start with a capital letter (this is mentioned here https://reactjs.org/docs/components-and-props.html#rendering-a-component in the Note at the bottom of the section). If you used hooks in your function, and it was not seen as a valid component by React, it would be breaking the "Rules of Hooks" set by React (https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions). I'm not sure if this would cause any bugs, but it is frowned upon by React -- if you have the "react-hooks/rules-of-hooks" ESLint rule enabled (from https://www.npmjs.com/package/eslint-plugin-react-hooks), you would see an error like this: ESLint: React Hook "useState" is called in function "someRenderFunction" that is neither a React function component nor a custom React Hook function.(react-hooks/rules-of-hooks)
The code which renders a component is followed as best practice.
Using Components we can pass state values to child component and can be used to display data. The same component can be re-used in other places.
If you have to just display a small function it can be used too if it shows constant information.
I'm working on the "Orders list" page.
I have a hook:
const [orders, setOrders] = useState([])
What happens currently:
when page is loaded - I fetch orders with Apollo useQuery
in the useEffect: once I get orders, I set them with setOrders
then, I return them with:
{orders.map((order) => (
<OrderComponent order={order}/>
))}
This works fine for displaying data, but I would like to be able to change order data (just state) from OrderComponent.
So let's say we have a notes input in the OrderComponent:
<input type="text" name="order_notes" value={order.notes}/>
The question is:
How to handle such updates from the child component?
What I tried:
On the parent page, I created handler:
const handleOrderNotes = ({ value, orderIndex }) => {
const ordersCopy = [...orders]
ordersCopy[ordersIndex].notes = value
setOrders(ordersCopy)
}
then I passed it to the OrderComponent along with orderIndex:
{orders.map((order, index) => (
<OrderComponent order={order} orderIndex={index} handleOrderNotes={handleOrderNotes}/>
))}
And added the handler to the input:
<input
type="text"
name="order_notes"
value={order.notes}
onChange={(value) => {
handleOrderNotes({value, orderIndex})
}}
/>
And it worked just fine. But after I started adding more and more stuff, to the point where I would like to use components in OrderComponent. I started to wonder if there is a better way of doing such things?
During the devlopment I was often getting errors like:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
So I believe that I'm missing something crucial here.
Just getting started with React, so would apprentice any input. Thanks :)
There are two ways I can think of to solve this. You would have to chose which one suits you based on how your app is organized/designed.
If using redux or some kind of state management for app, then you don't need to tell parent component but as you have index of the order with you in the child component itself, you can simply update that order in the app store/state itself.
You may want to wrap each component with useMemo and use useCallback for passing any callback to these child components. This second step you should do even if you are have state management done for your app to avoid unnecessary re-renders
Please change this
{orders.map((order, index) => ( <OrderComponent order={order} orderIndex={index} handleOrderNotes={handleOrderNotes}/> ))}
to
{orders.map((order, index) => ( <OrderComponent order={order} orderIndex={index} handleOrderNotes={({value,index})=>handleOrderNotes({value,index})}/> ))}
So, I'm using Apollo-link-state for local state management on an app I'm building, and I have a Modal component that pops up if modalData in the state changes. For it to listen to this, it's wrapped in a <Query> component, like so:
render(){
return (
<Query query={GET_MODAL_DATA}>
{({data}) => {
const currentModal = data.currentModal;
return (
<CSSTransition in={currentModal.operation ? true : false} unmountOnExit classNames="modal" timeout={{ enter: 1, exit: 200 }}>
<div className="modal-container" onClick={this.closeModal}>
<div onClick={e => e.stopPropagation()}>
<h2>
{ currentModal.operation+' '+currentModal.dataType }
</h2>
</div>
</div>
</CSSTransition>
)
}}
</Query>
)
}
with this arrangement, however, it re-renders (and re-queries) no matter what you do.
Change route? Re-renders. Change another component's local state? Re-renders. Change text fields? Re-renders.
Perhaps this is how Apollo is meant to work, but coming from Redux it seems kinda inefficient for every mounted component to re-query if you so much as breathe in the direction of your browser.
Is that what it's meant to do, or have I set something up incorrectly?
react-apollo will re-query every time unless you set the fetchPolicy to network-only.
As for the re-renders, it is more of a React issue than it is apollo.
You see, every time a state or a prop changes, react will run the component render() function. If react finds that the props for a component changed, it will re-render the component.
Since you are using an inline function as the children prop for Query component, this will cause re-rendering every time because every instance of a function is a reference to a different memory address, and thus is perceived as different. What you could do about this is to take out the function into: let's say, this.renderModal. Then, just pass the function a children to Query like this:
<Query>{this.renderModal}</Query>
This will ensures that the children prop passed to Query is the same function every time.
Second, you are passing a literal object { enter: 1, exit: 200 } to CSSTransition component. This has the same issue as above. Every instance of an object also has reference to different memory address, even if the value is exactly the same. One thing you can do about this is to take out the object, put it into constructor like
this.transitionTimeout = { enter: 1, exit: 200 };
and then pass that variable as a prop like:
<CSSTransition timeout={this.transitionTimeout}
This will ensures that the prop will point to the same object every time, thus preventing re-render.
Update:
You might also want to checkout about React.PureComponent. Here is an article that explains about it pretty well. Basically react will compare the value of the previous and next props shallowly, and if it is equal, it will not trigger render.