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.