Search code examples
javascriptreactjsreact-props

Prop passed to child component does not change its value in child upon updating prop in parent


I have two functional components called Crucials and Timer, where Crucials is the parent and Timer is the Child.

Crucials has the user time data that needs to be passed down to the Timer for it to start the timer from the time provided by user.

My parent component is as follows:

export default function Crucials() {

const [Hdata, setHData] = useState(0);
const [Mdata, setMData] = useState(0);
let data = {
    Hour: 0,
    Min: 0,
    Sec: 0
};
function timerStart(){
    if(Hdata === 0 && Mdata === 0){
        console.log("Minimum Timer Initiated");
        setHData((minH)=> minH = 0);
        setMData((minM)=> minM = 15);
    }
    data.Hour = 0;
    data.Min = Mdata;
    data.Secy = Hdata; 
}


return (<div>
    <div><Timer timerdata={data}/></div>
    <Button variant="contained" color="primary" onClick={timerStart}> Start Timer</Button>
    <form className={styles.HTM} onSubmit={timerStart}>
        <Input type="number" onChange={e => setHData(e.target.value) } id="outlined-basic" placeholder="Hours(H)" ></Input>
        <Input type="number" id="outlined-basic"  onChange={e => setMData(e.target.value)} placeholder="Minutes(M)" ></Input>
    </form>
</div>

)}

My child is as follows:

export default function Timer(timerdata) {

  let Seconds = timerdata.Sec;
  let Minutes = timerdata.Min;
  let Hours = timerdata.Hour;
....
...
}

All I want to do is for me to be able to pass the data object to the child and for it to only update when timerStart is invoked via the Start Timer button.

However, what is happening is that the data object passed to the child only ever holds the initialization values (0,0,0) and invoking timerStart function, which changes the values within data, is not reflected in the Child.

Also, for some odd reason, just inputting values into the two <Input> fields causes child to update data, but it still holds the initial values rather than what the user input.

I'm probably doing something wrong, but I'm unable to figure it out. Any help is appreciated.


Solution

  • You need to make data a state value, currently:

    • your updates to your object won't persist between re-renders. Using setHData/setMData to update your state causes your component function to be called/executed again, redefining local variables such as the data object to the initial values
    • updating your data alone using data.Hour = 0; etc. won't cause your component/children components to re-render with the updated values. As a result, you need to use react's state setter function, setX to signal to react that your data object has changed and that your component needs to re-render using the new state values.

    Instead, create a new state value that holds your data object, and use setData() to update it. This will cause your update to re-render your component + its children:

    const {useState} = React;
    const App = () => {
      const [data, setData] = useState({hour:1,min:2});
      const clickHandler = () => {
        setData({hour: 10, min: 20}); // use input values instead of hard-coding values
      };
      
      return <div>
        <Child data={data} />
        <button onClick={clickHandler}>Update data</button>
      </div>
    }
    
    const Child = (props) => <div>
      <p>H: {props.data.hour}</p>
      <p>M: {props.data.min}</p>
    </div>;
    
    ReactDOM.render(<App />, document.body);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>

    Side note:

    Your code setHData((minH)=> minH = 0); can be written as setHData(0);, and same with setMData(15). To update the state value, all you need to do is pass the new value to the state setter function. You don't need to pass an arrow function here, as your new value (0, and 15) doesn't rely on the previous state value.