Reputation: 808
function SchrödingersDiff()
{
const [count, setCount] = useState(0);
// simulate a changing state
useEffect(() =>
{
const int = window.setInterval(() => setCount(s => s + 1), 1000);
return () => window.clearInterval(int);
}, []);
const last = useRef(0);
const diff = useMemo(() =>
{
const lastValue = last.current;
last.current = count;
const diff = count - lastValue;
// console.log(diff);
return diff;
}, [count]);
// console.log(diff);
return diff;
}
Why does this component always render "0" when running under <React.StrictMode>
? It works fine (i.e. renders "1") without strict mode.
I know strict mode renders things twice, which would explain it, but I would expect useMemo
to catch that - and it does: the log in the memo function prints the correct "1" every second. Even the one outside does.
Is there a better way to calculate that diff?
This is my actual useState
/useEffect
/setInterval
, already adapted to keep the last:
export function useChannel(channelName, initialData, currentOnly)
{
const socket = useSc(s => s.socket); // gets a SocketCluster socket
const [data, setData] = useState(currentOnly ? initialData : {current: initialData});
useEffect(() =>
{
if (!socket) return;
const channel = socket.subscribe(channelName);
(async () =>
{
for await (const data of channel) setData(currentOnly ? data : s => ({current: data, last: s.current}));
})();
return () => channel.unsubscribe();
}, [socket, channelName]);
return data;
}
Upvotes: 2
Views: 563
Reputation: 664297
Is there a better way to calculate that diff?
I would extract the useState
hook, and put the calculation logic into the setState
function. You kinda did that already, but we can make this a bit more generic and reusable:
export function useCurrentChannel(channelName, initialData) {
const [data, setData] = useState(initialData)
useChannelSubscription(channelName, setData);
return data;
}
export function useChannelSubscription(channelName, onNewData) {
const socket = useSc(s => s.socket);
useEffect(() => {
if (!socket) return;
const channel = socket.subscribe(channelName);
(async () => {
for await (const data of channel) {
onNewData(data);
}
})();
return () => channel.unsubscribe();
}, [socket, channelName, onNewData]); // or put `onNewData` in a Ref
return socket;
}
Now you can also write
export function useHistoricChannel(channelName, initialData) {
const [data, setData] = useState({current: initialData})
const handleNewData = useCallback(
(data) => setData(s => ({current: data, last: s.current})),
[setData]
);
useChannelSubscription(channelName, handleNewData);
return data;
});
which might be nicer with a reducer:
export function useHistoricChannel(channelName, initialData) {
const [data, handleNewData] = useReducer(
(s, data) => ({current: data, last: s.current}),
{current: initialData}
);
useChannelSubscription(channelName, handleNewData);
return data;
});
Of course you can make the reducer compute anything (like a diff between current
and last
), without having to change useChannelSubscription
.
Upvotes: 1
Reputation: 773
You can define a useDiff
custom hook that internally keeps a state with the current and the last values so you can update both from your setInterval
callback like so:
const useDiff = () => {
const [state, setState] = useState({
current: 0,
last: 0,
});
useEffect(() => {
const interval = window.setInterval(() => {
setState((s) => ({ current: s.current + 1, last: s.current }));
}, 1000);
return () => window.clearInterval(interval);
}, []);
return state.current - state.last;
};
Demo: https://stackblitz.com/edit/react-akituv
Upvotes: 1