No more Redux (or Context) to manage complex data in your react applications, try “useSyncExternalStore” instead
Simplifying State Management in React with useSyncExternalStore
As we all know, managing complex data in React applications can be a challenge. Context just resolving props drilling it will still re render all its children when a minor change happened in state.
Redux is the popular solution, but it can be overkill for simple use cases. And huge bundle size!. With React 18, we have a new hook called useSyncExternalStore that can help us create a global state management system without the need for Redux or Context.
Creating a Custom Store and Reducer
Let’s create a simple store and reducer to manage an array of editable items. We’ll define a Store interface with getState, dispatch, and subscribe methods. We’ll also create a ListReducer interface to represent the state of our array.
// store.ts
import { useCallback } from "react";
import { useSyncExternalStore } from "react";
import { DispatcherActions, initialState, ListReducer, StoreAction } from "./reducer";
export interface Store {
getState: () => ListReducer;
dispatch: (fn: DispatcherActions[string], action?: StoreAction) => void;
subscribe: (onStoreChange: () => void) => () => void;
}
const createStore = (): Store => {
let state = initialState;
const getState = (): ListReducer => state;
const listeners: Set<() => void> = new Set();
const dispatch = (fn: DispatcherActions[string], action?: StoreAction) => {
state = fn(state, action || { payload: undefined });
listeners.forEach((l) => l());
};
const subscribe = (listener: () => void) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
return { getState, dispatch, subscribe };
};
//State created here.
export const store: Store = createStore();
Implementing useAppSelector
Next, we’ll create a custom useAppSelector hook that uses useSyncExternalStore to select a specific part of the state.
// store.ts
export const useAppSelector = (store: Store, selector: (state: ListReducer) => string) =>
useSyncExternalStore(
store.subscribe,
useCallback(() => selector(store.getState()), [store, selector]),
);
// reducer.ts
export interface ListReducer {
data: string[];
}
export const initialState: ListReducer = {
data: []
};
export interface StoreAction {
payload: any;
type?: string;
}
export interface DispatcherActions {
[key: string]: (state: ListReducer, action: StoreAction) => ListReducer;
}
const actions: DispatcherActions = {
changeData(state, action) {
state.data[action.payload.i] = action.payload.value;
return state;
},
};
export const {
changeData,
} = actions;
Now create react components
import Cell from "./cell";
import { store, useAppSelector } from "./store";
function App() {
const items = useAppSelector(store, (state) => state.data);
return (
<>
{items.map((d: string, i: number) => (
<Cell i={i} />
))}
</>
);
}
export default App;
import { changeData } from "./store/reducer";
import { store, useAppSelector } from "./store";
const Cell = ({ i }: { i: number }) => {
//Read only the expected value
const val = useAppSelector(store, (state) => state.data[i]);
const onChange = (e: { target: { value: any } }) => {
store.dispatch(changeData, { payload: { val: e.target.value, i: i } });
};
console.log("Print This one each render Cell index:", i);
return <input value={val} onChange={onChange} />;
};
export default Cell;
Try this and type something in your text box and check console 😃. only one cell should be getting re rendered.
https://medium.com/media/1e7a4e7820c778caa2d3be31d72cc1c4/href
Rendering a Large Number of Input Boxes
This solution can even handle rendering a large number of input boxes without performance issues.
Yup it is working with zero issues. I have used same solution in my excel package,
More information of package can be found here
What If you need to render a lot of input boxes in screen?.
Hope this is help full.
Thanks
Sojin Antony
Top comments (0)