Home > Net >  Getting previous State of useState([{}]) (array of objects)
Getting previous State of useState([{}]) (array of objects)

Time:12-11

I am struggling to get the real previous state of my inputs. I think the real issue Which I have figured out while writing this is my use of const inputsCopy = [...inputs] always thinking that this creates a deep copy and i won't mutate the original array.

const [inputs, setInputs] = useState(store.devices)

store.devices looks like this

devices = [{
 name: string,
network: string,
checked: boolean,
...etc
}]

I was trying to use a custom hook for getting the previous value after the inputs change. I am trying to check if the checked value has switched from true/false so i can not run my autosave feature in a useEffect hook.

function usePrevious<T>(value: T): T | undefined {
    // The ref object is a generic container whose current property is mutable ...
    // ... and can hold any value, similar to an instance property on a class
    const ref = useRef<T>();

    // Store current value in ref
    useEffect(() => {
        ref.current = value;
    }); // Only re-run if value changes

    // Return previous value (happens before update in useEffect above)
    return ref.current;
}

I have also tried another custom hook that works like useState but has a third return value for prev state. looked something like this.

const usePrevStateHook = (initial) => {
    const [target, setTarget] = useState(initial)
    const [prev, setPrev] = useState(initial)

    const setPrevValue = (value) => {
        if (target !== value){ // I converted them to JSON.stringify() for comparison
            setPrev(target)
            setTarget(value)
        }
    }
    return [prev, target, setPrevValue]

}

These hooks show the correct prevState after I grab data from the api but any input changes set prev state to the same prop values. I think my issue lies somewhere with mobx store.devices which i am setting the initial state to or I am having problems not copying/mutating the state somehow. I have also tried checking what the prevState is in the setState

setInputs(prev => {
    console.log(prev)
    return inputsCopy
})

After Writing this out I think my issue could be when a value changes on an input and onChange goes to my handleInputChange function I create a copy of the state inputs like

const inputsCopy = [...inputs]
inputsCopy[i][prop] = value
setInputs(inputsCopy)

For some reason I think this creates a deep copy all the time. I have had hella issues in the past doing this with redux and some other things thinking I am not mutating the original variable.

Cheers to all that reply!

EDIT: Clarification on why I am mutating (not what I intended) I have a lot of inputs in multiple components for configuring a device settings. The problem is how I setup my onChange functions <input type="text" value={input.propName} name="propName" onChange={(e) => onInputChange(e, index)} />

const onInputChange = (e, index) => {
    const value = e.target.value;
    const name = e.target.name;
    const inputsCopy = [...inputs]; // problem starts here
    inputsCopy[index][name] = value; // Mutated obj!?
    setInputs(inputsCopy);
}

that is What I think the source of why my custom prevState hooks are not working. Because I am mutating it.

my AUTOSAVE feature that I want to have the DIFF for to compare prevState with current

const renderCount = useRef(0)
useEffect(() => {
    renderCount.current  = 1
    if (renderCount.current > 1) {
        let checked = false
       // loop through prevState and currentState for checked value
       // if prevState[i].checked !== currentState[i].checked checked = true

        if (!checked) {
            const autoSave = setTimeout(() => {
                // SAVE INPUT DATA TO API
            }, 3000)

            return () => {
               clearTimeout(autoSave)
            }
        }
    }
}, [inputs])

Sorry I had to type this all out from memory. Not at the office.

CodePudding user response:

If I understand your question, you are trying to update state from the previous state value and avoid mutations. const inputsCopy = [...inputs] is only a shallow copy of the array, so the elements still refer back to the previous array.

const inputsCopy = [...inputs] // <-- shallow copy
inputsCopy[i][prop] = value    // <-- this is a mutation of the current state!!
setInputs(inputsCopy)

Use a functional state update to access the previous state, and ensure all state, and nested state, is shallow copied in order to avoid the mutations. Use Array.prototype.map to make a shallow copy of the inputs array, using the iterated index to match the specific element you want to update, and then also use the Spread Syntax to make a shallow copy of that element object, then overwrite the [prop] property value.

setInputs(inputs => inputs.map(
  (el, index) => index === i
    ? {
      ...el,
      [prop] = value,
    }
    : el
);

Though this is a Redux doc, the Immutable Update Patterns documentation is a fantastic explanation and example.

Excerpt:

Updating Nested Objects

The key to updating nested data is that every level of nesting must be copied and updated appropriately. This is often a difficult concept for those learning Redux, and there are some specific problems that frequently occur when trying to update nested objects. These lead to accidental direct mutation, and should be avoided.

  • Related