import React, { createContext, useContext, useReducer } from "react";
import { Text, View, Pressable, useColorScheme } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
// Types
interface State {
count: number;
todos: Todo[];
}
interface Todo {
id: string;
```typescript:src/store/context/store.ts
import React, { createContext, useContext, useReducer } from 'react';
// Types
interface State {
counter: {
value: number;
};
todos: {
items: string[];
};
}
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'ADD_TODO'; payload: string }
| { type: 'REMOVE_TODO'; payload: number };
// Initial State
const initialState: State = {
counter: {
value: 0,
},
todos: {
items: [],
},
};
// Reducer
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'INCREMENT':
return {
...state,
counter: { value: state.counter.value + 1 },
};
case 'DECREMENT':
return {
...state,
counter: { value: state.counter.value - 1 },
};
case 'ADD_TODO':
return {
...state,
todos: { items: [...state.todos.items, action.payload] },
};
case 'REMOVE_TODO':
return {
...state,
todos: {
items: state.todos.items.filter((_, index) => index !== action.payload),
},
};
default:
return state;
}
}
import React, { createContext, useContext, useReducer } from 'react';
// Context
const StateContext = createContext<State | undefined>(undefined);
const DispatchContext = createContext<React.Dispatch<Action> | undefined>(
undefined
);
// Provider Component
export function StoreProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// Custom Hooks
export function useState() {
const context = useContext(StateContext);
if (context === undefined) {
throw new Error('useState must be used within a StoreProvider');
}
return context;
}
export function useDispatch() {
const context = useContext(DispatchContext);
if (context === undefined) {
throw new Error('useDispatch must be used within a StoreProvider');
}
return context;
}
import { useCallback } from 'react';
import { useDispatch } from './Provider';
export function useCounterActions() {
const dispatch = useDispatch();
return {
increment: useCallback(() => {
dispatch({ type: 'INCREMENT' });
}, [dispatch]),
decrement: useCallback(() => {
dispatch({ type: 'DECREMENT' });
}, [dispatch]),
};
}
export function useTodoActions() {
const dispatch = useDispatch();
return {
addTodo: useCallback((todo: string) => {
dispatch({ type: 'ADD_TODO', payload: todo });
}, [dispatch]),
removeTodo: useCallback((index: number) => {
dispatch({ type: 'REMOVE_TODO', payload: index });
}, [dispatch]),
};
}
import React, { useState, useCallback } from 'react';
import { View, Text, TextInput, Button, FlatList } from 'react-native';
import { useState as useStoreState } from '../store/context/Provider';
import { useCounterActions, useTodoActions } from '../store/context/actions';
export function ContextScreen() {
const [newTodo, setNewTodo] = useState('');
const state = useStoreState();
const { increment, decrement } = useCounterActions();
const { addTodo, removeTodo } = useTodoActions();
const handleAddTodo = useCallback(() => {
if (newTodo.trim()) {
addTodo(newTodo.trim());
setNewTodo('');
}
}, [newTodo, addTodo]);
return (
<View className="p-4">
{/* Counter Section */}
<View className="mb-8">
<Text className="text-xl font-bold mb-4">
Counter: {state.counter.value}
</Text>
<View className="flex-row space-x-4">
<Button title="Increment" onPress={increment} />
<Button title="Decrement" onPress={decrement} />
</View>
</View>
{/* Todo Section */}
<View>
<Text className="text-xl font-bold mb-4">
Todos ({state.todos.items.length})
</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={handleAddTodo} />
</View>
<TodoList
todos={state.todos.items}
onRemove={removeTodo}
/>
</View>
</View>
);
}
const TodoList = React.memo(({
todos,
onRemove
}: {
todos: string[];
onRemove: (index: number) => void;
}) => (
<FlatList
data={todos}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item, index }) => (
<TodoItem todo={item} onRemove={() => onRemove(index)} />
)}
/>
));
const TodoItem = React.memo(({
todo,
onRemove
}: {
todo: string;
onRemove: () => void;
}) => (
<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>
));
import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import { StoreProvider, useState, useDispatch } from '../Provider';
import { useCounterActions, useTodoActions } from '../actions';
const wrapper = ({ children }: { children: React.ReactNode }) => (
<StoreProvider>{children}</StoreProvider>
);
describe('Context Store', () => {
describe('Counter', () => {
it('should handle increment and decrement', () => {
const { result } = renderHook(
() => ({
state: useState(),
actions: useCounterActions(),
}),
{ wrapper }
);
act(() => {
result.current.actions.increment();
});
expect(result.current.state.counter.value).toBe(1);
act(() => {
result.current.actions.decrement();
});
expect(result.current.state.counter.value).toBe(0);
});
});
describe('Todos', () => {
it('should handle adding and removing todos', () => {
const { result } = renderHook(
() => ({
state: useState(),
actions: useTodoActions(),
}),
{ wrapper }
);
act(() => {
result.current.actions.addTodo('Test todo');
});
expect(result.current.state.todos.items).toContain('Test todo');
act(() => {
result.current.actions.removeTodo(0);
});
expect(result.current.state.todos.items).toHaveLength(0);
});
});
});
- Split contexts for better performance:
// Separate contexts for different parts of the state
const CounterContext = createContext<CounterState | undefined>(undefined);
const TodosContext = createContext<TodosState | undefined>(undefined);
export function SplitProvider({ children }: { children: React.ReactNode }) {
const [counterState, counterDispatch] = useReducer(counterReducer, initialCounterState);
const [todosState, todosDispatch] = useReducer(todosReducer, initialTodosState);
return (
<CounterContext.Provider value={{ state: counterState, dispatch: counterDispatch }}>
<TodosContext.Provider value={{ state: todosState, dispatch: todosDispatch }}>
{children}
</TodosContext.Provider>
</CounterContext.Provider>
);
}
- Use memo for expensive computations:
import { useMemo } from 'react';
export function useTodoStats(todos: string[]) {
return useMemo(() => ({
total: todos.length,
isEmpty: todos.length === 0,
}), [todos]);
}
-
Context Organization
- Split contexts by domain
- Keep providers close to where they're needed
- Use composition for multiple contexts
- Implement proper TypeScript types
-
State Management
- Use reducers for complex state
- Split state logically
- Keep state normalized
- Implement proper error boundaries
-
Performance
- Split contexts to minimize re-renders
- Use memo for expensive computations
- Implement proper component memoization
- Keep contexts small and focused
-
Testing
- Test reducers independently
- Test context integration
- Test hooks
- Test error cases
- Creating too many contexts
- Not splitting contexts properly
- Overusing context for local state
- Not implementing proper error handling