I have a complex component with some "dynamic" imports.
I recreate my component tree on codesandbox: https://codesandbox.io/s/clever-rosalind-t3cfm?file=/src/App.js:298-302
I have an App.js file where I have a list of objects. Each object, render a RenderWidget component.
Inside RenderWidget, I need to get a "dynamic" comp. (I recreated that with a simple function, but i have more complexity in that part). So I wrapped the comp on useMemo to avoid component re-creation. (useMemo has a console.log to check if the Comp is re-initializated)
Inside the Comp, we have a useState declaration with the following initial value:
const getData = () => {
console.log("getData called");
return Math.random();
};
const [data, setData] = useState(getData());
If You click on H1, we force an update on App.js. If you check the console, You get "getData called" each time that you click on H1.
Why?
If parent component props have changed it will re-render all of its children, you can use React.memo to prevent unnecessary re-render.
I also suggest you to read this article, it's written by the author of the react core team,
in addition to memo, there are other ways to prevent re-rendering
export default memo(function RenderWidget() {
const LocalComp = useMemo(() => {
console.log("NO RE-INITIALIZED");
return getComp();
}, []);
return <LocalComp />;
});
I got the answer!
For the "lazy" initial value, we need to call the function like this:
// Replace that
const [data, setData] = useState(getData());
// With that
const [data, setData] = useState(() => getData());
Related
I have gone through a couple of articles on useCallback and useMemo on when to use and when not to use but I have mostly seen very contrived code. I was looking at a code at my company where I have noticed someone have done this:
const takePhoto = useCallback(() => {
launchCamera({ mediaType: "photo", cameraType: "front" }, onPickImage);
}, []);
const pickPhotoFromLibrary = async () => {
launchImageLibrary({ mediaType: "photo" }, onPickImage);
}
const onUploadPress = useCallback(() => {
Alert.alert(
"Upload Photo",
"From where would you like to take your photo?",
[
{ text: "Camera", onPress: () => takePhoto() },
{ text: "Library", onPress: () => pickPhotoFromLibrary() },
]
);
}, [pickPhotoFromLibrary, takePhoto]);
This is how onUploadPress is called:
<TouchableOpacity
style={styles.retakeButton}
onPress={onUploadPress}
>
Do you think this is the correct way of calling it? Based on my understanding from those articles, this looks in-correct. Can someone tell me when to use useCallback and also maybe explain useCallback in more human terms?
Article I read: When to useMemo and useCallback.
useCallback returns a normal JavaScript function regarding how to use it. It is the same as the one it gets as first parameter regarding what it does. The difference is that this function doesn't get recreated on a new memory reference every time the component re-renders, while a normal function does. It gets recreated on a new reference if one of the variables inside useCalback's dependency array changes.
Now, why would you wanna bother with this? Well, It's worth it whenever the normal behavior of a function is problematic for you. For example, if you have that function in the dependency array of an useEffect, or if you pass it down to a component that is memoized with memo.
The callback of an useEffect gets called on the first render and every time one of the variables inside the dependency array changes. And since normally a new version of that function is created on every render, the callback might get called infinitely. So useCallback is used to memoize it.
A memoized component with memo re-renders only if its state or props changes, not because its parent re-renders. And since normally a new version of that passed function as props is created, when the parent re-renders, the child component gets a new reference, hence it re-renders. So useCallback is used to memoize it.
To illustrate, I created the below working React application. Click on that button to trigger re-renders of the parent and watch the console. Hope it clears things up!
const MemoizedChildWithMemoizedFunctionInProps = React.memo(
({ memoizedDummyFunction }) => {
console.log("MemoizedChildWithMemoizedFunctionInProps renders");
return <div></div>;
}
);
const MemoizedChildWithNonMemoizedFunctionInProps = React.memo(
({ nonMemoizedDummyFunction }) => {
console.log("MemoizedChildWithNonMemoizedFunctionInProps renders");
return <div></div>;
}
);
const NonMemoizedChild = () => {
console.log("Non memoized child renders");
return <div></div>;
};
const Parent = () => {
const [state, setState] = React.useState(true);
const nonMemoizedFunction = () => {};
const memoizedFunction = React.useCallback(() => {}, []);
React.useEffect(() => {
console.log("useEffect callback with nonMemoizedFunction runs");
}, [nonMemoizedFunction]);
React.useEffect(() => {
console.log("useEffect callback with memoizedFunction runs");
}, [memoizedFunction]);
console.clear();
console.log("Parent renders");
return (
<div>
<button onClick={() => setState((prev) => !prev)}>Toggle state</button>
<MemoizedChildWithMemoizedFunctionInProps
memoizedFunction={memoizedFunction}
/>
<MemoizedChildWithNonMemoizedFunctionInProps
nonMemoizedFunction={nonMemoizedFunction}
/>
<NonMemoizedChild />
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It's to know that memoizing is not free, doing it wrong is worse than not having it. In your case, using useCallback for onUploadPress is a waste because a non memoized function, pickPhotoFromLibrary, is in the dependency array. Also, it's a waste if TouchableOpacity is not memoized with memo, which I'm not sure it's.
As a side note, there is useMemo, which behaves and is used like useCallback to memoize non-function but referenced values such as objects and arrays for the same reasons, or to memoize any result of a heavy calculation that you don't wanna repeat between renders.
A great resource to understand React render process in depth to know when to memorize and how to do it well: React Render.
In simple words, useCallback is used to save the function reference somewhere outside the component render so we could use the same reference again. That reference will be changed whenever one of the variables in the dependencies array changes.
As you know React tries to minimize the re-rendering process by watching some variables' value changes, then it decides to re-render not depending on the old-value and new-value of those variables.
So, the basic usage of useCallback is to hold old-value and the new-value equally.
I will try to demonstrate it more by giving some examples in situations we must use useCalback in.
Example 1: When the function is one of the dependencies array of the useEffect.
function Component(){
const [state, setState] = useState()
// Should use `useCallback`
function handleChange(input){
setState(...)
}
useEffect(()=>{
handleChange(...)
},[handleChange])
return ...
}
Example 2: When the function is being passed to one of the children components. Especially when it is being called on their useEffect hook, it leads to an infinite loop.
function Parent(){
const [state, setState] = useState()
function handleChange(input){
setState(...)
}
return <Child onChange={handleChange} />
}
function Child({onChange}){
const [state, setState] = useState()
useEffect(()=>{
onChange(...)
},[onChange])
return "Child"
}
Example 3: When you use React Context that holds a state and returns only the state setters functions, you need the consumer of that context to not rerender every time the state update as it may harm the performance.
const Context = React.createContext();
function ContextProvider({children}){
const [state, setState] = useState([]);
// Should use `useCallback`
const addToState = (input) => {
setState(prev => [...prev, input]);
}
// Should use `useCallback`
const removeFromState = (input) => {
setState(prev => prev.filter(elem => elem.id !== input.id));
}
// Should use `useCallback` with empty []
const getState = () => {
return state;
}
const contextValue= React.useMemo(
() => ({ addToState , removeFromState , getState}),
[addToState , removeFromState , getState]
);
// if we used `useCallback`, our contextValue will never change, and all the subscribers will not re-render
<Context.Provider value={contextValue}>
{children}
</Context.Provider>
}
Example 4: If you are subscribed to the observer, timer, document events, and need to unsubscribe when the component unmounts or for any other reason. So we need to access the same reference to unsubscribe from it.
function Component(){
// should use `useCallback`
const handler = () => {...}
useEffect(() => {
element.addEventListener(eventType, handler)
return () => element.removeEventListener(eventType, handler)
}, [eventType, element])
return ...
}
That's it, there are multiple situations you can use it too, but I hope these examples demonstrated the main idea behind useCallback. And always remember you don't need to use it if the cost of the re-render is negligible.
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I was facing almost the same problem as in this question
The code is a bit too much so I have made a stripped down version of the problem. (please forgive me if I made a mistake in doing so)
Basically, I have a main component and a sub component
main component
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
const newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
sub component
import React {useState} from 'react'
const SubComponent = ({updateStateFunction}) => {
return (
<div>
<button
onClick={() => updateStateFunction("Something new")}
>
</button>
</div>
)
}
export default SubComponent;
both the console logs give null.
My attempts at a solution:
Since most stack overflow answers suggested that stateupdates using hooks is asynchronous I tried using setTimeout
I thought we could then use async-await but that was a wrong approach
I tried updating the state inside useEffect but the point is that nothing is being re redered. This is because the variable that is being updated is never a part of an output but rather sort a helper varibale for further operations.
The way I did this was using the solution in the above refereced question:
const Main = (props) => {
/*Debugging*/
let newState = null
useEffect(()=>{
console.log("useEffect called")
setState(newState)
}, [newState])
/*Debugging*/
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
I though since the useEffect hook is not even being executed hence I did not try the other two methods in the solution
Am I referencing the wrong type of problem or is this a common behaviour in react?
Happy to provide any more information if needed
Edit:
I have added console.log() because I have operations followed by the state change that uses the value of the state variable.
Using React dev tools I see that the state is updating and that too almost instantly. The problem is that the button press leads to a dialogue pop-up in the real code whose component uses the state for other logic, hence I get an error that that state is still null
I am not sure how let newState = null has anything to do with any of the answers in the quoted question, so to be clear, this is how one would directly apply the accepted answer:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
useEffect(() => { console.log(state) }, [state])
return <SubComponent updateStateFunction = {updateStateFunction} />
}
However, there is no point of changing a state if it's not used for anything rendered on the screen - if Reacts does not detect any change in the return value, it might not commit any change to the DOM as a part of the optimizations, so it would probably not run any useEffects in that case.
I would recommend using React Dev Tools for checking the current state of a Component.
Also, console.log is basically the only side effect which does not need to be inside useEffect. If directly in the render function, it would be executed whenever the render function is called and would not depend on React committing changes to DOM...
Note that the first advice in my answer to the quoted question does not wrap console.log into useEffect - and to be clear again, this is how one would directly apply that advice:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
console.log(state)
return <SubComponent updateStateFunction = {updateStateFunction} />
}
The setting of the state is asynchronous in react.
An asynchronous function will be executed in parallel (well, kind of) as other instructions. Rather than console.logging after setting state, you could console.log state before the return function to know the new output.
There is nothing wrong with your first implementation. No need to use useEffect here
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
setState(data)
}
// try console logging here
console.log(state)
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
Think of it like this, whenever the state is set, your function which contains the state gets refreshed and re-run. You could console.log anywhere in the function to get the new state.
Use the react devtools to debug, even if the console.log() display null you see the state change in the devtools. The reason is that the state update is asynchronous.
If you still want to debug your react app using console.log() then call it just before the return statement or even in the jsx directly using curly braces.
I have a simplified version of my problem as follows:
const Component = () => {
const [data, setData] = useState([]);
const fn = useCallback((num) => {
const newData = [...data];
newData.push(num);
setData(newData);
}, [data]);
return <button onClick={() => fn(Math.random())}>{data.join()}</button>;
};
My problem is that newData is always [], instead of reflecting the updated state values. Therefore, my button will only show the latest data value instead of an array with increasing values. Why is this the case as I've included it in the dependency array of the useCallback function?
I guess the problem is most probably happen in the "button" component which you have simplified in your question therefore we cannot see the real problem.
There could be multiple reasons why states in the "useCallback" is not updated:
you didn't add the state in the dependency of "useCallback". This is
not your case as you already add data in the dependency
you use this "fn" in another "useCallback" and you didn't add fn as a dependency of that "useCallback".
// what you probably write in the "button" component
const handleClick = useCallback(() => {
...
doSomethingWithState(someState);
props.onClick(); //<--- this is the fn you passed
}, [someState]);
// what you should write
const handleClick = useCallback(() => {
...
doSomethingWithState(someState);
props.onClick(); //<--- this is the fn you passed
}, [someState, props.onClick]); //<---- you need to add the method as dependency as well
please verify if this is your case.
Your code works just as you expect:
https://codesandbox.io/s/relaxed-ishizaka-n2eg6?file=/src/App.js
It does show the updated state values.
I have some heavy forms that I'm dealing with. Thus, I'm trying to squeeze performance wherever I can find it. Recently I added the Why-did-you-render addon to get more insight on what might be slowing down my pages. I noticed that, for example, when I click on a checkbox component about all of my other components re-render. The justification is always the same. WDYR says
Re-rendered because of props changes: different functions with the
same name {prev onChangeHandler: ƒ} "!==" {next onChangeHandler: ƒ}
As much as possible, I try to respect best the best practices indications that I find. The callback functions that my component passes follow this pattern
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
export function TopLevelComponent({props}){
const defaultData = {name: '', useMale: false, useFemale: false}
const [data, setData] = useState(defData);
const { t } = useTranslation();
const updateState = (_attr, _val) => {
const update = {};
update[_attr] = _val;
setData({ ...data, ...update });
}
const updateName = (_v) => updateState('name', _v);//Text input
const updateUseMale = (_v) => updateState('useMale', _v);//checkbox
const updateUseFemale = (_v) => updateState('useFemale', _v);//checkbox
...
return <div>
...
<SomeInputComponent value={data.name} text={t('fullName')} onChangeHandler={updateName} />
<SomeCheckboxComponent value={data.useMale} onChangeHandler={updateUseMale} text={t('useMale')}/>
<SomeCheckboxComponent value={data.useFemale} onChangeHandler={updateUseFemale} text={t('useFemale')}/>
...
</div>
}
In an example like this one, altering any of the inputs (eg: Writing text in the text input or clicking one of the checkboxes) would cause the other 2 components to re-render with the justification presented above.
I guess that I could stop using functional components and utilize the shouldComponentUpdate() function, but functional components do present some advantages that I'd rather keep. How should I write my functions in such a way that interacting with one input does not force an update on another input?
The problem stems from the way you define your change handlers:
const updateName = (_v) => updateState('name', _v)
This line is called on each render and thus, every time your component is rendered, the prop has a new (albeit functionality-wise identical) value. The same holds for every other handler as well.
As an easy solution you can either upgrade your functional component to a fully fledged component and cache the handlers outside of the render function, or you can implement shouldComponentUpdate() in your child components.
You need to use memo for your child components to reduce renders
const SomeInputComponent = props => {
};
export default memo(SomeInputComponent);
// if it still causes rerender witout any prop change then you can use callback to allow or block render
e.f.
function arePropsEqual(prevProps, nextProps) {
return prevProps.name === nextProps.name; // use your logic to determine if props are same or not
}
export default memo(SomeInputComponent, arePropsEqual);
/* One reason for re-render is that `onChange` callback passed to child components is new on each parent render which causes child components to re-render even if you use `momo` because function is updated on each render so in order to fix this, you can use React hook `useCallback` to get the same function reference on each render.
So in you parent component, you need to do something like
*/
import { useCallback } from 'react';
const updateName = useCallback((_v) => updateState('name', _v), [])
You have to memoize parent function before pass to children, using useCallback for functional component or converting to class property if you use class.
export default class Parent extends React.PureComponent {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
console.log("click");
}
render() {
return (
<ChildComponent
onClick={ this.onClick }
/>
);
}
}
with useCallback:
Parent = () => {
const onClick = useCallback(
() => console.log('click'),
[]
);
return (
<ChildComponent
onClick={onClick}
/>
);
}
Lets say I'm doing a simple CRUD app in react. My functional component is basically just the form.
In the CREATE case I pass in an empty object via props
In the UPDATE case I pass in an object with the values via props (I got the data in the parent component with an API call)
I looks like this:
const MyForm = (props) => {
const [myValues, setMyValues] = useState(props.myValues);
const [errors, setErrors] = useState(0);
(...)
}
In the UPDATE case, I run (of course) into the issue that props.myValues is still empty when the component is mounted, and not set again (updated) when the api call from the parent component has finished thus leaving the form values empty.
Using a class component, I'd solve that with getDerivedStateFromProps(). Is there anything like that in a functional component? Or am I doing this wrong from the beginning? Thanks for any advice!
Yes, you can of course use useEffect.
In your case the code should look like this:
const MyForm = (props) => {
const [myValues, setMyValues] = useState(props.myValues);
const [errors, setErrors] = useState(0);
useEffect(() => {
setMyValues(props.myValues);
}, [props.myValues]);
}
Another way to do this is the Fully uncontrolled component with a key strategy. If you go that route, your component stays the same:
const MyForm = (props) => {
const [myValues, setMyValues] = useState(props.myValues);
const [errors, setErrors] = useState(0);
(...)
}
But there is one extra piece to its usage:
<MyForm myValues={myValues} key={myValues} />
Using the key prop like this means a new instance of this component will be created if myValues changes, meaning the default value you have provided to your internal myValues state will be reset to whatever the new values are.
This avoids the double render you get if you useEffect, but comes at the cost of a new component instance. I don't find this to be a problem if you do it at the individual input level, but you may not want to rebuild your whole form.
When you write:
const [myValues, setMyValues] = useState(props.myValues);
then myValues props is only used for initialize the myValues state.
Dan Abramov relates to this point in his latest article.
I would suggest:
1. Rename myValues prop to initialValues
2. Call the api after submit, and change your state according to the result.
It will look like:
const MyForm = (props) => {
const { initialValues, onSubmit } = this.props
const [myValues, setMyValues] = useState(initialValues);
...
const handleSubmit = () => {
// Assume that onSubmit is a call to Fetch API
onSubmit(myValues).then(response => response.json())
.then(updatedValues => setMyValues(updatedValues))
}
}