Compare different approaches to state management using counters and API data fetching. See how Redux, Context API, and Zustand handle various use cases.
React Context API for component-level state sharing.
// counter-context.tsx
const CounterContext = createContext({
count: 0,
increment: () => {},
decrement: () => {},
reset: () => {}
});
export function CounterProvider({ children }) {
const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const reset = () => setCount(0);
return (
<CounterContext.Provider value={{
count, increment, decrement, reset
}}>
{children}
</CounterContext.Provider>
);
}Redux with Redux Toolkit for predictable state updates.
// counter-slice.ts
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: (state) => {
state.count += 1;
},
decrement: (state) => {
state.count -= 1;
},
reset: (state) => {
state.count = 0;
}
}
});
export const { increment, decrement, reset } = counterSlice.actions;Zustand for simple yet powerful state management.
// counter-store.ts
export const useCounterStore = create((set) => ({
count: 0,
increment: () =>
set((state) => ({ count: state.count + 1 })),
decrement: () =>
set((state) => ({ count: state.count - 1 })),
reset: () =>
set({ count: 0 })
}));
const PostsContext = createContext<PostsContextType>({
posts: [],
loading: false,
error: null,
fetchPosts: async () => {},
});
export function PostsProvider({ children }) {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchPosts = async () => {
try {
setLoading(true);
const response = await fetch('/api/posts');
const data = await response.json();
setPosts(data);
} catch (err) {
setError('Failed to fetch posts');
} finally {
setLoading(false);
}
};
return (
<PostsContext.Provider value={{
posts, loading, error, fetchPosts
}}>
{children}
</PostsContext.Provider>
);
}
export const fetchPosts = createAsyncThunk(
'posts/fetchPosts',
async () => {
const response = await fetch('/api/posts');
return response.json();
}
);
const postsSlice = createSlice({
name: 'posts',
initialState: {
posts: [],
loading: false,
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.posts = action.payload;
state.loading = false;
})
.addCase(fetchPosts.rejected, (state) => {
state.loading = false;
state.error = 'Failed to fetch posts';
});
},
});
interface PostsStore {
posts: Post[];
loading: boolean;
error: string | null;
fetchPosts: () => Promise<void>;
}
export const usePostsStore = create<PostsStore>((set) => ({
posts: [],
loading: false,
error: null,
fetchPosts: async () => {
try {
set({ loading: true, error: null });
const response = await fetch('/api/posts');
const data = await response.json();
set({ posts: data, loading: false });
} catch (err) {
set({ error: 'Failed to fetch posts', loading: false });
}
},
}));