Reputation: 2506
Ok, so I'm trying to create a custom hook "useObjectState" so I can utilize objects in functional components a little better and to be able to merge object props. Here's what I have so far:
const useObjectState = initialObject => {
const [obj, setObj] = useState(initialObject);
const setMergedObj = x => {
setObj(obj => ({ ...obj, ...x }));
};
return [obj, setMergedObj];
};
And now instead of calling the useState() setter function as follows:
setObject(obj => ({...obj, prop1: newValue}))
I can call it this way:
setObject({prop1: newValue});
and it will merge just fine. The problem I'm having is with batching multiple calls, where if I don't use the custom hook, then these calls will batch into separate calls and execute consecutively:
setObject(obj => ({...obj, prop1: oldVal + 1}));
setObject(obj => ({...obj, prop1: oldVal + 1}));
setObject(obj => ({...obj, prop1: oldVal + 1}));
But with the custom hook that I wrote that doesn't seem to work and the last call will override the first two:
setObject({prop1: oldVal + 1});
setObject({prop1: oldVal + 1});
setObject({prop1: oldVal + 1}); //only this function works, the other 2 are overwritten
Any idea on how to fix this?
SAMPLE CODE (as someone asked)
import React, { useState } from "react";
import "./App.css";
const useObjectState = initialObject => {
const [obj, setObj] = useState(initialObject);
const setMergedObj = x => {
setObj(obj => ({ ...obj, ...x }));
};
return [obj, setMergedObj];
};
function App() {
let [obj, setObj] = useObjectState({
num1: 1,
num2: 10
});
let [obj2, setObj2] = useState({
num1: 1,
num2: 10
});
const increment = x => {
setObj({ num1: obj.num1 + 1 });
setObj({ num1: obj.num1 + 1 });
setObj({ num1: obj.num1 + 1 }); //this will override the other 2
setObj({ num2: obj.num2 + 10 });
setObj2(obj2 => ({ ...obj2, num1: obj2.num1 + 1 }));
setObj2(obj2 => ({ ...obj2, num1: obj2.num1 + 1 }));
setObj2(obj2 => ({ ...obj2, num1: obj2.num1 + 1 })); //these 3 functions will add 3 at a time to obj2.num1
setObj2(obj2 => ({ ...obj2, num2: obj2.num2 + 10 }));
};
const decrement = () => {
setObj({ num1: obj.num1 - 1 });
setObj({ num2: obj.num2 - 10 });
};
return (
<div className="App">
<button onClick={() => increment(1)}>+</button>
<div>{obj.num1}</div>
<div>{obj.num2}</div>
<div>{obj2.num1}</div>
<div>{obj2.num2}</div>
<button onClick={() => decrement()}>-</button>
</div>
);
}
export default App;
Upvotes: 1
Views: 2646
Reputation: 1214
One issue I see is that you are passing a reference to the original object:
setObj({num1: obj.num1 + 1});
setObj()
's argument has no way of referencing the internally updated state, so obj.num1
will always reference the original value returned by the hook.
You might want to consider allowing your hook to receive a function, the way the original useState
does, and rewriting your hook like:
const setMergedObj = x => {
typeof x === "function"
? setObj(obj => ({...obj, ...x(obj)}))
: setObj(obj => ({...obj, ...x}))
;
};
Then using it like
setObj(obj => ({num1: obj.num1 + 1}));
I can see the object-only version still being relevant, if you're updating date from elsewhere, but you'll need the functional version for referencing the existing state.
Upvotes: 3