Reusable React Code Modules, Part 7 - App State
Managing State with Ease: Building Reusable State Management Modules
Managing application state is a crucial aspect of building front-end web applications with React. This post covers how to structure reusable app state modules in React, handle state efficiently, and manage data flow. We'll explore common state management libraries, compare their features, and provide practical examples and code snippets.
Common Libraries and Tools
1. Redux
Redux is a predictable state container for JavaScript apps, widely used in React applications
Key Features
Predictable state management
Centralized state
Middleware support
DevTools integration
2. MobX
MobX is a simple and scalable state management solution, using observables to track state changes.
Key Features
Observable state
Easy-to-understand syntax
Built-in support for computed values
Side-effect management
3. Context API
React's Context API provides a way to pass data through the component tree without having to pass props down manually at every level
Key Features
Built-in React solution
Simplifies prop drilling
Suitable for simple state management
4. Recoil
Recoil is a state management library for React from Facebook, providing fine-grained state management and atom-based architecture
Key Features
Fine-grained state management
Atom-based architecture
Built-in support for derived state
React-like API
Comparison
Redux: Best for large-scale applications requiring predictable and centralized state management.
MobX: Ideal for applications needing a simpler syntax and observable state.
Context API: Suitable for small to medium applications where complex state management is unnecessary.
Recoil: Suitable for applications needing fine-grained state management with a modern, React-like API.
Examples
Example 1: Redux
Setup:
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
const App = () => (
<Provider store={store}>
<MyComponent />
</Provider>
);
export default App;
Reducer:
const initialState = {
count: 0,
};
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
export default counterReducer;
Action:
export const increment = () => ({
type: 'INCREMENT',
});
export const decrement = () => ({
type: 'DECREMENT',
});
Component:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions';
const MyComponent = () => {
const count = useSelector(state => state.counter.count);
const dispatch = useDispatch();
return (
<div>
<h1>{count}</h1>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
};
export default MyComponent;
Example 2: MobX
Setup:
import { observable, action } from 'mobx';
import { Observer, useLocalStore } from 'mobx-react-lite';
class CounterStore {
@observable count = 0;
@action increment = () => {
this.count++;
};
@action decrement = () => {
this.count--;
};
}
const counterStore = new CounterStore();
const App = () => (
<Observer>
{() => <MyComponent store={counterStore} />}
</Observer>
);
export default App;
Component:
import React from 'react';
import { Observer } from 'mobx-react-lite';
const MyComponent = ({ store }) => (
<Observer>
{() => (
<div>
<h1>{store.count}</h1>
<button onClick={store.increment}>Increment</button>
<button onClick={store.decrement}>Decrement</button>
</div>
)}
</Observer>
);
export default MyComponent;
Example 3: Context API
Setup:
import React, { createContext, useState, useContext } from 'react';
const CountContext = createContext();
const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<CountContext.Provider value={{ count, increment, decrement }}>
{children}
</CountContext.Provider>
);
};
const App = () => (
<CountProvider>
<MyComponent />
</CountProvider>
);
export default App;
Component:
import React, { useContext } from 'react';
import { CountContext } from './CountProvider';
const MyComponent = () => {
const { count, increment, decrement } = useContext(CountContext);
return (
<div>
<h1>{count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
export default MyComponent;
Example 4: Recoil
Setup:
import React from 'react';
import { RecoilRoot, atom, selector, useRecoilState, useRecoilValue } from 'recoil';
const countState = atom({
key: 'countState',
default: 0,
});
const doubleCountState = selector({
key: 'doubleCountState',
get: ({ get }) => get(countState) * 2,
});
const App = () => (
<RecoilRoot>
<MyComponent />
</RecoilRoot>
);
export default App;
Component:
import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { countState, doubleCountState } from './store';
const MyComponent = () => {
const [count, setCount] = useRecoilState(countState);
const doubleCount = useRecoilValue(doubleCountState);
return (
<div>
<h1>{count}</h1>
<h2>{doubleCount}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
};
export default MyComponent;