React unnecessary render of element - reactjs

I've just started learning React and was putting together a small app which makes calls to a quotes API. The API has an endpoint that returns a random quote. When the app initially loads it makes a call to the API and shows a quote, and there's a button that can be clicked to get a new random quote (new call to the API).
I have a root component named App. This component has a QuoteWrap component as a child. The QuoteWrap component has two children: the button that is used to get a new random quote and a Quote component which shows the author of the quote and the quote itself. This is the code inside of the QuoteWrap component:
export default function QuoteWrap() {
const { quoteData, isLoading, fireNewCall } = useQuote();
const handleClick = () => {
fireNewCall();
};
return(
<>
<button onClick={handleClick}>Get random quote</button>
{ isLoading ?
<h2>Loading...</h2>
:
<Quote author={quoteData.author} quote={quoteData.quote} />
}
</>
);
}
useQuote() is a custom hook that manages the calls to the API and returns 3 values: 1- the data, 2- if a call is in process and 3- a function to make a call to the API.
Obviously, every time the button is clicked, the whole QuoteWrap component is re-rendered (as quoteData and isLoading change). But really, the button doesn't need to be re-rendered as it never changes.
So I thought: ok, I can move the button up to the App component. But then I don't have access to the fireNewCall function in the useQuote hook.
How can I prevent the button from being re-rendered? Is it even important in this case or am I getting too obsessed with React re-renders?
Thanks!

Your component will re-render every time the handleClick function changes, which is every time that QuoteWrap is rendered.
The solution is the useCallback hook. useCallback will return the same function to handleClick, every time QuoteWrap is rendered, so long as the dependencies haven't changed.
https://reactjs.org/docs/hooks-reference.html#usecallback
You would use it like this:
const handleClick = useCallback(() => {
fireNewCall();
},[fireNewCall]);
fireNewCall is the dependency, so as long as useQuote returns a stable fireNewCall function, then your button will not re-render, since the handleClick property hasn't changed.

I think you might get too obsessed with React re-renders. The button should be re-rendered because, handleClick should be changed when fireNewCall changed for some case. Even if, handleClick will never be changed. It's no need to think about an element re-render.

Pretty much what Benjamin and Viet said - in your original code, a new function is assigned to handleClick on each render. You can use React.useCallback to maintain the original function reference and only update it when something in the dependency array changes - in this case, just fireNewCall needs to go into the dependency array.
But as Viet says, don't get too obsessed with it. Using React.useCallback might even slow down your code. Check out Kent C. Dodds When to useMemo and useCallback post for more insight.

Related

How to force an update for a functional component?

I'm learning redux and want to find out how useSelector updates a component, because the component doesn't have its own state.
I understand that useSelector() subscribes the component to the store, and when the store is updated, the component also updates.
Class components have this.forceUpdate(), but functional components don't have it.
How does one force-update a functional component?
You can simply do this
Add a dummy state that you can change to reliably initiate a re-render.
const [rerender, setRerender] = useState(false);
...
//And whenever you want to re-render, you can do this
setRerender(!rerender);
And this will re-render the component, since components always re-render on state change
The react-redux package relies on the rendering engine of react/react-dom to trigger the re-render of a given component that uses the useSelector hook.
If you take a look at the source of useSelector you can notice the use of useReducer:
const [, forceRender] = useReducer((s) => s + 1, 0)
As the name (forceRender) implies, redux uses this to trigger a re-render by react.
With v8 of react-redux the implementation of this mechanism changes but still relies on react-hooks for the re-render.
If you are curious how React handles re-renders, take a look at this excellent SO answer. It provides a great entry on the implementation details of how react-hooks are associated with the calling component.
I don't repeat Ryan here, but to sum it up:
The renderer keeps a reference to the component that is currently rendered. All hooks being executed during this render (no matter how deeply nested in custom-hooks they are) ultimately belong to this component.
So, the useReducer is associated with the component within which you called useSelector.
The dispatch function of useReducer triggers a re-render of this component (React either calls the render() method of a class-component or executes the function body of a functional component).
If you are curious how react-redux determines when it should force this re-render (by utilizing useReducer), take another look at the source code of useSelector.
Redux uses the subscriber-pattern to get notified of updates to the state. If the root-state of redux is updated the following things happen:
useSelector hooks in your application re-run their selector function
This re-selected state is compared to the previously selected state (by default via === comparison). The second argument to useSelector can be a comparison function to change this behavior
If the re-selected state differs from the previously selected state, a re-render is triggered via the useReducer hook.
The subscriber pattern is very react-like but potentially helps save many re-renders. Calling several useSelector hooks is cheap when compared with re-renders.
First of all, I want to mention that you don't need to do a force update when you use useSelector hook. Rerender will happen automatically whenever the selected state value will be updated.
But if you need to force update the functional component you can use this approach.
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => ++value); // update the state to force render
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
I highly recommend avoiding the use of this hack, in 99% of issues you can resolve them without force update. But in any case, it's good to know that there is such a possibility in the functional component exists too.
Maybe something like this could help you:
In a Class Component you could pass a property like the one below...
<Element onSomethingHappen={
()=>{
if(shouldComponentUpdate())
this.forceUpdate();
}}/>
In the function component you can call the updater like this one:
function FunctionComponent(props){
//When you need it you can update like this one...
props.onSomethingHappen();
// Here you are ;) let me know if this helps you
}
Continuing on other answers, to keep your code clean you can create a dummy state and then set it in your own forceUpdate function:
const [helper, setHelper] = useState(false);
function forceUpdate(){
setHelper(!helper);
}
Now you can just call forceUpdate() in the rest of your code:
<div onClick={() => forceUpdate()} />

