- Install dependencies:
npm install @reduxjs/toolkit react-redux
// src/store/redux/store.ts
import { configureStore } from "@reduxjs/toolkit";
import { counterSlice } from "./slices/counterSlice";
import { todoSlice } from "./slices/todoSlice";
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
todos: todoSlice.reducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// src/store/redux/slices/counterSlice.ts
import { createSlice } from "@reduxjs/toolkit";
interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
// src/store/redux/slices/todoSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface TodoState {
todos: string[];
}
const initialState: TodoState = {
todos: [],
};
export const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
state.todos.push(action.payload);
},
removeTodo: (state, action: PayloadAction<number>) => {
state.todos.splice(action.payload, 1);
},
},
});
export const { addTodo, removeTodo } = todoSlice.actions;
// src/store/redux/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// src/screens/ReduxScreen.tsx
import React, { useState } from "react";
import { View, Text, TextInput, Button, FlatList } from "react-native";
import { useAppDispatch, useAppSelector } from "../store/redux/hooks";
import { increment, decrement } from "../store/redux/slices/counterSlice";
import { addTodo, removeTodo } from "../store/redux/slices/todoSlice";
export function ReduxScreen() {
const [newTodo, setNewTodo] = useState("");
const count = useAppSelector((state) => state.counter.value);
const todos = useAppSelector((state) => state.todos.todos);
const dispatch = useAppDispatch();
return (
<View className='p-4'>
{/* Counter Section */}
<View className='mb-8'>
<Text className='text-xl font-bold mb-4'>Counter: {count}</Text>
<View className='flex-row space-x-4'>
<Button title='Increment' onPress={() => dispatch(increment())} />
<Button title='Decrement' onPress={() => dispatch(decrement())} />
</View>
</View>
{/* Todo Section */}
<View>
<Text className='text-xl font-bold mb-4'>Todos</Text>
<View className='flex-row space-x-2 mb-4'>
<TextInput
className='flex-1 border p-2 rounded'
value={newTodo}
onChangeText={setNewTodo}
placeholder='New todo'
/>
<Button
title='Add'
onPress={() => {
if (newTodo.trim()) {
dispatch(addTodo(newTodo.trim()));
setNewTodo("");
}
}}
/>
</View>
<FlatList
data={todos}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item, index }) => (
<View className='flex-row justify-between items-center p-2 bg-gray-100 mb-2 rounded'>
<Text>{item}</Text>
<Button
title='Remove'
onPress={() => dispatch(removeTodo(index))}
color='red'
/>
</View>
)}
/>
</View>
</View>
);
}
// src/store/redux/__tests__/counterSlice.test.ts
import { store } from "../store";
import { increment, decrement } from "../slices/counterSlice";
describe("Counter Slice", () => {
it("should handle increment", () => {
store.dispatch(increment());
expect(store.getState().counter.value).toBe(1);
});
it("should handle decrement", () => {
store.dispatch(decrement());
expect(store.getState().counter.value).toBe(0);
});
});
- Use memoization for selectors:
import { createSelector } from "@reduxjs/toolkit";
export const selectTodoCount = createSelector(
(state: RootState) => state.todos.todos,
(todos) => todos.length
);
- Implement
React.memo
for list items:
const TodoItem = React.memo(({ todo, onRemove }: TodoItemProps) => {
return (
<View className='flex-row justify-between items-center p-2 bg-gray-100 mb-2 rounded'>
<Text>{todo}</Text>
<Button title='Remove' onPress={onRemove} color='red' />
</View>
);
});
-
Slice Organization
- Keep slices focused and small
- Use feature-based organization
- Implement proper TypeScript types
-
State Shape
- Normalize complex data
- Avoid deeply nested state
- Keep state minimal
-
Performance
- Use memoized selectors
- Implement proper component memoization
- Avoid unnecessary re-renders
-
Testing
- Test reducers independently
- Test async actions
- Test selectors
- Mutating state directly (outside of createSlice)
- Overusing global state
- Not implementing proper TypeScript types
- Missing proper error handling