0

How to add keep updating state with new Time but to the same object key: 1?

This is how it should look:

time: ["21:10:56", "21:10:56", "21:10:56"]

Currently every time i press a key it creates a new object.

enter image description here

Same goes when i enter value in Number Two input to have its own key: 2 object and add time value to it.

const {useState, useEffect} = React;

const App = () => {
  const [state, setState] = useState([])

  const handleChange = (e, key) => {
    const d = new Date()
    const time = `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`

    setState((prevState) => {
      // console.log(state[0].time)
      // const initialArray = state.concat({ time })
      // console.log('test', initialArray)
      // const newArray = [...initialArray, { time }]

      return [...prevState, { key, time: [time] }]
      // return prevState.concat({ key, time: [time] })
    })
  }
  
    useEffect(() => {
    console.log(state)
  }, [state])
  
  return (
    <div>
      <div>
        <p>
          {`Number One:`}
          <input type="number" onChange={(e) => handleChange(e, 1)} />
        </p>
      </div>

      <div>
        <p>
          {`Number Two:`}
          <input type="number" onChange={(e) => handleChange(e, 2)} />
        </p>
      </div>
    </div>
  );
};

ReactDOM.render(
  <App/>,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>

3 Answers 3

2

I would change the state to be an object using key as the property name and time as the value, and then use something like JakeParis' answer.

But if that's what you're stuck with:

setState((prevState) => {
  return [...(prevState?.filter(el => el.key !== key) || []), {
    key,
    time: [...(prevState?.find(el => el.key === key)?.time || []), time]
  }];
})

This returns a new array with (first spread) all of the elements in the previous state where the key is different, then a new copy of the object with the key you're working with, with the time value set to an array of the previous values of the time array of the element with that key (or an empty array, in case this is a new key), and the time passed in.

The above works for browsers supporting optional chaining, which is a surprising number of them. Here's one that works for older browsers:

setState((prevState) => {
  return prevState ? [...prevState.filter(el => el.key !== key), {
    key,
    time: [...(prevState.find(el => el.key === key) ? prevState.find(el => el.key === key).time : []), time]
  }] : [{key, time: [time] }];
})
Sign up to request clarification or add additional context in comments.

7 Comments

Change this ...prevState.filter(el => el.key !== key), to ...prevState.filter(el => el.key !== key || []) might work!
Tried Heretic and Zabeeh did not work i assume because my state is empty and thus getting undefined,
I added optional chaining, which will only work in very recent versions of browsers while I construct a workaround for less modern ones.
@ZabeehUllahBabar filter will return an empty array if nothing matches the test function.
@HereticMonkey I like your answer because it create a new object. My solution even I added array empty checks won't call the useEffect((), [state]) because it doesn't realize state changed.
|
2

You are adding to the prevState a new item by doing this return [...prevState, { key, time: [time] }]. Instead you need to find the item that has the same key as the one the called handle change and push the time to the time array or create a new object if item is not found.

 const handleChange = (e, key) => {
  const d = new Date()
  const time = `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`

  setState((prevState) => {
    let object = prevState.find(obj => obj.key === key);
    if (!object) {
      prevState.push({key, time: [time]});
    }  
    else object.time.push(time)

    return prevState;
  })
}

1 Comment

I am doing this in case prevState is empty or item with that key wasn't found prevState.push({key, time: [time]}); And i believe you should be able to return the same variable as they are just holding values. It just happens to be that we edited the same variable. You could copy the prevState to newState and edit that and return it, but same thing.
1

I believe that your error is here:

return [...prevState, { key, time: [time] }]

That should maybe be:

const newTimeArray = [...prevState[key].time];
newTimeArray.push(time);
return { ...prevState, key: newTimeArray };

Update

HereticMonkey noted that state is an array not an object, so this should be returning an array. But the general idea still holds.

3 Comments

or, more succinctly, return { ...prevState, key: [...prevState[key], time] }; But not as an answer to this question, because state is an array, not an object.
@HereticMonkey That's what I get for answering a React question as a plain-javascript person!
It's a good answer, I'd keep it and suggest the OP change their data structure to match. It would make much more sense to me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.