Reputation: 1651
In my react app, I have a custom hook useDataObject()
that computes an object based on the value of another hook, e.g. useScreenWidth()
. My problem is, that I don't where to place the calculation logic, so the object is ready immediately on the first return:
function useScreenWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return width;
}
function useDataObject() {
const width = useScreenWidth();
const [dataObject, setDataObject] = useState();
useEffect(() => {
// Some logic that creates dataObject from width
const dataObject = {
firstValue: 2 * width,
secondValue: width + 10
};
setDataObject(dataObject);
}, [width]);
return dataObject;
}
export default function App() {
const dataObject = useDataObject();
// Can I get rid of this check?
// Currently needed to prevent "Can not read property firstValue of undefined
if (!dataObject) {
return null;
}
return (
<>
<div>Data object first value: {dataObject.firstValue}</div>
<div>Data object second value: {dataObject.secondValue}</div>
</>
);
}
See this Sandbox
Because useDataObject
does not have an initial state I need to wait for the first useEffect
to finish until I get a valid dataObject
. This means I need to always include a null check after calls to useDataObject()
.
What would be the best approach to execute the calculation logic in useDataObject()
before the first return and then every time the width changes?
One approach I tried was creating an internal function that does the calculation:
function useDataObject() {
const width = useScreenWidth();
const [dataObject, setDataObject] = useState(calculateDataObject());
function calculateDataObject() {
// Some logic that creates dataObject from width
const dataObject = {
firstValue: 2 * width,
secondValue: width + 10
};
return dataObject;
}
useEffect(() => {
setDataObject(calculateDataObject());
}, [width, calculateDataObject]);
return dataObject;
}
But this looks pretty clunky and confusing. It also gives me a linting error telling me I should use useCallback()
. Isn't there a more straight forward way?
Thank you!
Upvotes: 2
Views: 858
Reputation: 1651
Answering my own question. I found using the useMemo()
hook (see docs) instead of useState()
+ useEffect()
is a possible solution for my scenario:
function useDataObject() {
const width = useScreenWidth();
return useMemo(() => {
// Do some calculation with width
const dataObject = {
firstValue: 2 * width,
secondValue: width + 10
};
return dataObject;
}, [width]);
}
dataObject
immediately. [width]
changes the calculation is redone and the new value is returned.I can not guarantee that there is no better solution, but for my purpose it does exactly what I need.
(Of course one could also integrate the useScreenWidth
hook into the useDataObject
Hook. But in my project, I need the useScreenWidth
separately.)
Upvotes: 1