NEW
View on Spotify
Catch this episode and more on Spotify Stay updated with the latest in tech and climate solutions.
Catch this episode and more on Spotify Stay updated with the latest in tech and climate solutions.
Explore various libraries, patterns, and approaches to handle state efficiently in React applications.
As React applications expand, managing state becomes increasingly intricate. This is a discussion of sophisticated state management strategies, libraries, and patterns:
useState
or useReducer
hooks.useMemo
, useCallback
) to optimize performance and avoid unnecessary re-renders.Redux
MobX
Recoil
Context API
React.createContext
.Provider
component to pass the current context value down the tree.useContext
to consume the context in child components.State Machines (XState)
useMachine
hook to manage the state within your components.Component Local State
useState
or useReducer
hooks is sufficient.javascriptconst [state, setState] = useState(initialState);
Global State Management
javascriptconst store = createStore(reducer);
<Provider store={store}>
<App />
</Provider>
Normalization
normalizr
to transform nested data into a normalized shape.Separation of Concerns
Memoization
useMemo
and useCallback
hooks.javascriptconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
Pros:
Cons:
Example Usage:
javascriptimport { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
Pros:
Cons:
Example Usage:
javascriptimport { observable, action } from 'mobx';
class Store {
@observable count = 0;
@action increment() {
this.count += 1;
}
}
Pros:
Cons:
Example Usage:
javascriptimport { atom, selector, useRecoilState } from 'recoil';
const countState = atom({
key: 'countState',
default: 0,
});
const doubledCountState = selector({
key: 'doubledCountState',
get: ({get}) => get(countState) * 2,
});
Context API with Reducers
javascriptconst CountContext = React.createContext();
const countReducer = (state, action) => {
switch(action.type) {
case 'increment':
return { count: state.count + 1 };
default:
return state;
}
};
const CountProvider = ({ children }) => {
const [state, dispatch] = useReducer(countReducer, { count: 0 });
return (
<CountContext.Provider value={{ state, dispatch }}>
{children}
</CountContext.Provider>
);
};
State Machines with XState
javascriptimport { useMachine } from '@xstate/react';
import { Machine } from 'xstate';
const toggleMachine = Machine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } },
},
});
const ToggleComponent = () => {
const [current, send] = useMachine(toggleMachine);
return (
<button onClick={() => send('TOGGLE')}>
{current.matches('inactive') ? 'Off' : 'On'}
</button>
);
};
Optimizing with Selectors and Memoization
javascriptconst expensiveComputation = (state) => {
// Complex computation here
return result;
};
const memoizedValue = useMemo(() => expensiveComputation(state), [state]);
Using Middlewares in Redux
javascriptconst loggerMiddleware = store => next => action => {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
};
const store = createStore(
rootReducer,
applyMiddleware(loggerMiddleware)
);
Scenario: Managing asynchronous actions like API calls. Implementation:
javascriptimport { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
javascriptconst fetchData = () => {
return async dispatch => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_DATA_FAILURE', error });
}
};
};
javascriptimport { useDispatch, useSelector } from 'react-redux';
const MyComponent = () => {
const dispatch = useDispatch();
const data = useSelector(state => state.data);
useEffect(() => {
dispatch(fetchData());
}, [dispatch]);
return (
<div>
{data.isLoading ? <p>Loading...</p> : <p>{data.content}</p>}
</div>
);
};
Scenario: Reactive state management with observables. Implementation:
javascriptimport { observable, action } from 'mobx';
class TodoStore {
@observable todos = [];
@action addTodo = (todo) => {
this.todos.push(todo);
};
}
const store = new TodoStore();
javascriptimport { observer } from 'mobx-react';
const TodoList = observer(({ store }) => (
<div>
{store.todos.map(todo => (
<p key={todo.id}>{todo.text}</p>
))}
<button onClick={() => store.addTodo({ id: 1, text: 'New Todo' })}>
Add Todo
</button>
</div>
));
Scenario: Simple state sharing and derived state. Implementation:
javascriptimport { atom, selector } from 'recoil';
const textState = atom({
key: 'textState',
default: '',
});
const charCountState = selector({
key: 'charCountState',
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
javascriptimport { useRecoilState, useRecoilValue } from 'recoil';
const CharacterCounter = () => {
const [text, setText] = useRecoilState(textState);
const charCount = useRecoilValue(charCountState);
return (
<div>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
<p>Character Count: {charCount}</p>
</div>
);
};
Optimizing with Memoization and Selectors
javascriptconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const selector = selectorFamily({
key: 'filteredItems',
get: (filter) => ({get}) => {
const items = get(itemsState);
return items.filter(item => item.includes(filter));
},
});
Code Splitting and Lazy Loading
javascriptconst OtherComponent = React.lazy(() => import('./OtherComponent'));
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
Error Handling and Fallbacks
javascriptconst ErrorBoundary = ({ children }) => {
const [hasError, setHasError] = useState(false);
return (
<ErrorBoundary FallbackComponent={<div>Something went wrong</div>}
onError={() => setHasError(true)}>
{hasError ? <div>Oops! Something went wrong.</div> : children}
</ErrorBoundary>
);
};