React: Why Child Component Doesn't Update When Prop Changes

React 16.13.1 - child component does not re-render when props change

It looks like the same expression object is being passed in all the time.

React checks the props that a component receives for changes when deciding to render. It finds that none of the props items have changed, they are all the same objects as before, and concludes that the child component does not need to be rerendered. It will not do a deep inspection of all properties of each prop.

This also explains why a rerender can be forced by making a copy of the expression object. The copy is always a new object, thus causing a rerender, regardless if any of its content have changed or not.

You could avoid this situation as you do already, by making a copy, or by dissecting the expression object into its properties and then feeding each of those as separate props into the child.

As a final note, a copy can also be made by passing it in as expression={{...expression}}.

React child component not updating when props change

You mutate your state object in your handleChange function. The component doesn't rerender because searchState is still the same object reference from the previous render cycle.

let handleChange = (event) => {
searchState['results'] = searchProducts(event.target.value); // mutation!
searchState['query'] = event.target.value; // mutation!
setSearchState(searchState); // safe reference back into state
};

You shouldn't mutate state object directly. Use a functional state update and shallow copy existing state into a new state object reference. Then update the properties you want to update.

let handleChange = (event) => {
const { value } = event.target;

setSearchState(searchState => ({
...searchState,
results: searchProducts(value),
query: value,
}));
};

React not re-rendering child component after updating state var passed as prop

Found the answer! The resetPaint value in the parent was getting re-set before the cells had had time to finish rendering. Moving it to a useEffect like so solved the problem.

  useEffect(() => {
resetPaint && setResetPaint(false);
}, [resetPaint]);

I've updated the codesandbox to reflect this.

Component not updating when I change the props that I pass to it in React

A component only updates once either its state changes or its props change. A state is a variable or set of variables which is remembered when the component re-renders. All other variables will go back to their default value as soon as the component re-renders. You can see it as the component's memory.

So in your case changing your text variable won't update your parent's state, and thus won't re-render the component, which in return won't re-render and update the child's component.

If you want your parent component to update it's state (and update the child's props) you need to declare your text variable like this:

const [text, setText] = React.useState("This is the original text");

Text is your variable, it is now included within your component's state and will be remembered when the component re-renders. You can give this any name you want.

setText is a function which updates your text variable and also re-renders your component and it's children. You can give this any name you want.

"This is the original text" is your initial state, the initial value for your text variable.

To update your state you can do something like this:

setText("This is the new text");

So in your case it will look something like this:

function MainPage(){
const [text, setText] = React.useState("This is the original text");

React.useEffect(() => {
const timeout = setTimeout(function(){
setText("This is the new text")
}, 3000);

return clearTimeout(timeout)
}, []);

return(<DisplayText text={text} />);
}

useEffect is necessary to be able to define your setTimeout as soon as the component mounts. It can be use to execute some code as soon as a certain variable (defined between the [] brackets) updates. For example: If you wrote it like this:

React.useEffect(() => {
// execute some code
}, [text])

It would execute some code as soon as your text variables changes. Leave the [] brackets empty to only trigger useEffect when the component mounts and unmounts.

Within the useEffect hook you declare your setTimeout, this sets your timer as soon as the component mounts in this case.
The return method within your useEffect clears your timeout again as soon as the component unmounts. This will prevent your timer from running indefinitely after your component unmounts.

React doesn't update child when props change?

Because you don't update state in StatefulEditor when props change. You need to add useEffect to update.

const StatefulEditor = (props) => {
const [name, setName] = useState(props.item.name);
const [description, setDescription] = useState(props.item.description);

useEffect(() => {
setName(props.item.name);
setDescription(props.item.description);
}, [props.item]);

...

https://codesandbox.io/s/dervied-state-from-props-problem-forked-uiidu?file=/src/components/EditableList.jsx

Why React component doesn't re-render (change styles) when props changed?

This is a classic case of copying props into local state -- which you usually do not want to do as it introduces a surface area for bugs, including the one you are seeing. If something is available as a prop -- what is the purpose of copying it into local state? You should instead use callbacks to alter wherever the state of that prop lives in the ancestors. Copying means you now have to manage keeping the local state and prop in sync -- which is why usually copying in the first place is an antipattern.

The reason the state doesn't update when isSelected changes is because the parameter to useState is only its initial value. By design, even when a rerender occurs due to the prop changing, the state item wont update. Copying it into local state means its up to you to keep them in sync (common cause of bugs).

Two choices:

Option A

Don't copy props into state, so you don't even need to deal with making sure the props and internal state are in sync. Use isSelected directly and remove the state item. To set the selected state, you will need to pass down into the component from the parent a callback over props -- which accepts the changed value and changes the state in that parent component. This gets rid of the pointless barrier in between the props and the actual thing you are rendering.

Option B

If you must keep a copy of the state around for some reason, make sure you update the state when the props change with an additional effect.

type TagItemProps = {
tag: Tag;
isSelected: boolean;
onSelect: (tag: Tag) => boolean;
onDeselect: (tag: Tag) => void;
};

const TagItem = ({ tag, isSelected = false, onSelect, onDeselect }: TagItemProps) => {
const [selected, setSelected] = useState(isSelected);

useEffect(() => {
setSelected(isSelected )
}, [isSelected ])

console.log("isSelected: ", isSelected);
console.log("selected: ", selected);

const handleSelect = useCallback(() => {
if (!onSelect(tag)) return;
setSelected(true);
}, [onSelect, tag]);

const handleDeselect = useCallback(() => {
onDeselect(tag);
setSelected(false);
}, [onDeselect, tag]);

return (
<TouchableOpacity
style={[styles.container, selected ? styles.selected : null]}
onPress={selected ? handleDeselect : handleSelect}
>
<Text style={[styles.text, selected ? { color: '#fff' } : null]}>
{capitalizeFirstLetter(tag.name)}
</Text>
</TouchableOpacity>
);
};

export default TagItem;

const styles = StyleSheet.create({
container: {
flex: 0,
backgroundColor: '#fff',
borderRadius: 20,
paddingHorizontal: 10,
paddingVertical: 12,
margin: 5,
},
selected: {
backgroundColor: 'green',
},
text: {
textAlign: 'center',
fontWeight: 'bold',
},
});


Related Topics



Leave a reply



Submit