--- id: recipes title: Recipes sidebar_label: Recipes --- React Tracked provides a primitive API, and there are various ways to use it for apps. ## Recipes for createContainer The argument `useValue` in `createContainer` is so flexible and there are various usages. ### useReducer (props) This is the most typical usage. You define a generic reducer and pass `reducer` and `initialState` as props. ```javascript const { Provider, useTracked, // ... } = createContainer(({ reducer, initialState, init }) => useReducer(reducer, initialState, init)); const reducer = ...; const App = ({ initialState }) => ( ... ); ``` ### useReducer (embedded) For most cases, you would have a static reducer. In this case, define useValue with the reducer in advance. The `initialState` can be defined in useValue like the following example, or can be taken from props: `({ initialState }) => useReducer(...)` This is good for TypeScript because the hooks returned by `createContainer` is already typed. ```javascript const reducer = ...; const initialState = ...; const { Provider, useTracked, // ... } = createContainer(() => useReducer(reducer, initialState)); const App = () => ( ... ); ``` ### useState (props) If you don't need reducer, useState would be simpler. ```javascript const { Provider, useTracked, // ... } = createContainer(({ initialState }) => useState(initialState); const App = ({ initialState }) => ( ... ); ``` ### useState (empty object) You could even start with completely an empty object. This might not be TypeScript friendly. Although, you could do this: `useState({})` ```javascript const { Provider, useTracked, // ... } = createContainer(() => useState({}); const App = () => ( ... ); ``` ### useReducer (with persistence) Here's how to persist state in localStorage. ```javascript const reducer = ...; const initialState = ...; // used only if localStorage is empty. const storageKey = 'persistedState'; const init = () => { let preloadedState; try { preloadedState = JSON.parse(window.localStorage.getItem(storageKey)); // validate preloadedState if necessary } catch (e) { // ignore } return preloadedState || initialState; }; const useValue = () => { const [state, dispatch] = useReducer(reducer, null, init); useEffect(() => { window.localStorage.setItem(storageKey, JSON.stringify(state)); }, [state]); return [state, dispatch]; }; const { Provider, useTracked, // ... } = createContainer(useValue); const App = () => ( ... ); ``` Using async storage is a bit tricky. See [the thread](https://github.com/dai-shi/react-tracked/issues/8#issuecomment-548095476) for an example. ### useState (with propState) If you already have a state and would like to use Provider with it, you can sync a container state with a state from props. ```javascript const useValue = ({ propState }) => { const [state, setState] = useState(propState); useEffect(() => { // or useLayoutEffect setState(propState); }, [propState]); return [state, setState]; }; const { Provider, useTracked, // ... } = createContainer(useValue); const App = ({ propState }) => ...; ``` Note that `propState` has to be updated immutably. ### useReducer (with event listener) Here's how to dispatch actions by DOM events. ```javascript const reducer = ...; const initialState = ...; const useValue = () => { const [state, dispatch] = useReducer(reducer, initialState); useEffect(() => { const listener = () => { dispatch({ type: 'WINDOW_RESIZED', width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', listener); return () => { window.removeEventListener('resize', listener); }; }, []); return [state, dispatch]; }; const { Provider, useTracked, // ... } = createContainer(useValue); const App = () => ( ... ); ``` ### useState (with update functions) If you want to have custom update functions, you can store them in a state object. Be sure to use `useCallback` and `useMemo` to make the state object stable. ```javascript const useValue = () => { const [count, setCount] = useState(0); const increment = useCallback(() => setCount((c) => c + 1), []); const decrement = useCallback(() => setCount((c) => c - 1), []); const state = useMemo( () => ({ count, increment, decrement, }), [count, increment, decrement], ); return [ state, () => { throw new Error('use functions in the state'); }, ]; }; const { Provider, useTrackedState } = createContainer(useValue); const App = () => ...; ``` Note: With custom update functions, you don't get the benefit even if you enable `concurrentMode` in `createContainer`. ## Recipes for useTrackedState and useTracked The `useTrackedState` and `useTracked` hooks are useful as is, but new hooks can also be created based on them. ### useSelectorWithTracking Selector interface is useful to share selection logic. You can create a selector hook with state usage tracking very easily. ```javascript const useSelectorWithTracking = (selector) => selector(useTrackedState()); ``` Note: This is different from `useSelector` which has no tracking support and triggers re-render based on the ref equality of selected value. ### useTrackedByName (based on useState) Sometimes, you might want to select a state by its property name. Here's a custom hook to return a tuple `[value, setValue]` selected by a name. ```javascript const useTrackedByName = (name) => { const [state, setState] = useTracked(); const update = useCallback( (newVal) => { setState((oldVal) => ({ ...oldVal, [name]: typeof newVal === 'function' ? newVal(oldVal[name]) : newVal, })); }, [setState, name], ); return [state[name], update]; }; ``` ### useTrackedWithImmer (based on useState) Updating a property deep in a state object is troublesome. Here's a custom hook to use [immer](https://github.com/immerjs/immer) for setState. ```javascript import produce from 'immer'; const useTrackedWithImmer = () => { const [state, setState] = useTracked(); const update = useCallback( (updater) => { setState((oldVal) => produce(oldVal, updater)); }, [setState], ); return [state, update]; }; ``` Note: This can also be done at `createContainer`. ## Recipes for useUpdate (useDispatch) The `useUpdate` simply returns the second item in a tuple returned by `useState` or `useReducer`. It can also be extended as a custom hook. ### useSafeDispatch This is a modified version of useDispatch that calls `getUntrackedObject` recursively on an action object before dispatching it. ```javascript import { getUntrackedObject } from 'react-tracked'; const untrackDeep = (obj) => { if (typeof obj !== 'object' || obj === null) return obj; const untrackedObj = getUntrackedObject(obj); if (untrackedObj !== null) return untrackedObj; const newObj = {}; let modified = false; Object.entries(obj).forEach(([k, v]) => { newObj[k] = untrackDeep(v); if (newObj[k] !== null) { modified = true; } else { newObj[k] = v; } }); return modified ? newObj : obj; }; const useSafeDispatch = () => { const dispatch = useDispatch(); return useCallback( (action) => { dispatch(untrackDeep(action)); }, [dispatch], ); }; ```