This morning I was trying to render a child component that was receiving props. The problem was that it didn't rerender after the props changed. 😱
Let's analyze what the hell was happening...
A simplified version of the parent was:
jsximport react, { useState, useEffect } from 'react'; import moment from 'moment'; const App = () => { const [time, setTime] = useState(moment()); return( <Timer time = { time } setTime = { setTime } /> ); } export default App;
The child got the time from the parent and I could add minutes to it by clicking a button:
jsxconst Timer = ({ time, setTime }) => { const addMinutes = (mins) => { // Adding 15 minutes to the timer let newTime = time.add(15, 'minute'); // Setting new state setTime(newTime); } return( <div className = 'Timer'> The time is {time.format('LT')} <button onClick = { () => addMinutes(15) }>Click</button> </div> ) }
After clicking the button of the timer, the text rendered remained equal to the one on the first render. 🧐
The problem was that I was adding 15 minutes to the exact same object received as a prop. Since I was modifying the same reference, React couldn't detect a state change. Hence, the child wasn't rerendering.
Let's refresh some JavaScript theory:
jsxlet object = {name: 'Erik', age: 29}; // Here we are not making a copy of the object // Both objects have the same reference let objA = object; let objB = object; // We are modifying the original object objA.age = 99; // Hence... console.log(objA.age); // 99 console.log(objB.age); // 99
The solution is to clone the original object:
jsxlet object = {name: 'Erik', age: 29}; // We create a copy of the original object // Using the spread (...) operator let objA = {...object}; let objB = {...object}; // Now we aren't modifying the original object objA.age = 99; // Hence... console.log(objA.age); // 99 console.log(objB.age); // 29
I needed to clone the original object received as a prop:
jsxconst Timer = ({ time, setTime }) => { const addMinutes = (mins) => { // ✅ Cloning the time object let clone = moment(time); // Adding 15 minutes to the timer let newTime = clone.add(15, 'minute'); // Setting new state setTime(newTime); } return( <div className = 'Timer'> The time is {time.format('LT')} <button onClick = { () => addMinutes(15) }>Click</button> </div> ) }
Now React was detecting that the previous state and the current one were different objects. Thus, the parent/child rerendered.
Hi, I'm Erik, an engineer from Barcelona. If you like the post or have any comments, say hi.