Friday, November 10, 2023

Zustand computed values, pt. 2

I’ve been trying to find a way for computed values in Zustand for a while. The general recommended way is using a custom hook, according to Daishi Kato himself. The issue with this approach, however, is that your computed logic will be completely alienated from the store itself.

I just found a neat approach which seems to work: using a nested object inside the store itself, as pointed in this highly cheered comment. Below is a fully typed example:

import {create} from 'zustand';
import {combine} from 'zustand/middleware';
import {immer} from 'zustand/middleware/immer';

export interface Person {
	name: string;
	age: number;
}

const usePeople = create(immer(
	combine({
		people: [] as Person[],
	},
	(set, get) => ({
		$computed: {
			get count(): number {
				return get().people.length;
			},
		},
		addNew(name: string, age: number): void {
			set(state => {
				state.people.push({name, age});
			});
		},
	})),
));

export default usePeople;

The tradeoff, as benchmarked by myself, is that this $computed getters are not cached and will run every time the state changes, in addition to every time they are requested – which happens when the component re-renders. The ordinary custom hook approach runs only when they are requested. So, there is a performance penalty, which can be huge if the logic is too demanding.

No comments: