The State of
But why?
So i would like to
So lets get the basics out of the way
const Component = () => {
const [count, setCount] = useState(0);
return (
<div>
{count}
<button onClick={() => setCount(count => ++count)}>+</button>
<button onClick={() => setCount(count => --count)}>-</button>
</div>
);
}
https://jsfiddle.net/3qLrcg2e/
const StateContext = React.createContext();
const Root = () => {
const state = React.useState(0);
return (
<StateContext.Provider value={state}>
<Component />
</StateContext.Provider>
);
}
const Component = () => {
const [count, setCount] = React.useContext(StateContext);
return (
<div>
{count}
<button onClick={() => setCount(count => ++count)}>+</button>
<button onClick={() => setCount(count => --count)}>-</button>
</div>
);
}
ReactDOM.render(<Root />, document.querySelector("#app"))
https://jsfiddle.net/3qLrcg2e/1/
The mental model of React
Okay thats cool...
Performance
Flexibility
Features
DX
Performance
Flexibility
Features
DX
Transient updates
Immutability/Reactivity
Selectors
Performance
Flexibility
Features
DX
Internal/external
Composable
derived or computed state
Performance
Flexibility
Features
DX
Runtime type checking
Timetravel
Patches
Performance
Flexibility
Features
DX
Debugging tools
Infered typechecking
Boilerplate
Performance
Flexibility
Features
DX
Lets take a look at a few options
Is based on Immutability
Is managed internally
Opinionated structure
Centralized state
Is based on observability
Is managed externally
Flexible structure
Distributed state
// Actions
export const add = () => ({
type: 'ADD',
})
export const subtract = () => ({
type: 'SUBTRACT',
})
// Reducers
const reducer = (state = { count: 0 }, action) => {
switch (action.type) {
case 'ADD':
return { ...state, count: state.count + 1 }
case 'SUBTRACT':
return { ...state, count: state.count - 1 }
default:
return state
}
}
// Selectors
const getCount = (state) => state.count;
const Component = ({count, add, subtract}) => {
return (
<div>
<h2>{count}</h2>
<button onClick={add}>
+
</button>
<button onClick={subtract}>
-
</button>
</div>
)
)
const mapStateToProps = state => ({
count: getCount(state)
})
const mapDispatchToProps = dispatch => ({
add: () => dispatch(actions.add()),
subtract: () => dispatch(actions.subtract())
})
const ConnectedComponent = connect(
mapStateToProps,
mapDispatchToProps
)(Component)
// const store = createStore(reducer);
// render(
// <Provider store={store}>
// <App />
// </Provider>,
// document.getElementById('root')
// )
const appState = observable({
count: 0,
subtract: action(function() {
appState.count -= 1;
}),
add: action(function () {
appState.count += 1;
})
})
const Component = observer(() => {
return (
<div>
<h2>{appState.count}</h2>
<button onClick={appState.add}>
+
</button>
<button onClick={appState.subtract}>
-
</button>
</div>
)
})But these are the OG state managers...
Is based on Immutability
Is managed internally
Opinionated structure
Centralized state
Is based on Immutability
Is managed externally
Flexible structure
Distributed state
const countAtom = atom(0);
const addCountAtom = atom(null, (_get, set) => {
set(countAtom, (c) => c + 1);
});
const subtractCountAtom = atom(null, (_get, set) => {
set(countAtom, (c) => c - 1);
});
const Component = () => {
const [count] = useAtom(countAtom);
const [, add] = useAtom(addCountAtom);
const [, subtract] = useAtom(subtractCountAtom);
return (
<div>
{count}
<button onClick={add}>
+
</button>
<button onClick={subtract}>
-
</button>
</div>
);
};const useCountStore = create((set) => ({
count: 0,
add: () => set((prev) => ({ count: prev.count + 1 })),
subtract: () => set((prev) => ({ count: prev.count - 1 }))
}));
const Component = () => {
const state = useStore();
return (
<div>
{state.count}
<button onClick={state.add}>
+
</button>
<button onClick={state.subtract}>
-
</button>
</div>
);
};But which state manager did i pick?
Is based on Immutability
Is managed externally
Flexible structure
Distributed state
Is based on Reactivity
Is managed externally
Flexible structure
Distributed state
const useCountStore = create((set) => ({
count: 0,
add: () => set((prev) => ({ count: prev.count + 1 })),
subtract: () => set((prev) => ({ count: prev.count - 1 }))
}));
const Component = () => {
const state = useStore();
return (
<div>
{state.count}
<button onClick={state.add}>
+
</button>
<button onClick={state.subtract}>
-
</button>
</div>
);
};const countState = proxy({
count: 0,
});
const add = () => { countState.count += 1 };
const subtract = () => { countState.count -= 1 };
const Component = () => {
const state = useSnapshot(countState);
return (
<div>
{state.count}
<button onClick={add}>
+
</button>
<button onClick={subtract}>
-
</button>
</div>
);
};Immutability stays close to react
Works as expected.
Manual render optimization
Tends to use less memory
Less compatible with built-in react features
Proxies are unusual in react.
Works magically
Automatic render optimization
Tends to use more memory
More compatible with built-in react features
Thanks for great state managers
If someone comes from redux or likes react's natural immutable updates, zustand would fit well.
If someone is from vue/svelte/mobx, or new to JS and/or react, valtio's mutable model would fit well.
zustand is a very thin library and never behaves unexpectedly. valtio comes with proxy magic.
Oh my...
Immer
Recoil
State Machines
Server cache state
Form state
unstated
Server side rendering support
Packages: React-Query, Apollo
Purpose: Maintain server state
Packages: Xstate
Purpose: Maintain the relations between states.
Useful links for when you are figuring out...