How to pass callback as a prop to lower components in React without creating a new callback each time?

I am coming form angular and getting used to react. I was reading the docs on event handling and stumbled upon this:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
the docs then say:
"
The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem."
I don't have access to constructor as I am using hooks. What should I do?
I prepared a quick demo. The structure is App > [CompA > Comp B]. There is a button in component B that increments count and the state of the count is kept in App. Through props the callback is delegated from App through Comp A to comp B. Whenever I click the count button in Comp B, it re-renders the entire Comp A. This can be verified by the random number generation. It's fine for this component but wouldn't this be a problem in a large project?
Now imagine I have a quiz app and I keep score in the main App. If someone selects right answer inside a Question component I would like to keep a count of that in the main App. Doing it this way would re-render any intermediate components. What's the pattern I should follow?
Use the useCallback hook. It'll return the same function as long as its dependencies remain the same.
const callback = useCallback(()=> { /* do something */ }, [/* dependencies */])
The dependencies work the same as the useEffect hook.
Edit
From your demo, <CompA onCount={() => setCount(count + 1)} /> is perfectly fine, Dan Abramov said as much.
Parent re-renders will render their children, so if the count state is in the parent, the children will be re-rendered. That's perfectly fine. React is built to do that very quickly.

Need a clearer explanation how to avoid infinite re-rendering with React hooks

Not so fluent with React hooks, used plenty of class components before, hope you'll be forgiving.
The current code causes infinite re-rendering, and I think I understand why - the entire function body is being called on re-render.
const NavTabs = () => {
const classes = useStyles();
const [categories, setCategories] = React.useState();
const axiosPromise = getRequest(consts.categoriesURL);
axiosPromise.then(data => {
setCategories(data.value);
})
return (
<div className={classes.root}>
<AppBar position="static">
</AppBar>
{categories && <DynamicTabs categories={categories}/>}
</div>
);
}
I guess I could do something like if (!categories) { const axiosPromise [...] and so forth, i.e. do the http request only if categories haven't been populated yet. I guess this could also be solved by useEffect? Or wrapping the hook in an internal function?
I guess my real question is - why is React re-rendering the entire function body? Shouldn't it re-render only the return function? And then what is the point of using hooks that will be re-run on every render?
Compared to class components - shouldn't the code in the function body be equivalent to the constructor code in class components, and the return function - equivalent to the render method?
I guess I could do something like if (!categories) { const axiosPromise [...] and so forth, i.e. do the http request only if categories haven't been populated yet. I guess this could also be solved by useEffect? Or wrapping the hook in an internal function?
Yes, useEffect is the way to go here. Making a request and setting the result as state are side effects should only be run once in your case. We can achieve that easily with useEffect.
I guess my real question is - why is React re-rendering the entire function body? Shouldn't it re-render only the return function? And then what is the point of using hooks that will be re-run on every render?
React has no way to split a js function and only re-render the return. The function is atomic and must be completed. That is what hooks are for. React controls when hooks are ran so it can do fun stuff like batch state updates, ignore outdated effects and prioritise high priority work like animations.
Compared to class components - shouldn't the code in the function body be equivalent to the constructor code in class components, and the return function - equivalent to the render method?
The functional component is equivalent to the render method of a class component. They are called in a similar way. All the other lifecycle methods are replaced by hooks.
I recommend the react docs are great place to start and Dan Abramov has a great deep dive on hooks.
Yes, getRequest is being invoked each render cycle which sets some state and triggers a rerender. Placing it in an effect hook with a dependency array is likely the best solution. What dependencies you define will dictate when getRequest can be invoked.
Why is React re-rendering the entire function body?
The entire function body needs to run in order to determine the return value.
And then what is the point of using hooks that will be re-run on every render?
Hooks are run on every render, in the same order they are defined, but depending on dependencies may not invoke a callback. Hooks are what give functional components so much viability and sense of component lifecycle, to nearly be equivalent to class-based components in functionality. In most cases, you can completely convert a class-based component to a functional one and not drop any functionality.
Compared to class components - shouldn't the code in the function body be equivalent to the constructor code in class components, and the return function - equivalent to the render method?
It is more accurate to think of the entire functional components definition as the class-based render function, which can contain some logic and returns computed JSX to render to the DOM.
Example Solution:
const NavTabs = () => {
const classes = useStyles();
const [categories, setCategories] = React.useState(); // <-- no initial state!
useEffect(() => {
getRequest(consts.categoriesURL).then(data => {
setCategories(data.value); // <-- will update state and trigger render
});
}, []); // <-- empty dependency is run once on component mount
return (
<div className={classes.root}>
<AppBar position="static">
</AppBar>
{categories && <DynamicTabs categories={categories}/>}
</div>
);
}
To answer "why react is running the entire function" the answer is that javascript functions work that way: you always have to run the whole thing, they don't stop in the middle*. I understand what you are thinking here, if you are used to class components: don't I have a constructor section and a render section? and the answer is: not really if you are using function components. You only have render. But hooks are magic, and they let you pretend to have two parts.
Hooks know when they are called, and assuming you always call them in the same order, the can keep track of state outside the render function. so the way the work is sorta like this:
React detects a function component and creates or re-uses an existing rendering context for that component. This is where the hook information lives.
React calls your function component and it starts running.
You call hooks within your function component. These check what the current rendering context is, and save/get relevant information from that context. In a sense the rendering context is a "global" variable.
You do whatever else you want within the function, and eventually return a component tree (JSX) or null.
react then (eventually) updates the DOM to match what you returned, and saves the changes to the rendering context, so the next time render is called, it can re-use the context.
The magic is that the rendering context can do fancy things with hooks, like only run them once, always return the same value from a hook, or any other number of things. But in a sense, the component "class" becomes the react-internal rendering context that hooks know how to access.
Here is an example of the useState hook implemented in a class component: (You wouldn't ever need to do this, but it's an example of how hooks work).
class FakeHook extends React.Component {
constructor(...args) {
super(...args)
this.state = {}
this.useStateCalls = 0
}
useState(defaultValue){
const currentRenderContext = this.state
let value = defaultValue
const currentStateKey = `useState${this.useStateCalls}`
if (currentStateKey in currentRenderContext) value = currentRenderContext[currentStateKey]
this.useStateCalls++
return[value, (newValue) => this.setState({[currentStateKey]: newValue})]
}
render(){
this.useStateCalls = 0
let [fooState, setFoo] = this.useState("foo default")
let [barState, setBar] = this.useState("bar default")
return(
<dl>
<dt>Foo state</dt>
<dd>
<strong>Value:</strong>
<div>{fooState}</div>
<button onClick={(event) => {event.preventDefault(); setFoo(`foo updated at ${new Date().toLocaleString()}`)}}>Update Foo</button>
</dd>
<dt>Bar state</dt>
<dd>
<strong>Value:</strong>
<div>{barState}</div>
<button onClick={(event) => {event.preventDefault(); setBar(`bar updated at ${new Date().toLocaleString()}`)}}>Update Bar</button>
</dd>
<dt>Render context state:</dt>
<dd><pre>{JSON.stringify(this.state)}</pre></dd>
</dl>
)
}
}
ReactDOM.render(<FakeHook/>, document.getElementById('main'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main id=main>loading or error occurred...</main>
Notice that state is stored based on the order the hook is called inside render. In real hooks, the render context is stored somewhere other than this.state, but hooks know how to get it, and you don't really care. Also, this is just an example, real hooks work slightly differently, but the concept is the same.
*: async functions and generators don't run all at once, and instead return a special object that lets the function run in multiple steps, waiting or pausing on await or yield.

Can't perform a React State update on unMounted child component?

Am getting this warning:
Can't perform a React state update on unmounted component. This is a no-op...
It results from a child component and I can't figure out how to make it go away.
Please note that I have read many other posts about why this happens, and understand the basic issue. However, most solutions suggest cancelling subscriptions in a componentWillUnmount style function (I'm using react hooks)
I don't know if this points to some larger fundamental misunderstanding I have of React,but here is essentially what i have:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) Please note:
MyTextarea component has a parent component which is only rendered on a certain route.
Theres also a Menu component that once clicked, changes the route and depending on the situation will either show MyTextarea's parent component or show another component.
This warning happens once I click the Menu to switch off MyTextarea's parent component.
More Context
Other answers on StackOverflow suggest making changes to prevent state updates when a component isn't mounted. In my situation, I cannot do that because I didn't design the Picker component (rendered by MyTextarea). The Warning originates from this <Picker onEmojiClick={onClick}> line but I wouldn't want to modify this off-the-shelf component.
That's explains my attempt to either render the component or not based on the isMountedRef. However this doesn't work either. What happens is the component is either rendered if i set useRef(true), or it's never rendered at all if i set useRef(null) as many have suggested.
I'm not exactly sure what your problem actually is (is it that you can't get rid of the warning or that the <Picker> is either always rendering or never is), but I'll try to address all the problems I see.
Firstly, you shouldn't need to conditionally render the <Picker> depending on whether MyTextArea is mounted or not. Since components only render after mounting, the <Picker> will never render if the component it's in hasn't mounted.
That being said, if you still want to keep track of when the component is mounted, I'd suggest not using hooks, and using componentDidMount and componentWillUnmount with setState() instead. Not only will this make it easier to understand your component's lifecycle, but there are also some problems with the way you're using hooks.
Right now, your useRef(true) will set isMountedRef.current to true when the component is initialized, so it will be true even before its mounted. useRef() is not the same as componentDidMount().
Using 'useEffect()' to switch isMountedRef.current to true when the component is mounted won't work either. While it will fire when the component is mounted, useEffect() is for side effects, not state updates, so it doesn't trigger a re-render, which is why the component never renders when you set useRef(null).
Also, your useEffect() hook will fire every time your component updates, not just when it mounts, and your clean up function (the function being returned) will also fire on every update, not just when it unmounts. So on every update, isMountedRef.current will switch from true to false and back to true. However, none of this matters because the component won't re-render anyways (as explained above).
If you really do need to use useEffect() then you should combine it into one function and use it to update state so that it triggers a re-render:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
Lastly, from the code you shared, your component couldn't be causing the warning because there are no state updates anywhere in your code. You should check the picker's repo for issues.
Edit: Seems the warning is caused by your Picker package and there's already an issue for it https://github.com/ealush/emoji-picker-react/issues/142

Passed-in prop used to initialize a useState hook gets updated when calling the hook's updater method

Given the following code:
const MyComponent = ({ myObj }) => {
const [myStatefulObj, setMyStatefulObj] = useState(myObj.subObj)
const handleChange = () => {
const updatedObj = { newProperty: Math.random() }
setMyStatefulObj(updatedObj) // myObj is updated too!
}
return <div />
}
I am trying to initialize a new local prop with useState, and hydrating its initial value with a prop that is passed in from a parent component.
When I update my prop using setMyStatefulObj everything works fine, except that it's also updating the prop that's passed into the component.
This is an undesired side effect and one I haven't come across before.
The prop that is getting passed in is from a parent that creates it with a selector from react-redux's useSelector hook.
There are no actions being dispatched or anything, but simply logging the value of the myObj prop shows that it is indeed being updated every time the local setMyStatefulObj method gets called.
The effect you are describing doesn't make sense and I was not able to reproduce it. Maybe you could create a snippet and share it.
I don't think that was caused by the hooks function.
I have created a simple example in CodeSandBox as you said, and it obviously not reproduced. You can edit this sandbox and try to simulate your situation maybe you can find the root cause.
But since you are using redux, I would suggest you to use useSelector to select the state you need inside hooks function, then you would not have this kind of concern, and it will have better performance(if you pass the prop, every time myObj changes the hooks component will re-render).

Resources