Skip to content

Instantly share code, notes, and snippets.

@alanthai
Created April 22, 2020 02:50
Show Gist options
  • Save alanthai/0e4bdae4ad65114cc6608136f053e901 to your computer and use it in GitHub Desktop.
Save alanthai/0e4bdae4ad65114cc6608136f053e901 to your computer and use it in GitHub Desktop.
A more performant useReducer that only re-renders when the modified part of state is being read.
// example: https://stackblitz.com/edit/react-ts-hesbky?file=use-reducer.ts
// inspired by https://github.com/zeit/swr/pull/186/files
import {useState, useRef} from "react";
type StateDependencies<T> = {
[K in keyof T]?: boolean;
};
function equals(compareA, compareB) {
return compareA === compareB;
}
// only for shallow objects
export function useReducer<State extends object, Action>(
reducerFn: (state: State, action: Action) => State,
initialState: State,
comparator = equals
) {
const rerender = useState<any>({})[1];
const stateDependencies = useRef<StateDependencies<State>>({});
const stateRef = useRef<State>(initialState);
const countRef = useRef(0);
const trackersRef = useRef<Record<string, number>>({});
let getTracker = () => {
countRef.current++;
getTracker = () => countRef.current;
return getTracker();
};
const dispatch = (action: Action) => {
let shouldUpdateState = false;
const state = { ...stateRef.current };
const newState = reducerFn(stateRef.current, action);
stateRef.current = newState;
for (let k in newState) {
if (!comparator(state[k], newState[k])) {
if (stateDependencies.current[k] && trackersRef.current[k] === countRef.current) {
rerender({});
break;
}
}
}
};
const state: Readonly<State> = (() => {
const properties = Object.keys(stateRef.current).reduce((props, key) => {
props[key] = {
get: function () {
trackersRef.current[key] = getTracker();
stateDependencies.current[key] = true;
return stateRef.current[key];
},
};
return props;
}, {} as Readonly<State>);
return Object.defineProperties({}, properties);
})();
return [state, dispatch] as const;
}
export function useMerge<State extends object>(initialState: State) {
return useReducer(Object.assign, initialState);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment