RE05 Exercises
Practical Exercises
- Using React Context
Objective: Create and use a React context to manage and share state across components.
Task:
Create a UserContext with createContext. Create a UserProvider component that uses the context provider to pass a user object and a function to update it. Consume the context in a child component using the useContext hook.
import React, { createContext, useContext, useState } from 'react';
interface User {
name: string;
age: number;
}
const UserContext = createContext<{ user: User | null; setUser: React.Dispatch<React.SetStateAction<User | null>> }>({ user: null, setUser: () => {} });
const UserProvider: React.FC = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
const ChildComponent: React.FC = () => {
const { user, setUser } = useContext(UserContext);
// Example usage of context
return <div>{user ? `Hello, ${user.name}` : 'No user'}</div>;
};
const ParentComponent: React.FC = () => (
<UserProvider>
<ChildComponent />
</UserProvider>
);
export default ParentComponent;- Managing State with useReducer
Objective: Use the useReducer hook to manage complex state.
Task:
Create a reducer function for managing the state of a counter (increment and decrement actions). Use useReducer in a component to manage the counter state and dispatch actions.
import React, { useReducer } from 'react';
type CounterState = { count: number };
type CounterAction = { type: 'increment' | 'decrement' };
const counterReducer = (state: CounterState, action: CounterAction): CounterState => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
};
const CounterComponent: React.FC = () => {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
};
export default CounterComponent;- Combining Context and Reducers
Objective: Integrate context with reducers to manage complex state across multiple components.
Task:
Create a context to share a state managed by a reducer. Implement a provider component that uses useReducer. Consume the shared state and dispatch actions in child components.
import React, { createContext, useContext, useReducer } from 'react';
type State = { count: number };
type Action = { type: 'increment' | 'decrement' };
type Dispatch = (action: Action) => void;
const CountContext = createContext<{ state: State; dispatch: Dispatch } | undefined>(undefined);
const countReducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error('Unhandled action');
}
};
const CountProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(countReducer, { count: 0 });
return <CountContext.Provider value={{ state, dispatch }}>{children}</CountContext.Provider>;
};
const useCount = () => {
const context = useContext(CountContext);
if (!context) {
throw new Error('useCount must be used within a CountProvider');
}
return context;
};
const CounterButton: React.FC = () => {
const { dispatch } = useCount();
return <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>;
};
const CounterDisplay: React.FC = () => {
const { state } = useCount();
return <div>Count: {state.count}</div>;
};
const App: React.FC = () => (
<CountProvider>
<CounterDisplay />
<CounterButton />
</CountProvider>
);
export default App;