Skip to content

Instantly share code, notes, and snippets.

@theluk
Last active July 11, 2021 12:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theluk/13b7a17455b599699b7d34775cbf8273 to your computer and use it in GitHub Desktop.
Save theluk/13b7a17455b599699b7d34775cbf8273 to your computer and use it in GitHub Desktop.
useLocal: like useState, but updates if initial value changes

In situations where you initialize useState with some value and later on you want to update the state, because the parent changed, you would need to currently listen on the parent change in an useeffect and then set the state to update it, or more precisely, update the local version.

For this a hook I use quite often for forms or temporarily updated values is the useLocal hook.

Let's take this example

const RangeDatepicker = ({ value: { from, to }, onChange }) => {
   const [from, setFrom] = useLocal(from)
   const [to, setTo] = useLocal(to)
   const update = () => {
    if (from && to) {
      onChange({ from, to });
    }
   }
   
   useEffect(update, [from, to])
   
   return <MyCalendarRangePicker from={from} to={to} onChangeFrom={setFrom} onChangeTo={setTo} />
}

It works as follows.

  1. the controlling component sets from and to
  2. the RangeDatepicker copies the initial values
  3. On every change of the RangeDatepicker, locally values are updated, but not propagated back
  4. When both values (from and to) are set correctly, the onChange will be triggered
  5. the parent then updates the local values using the inner logic of useLocal
  6. the parent can always decide to override local values in a controlled manner

Notes

Note that the parent would always override the local version if it would be for example an object that is genreated on each render. So make sure, when you are creating computed values in an object, that the computed value must be memoized, when you are using this hook

import { useState, useEffect, Dispatch, SetStateAction } from 'react';
export function useLocal<S>(
remote: S | (() => S),
): [S, Dispatch<SetStateAction<S>>] {
const [data, setData] = useState(remote);
useEffect(() => setData(remote), [remote]);
return [data, setData];
}
import { useState, useEffect, Dispatch, useRef } from 'react';
type CheckFunc<S> = (nextRemoteValue: S, currentLocalValue: S) => boolean;
export function useLocal<S>(
remote: S,
condition?: CheckFunc<S>,
): [S, Dispatch<S>] {
const [data, setData] = useState(remote);
const canUpdateRef = useRef<undefined | ((nextVal: S) => boolean)>();
canUpdateRef.current = condition
? (next: S) => condition(next, data)
: undefined;
useEffect(() => {
if (canUpdateRef.current && !canUpdateRef.current(remote)) {
return;
}
setData(remote);
}, [remote]);
return [data, setData];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment