Skip to content

Instantly share code, notes, and snippets.

@jeromeabel
Last active May 25, 2023 09:37
Show Gist options
  • Save jeromeabel/4bdd305a39e5b14a26f971ddc36b3b2a to your computer and use it in GitHub Desktop.
Save jeromeabel/4bdd305a39e5b14a26f971ddc36b3b2a to your computer and use it in GitHub Desktop.

Using React Context with Typescript - guide

In React, dealing with props might be too verbose and not efficient. An alternative is to use the React Context API. It is useful for sharing data between the app.

A practical example:

  • Add an employee
  • View a list of employees

rect403540

Type

Define the properties and types of an employee

// file: types/index.ts
export type EmployeeType = {
	name: string;
	age: number;
	email: string;
	dateOfBirth: Date;
};
export type EmployeesType = EmployeeType[];

Context

Define the context and the provider.

  1. We are defining a kind of contract : every components in the tree can access the context. Here, there are an array of employees and a function to add an employee to this array.
  2. The provider implements the context using the useState hook to store employees.
  3. We add a custom Hook to force a good usage of the provider.
// file: context/EmployeesContext.ts
import { PropsWithChildren, createContext, useContext, useState } from 'react';
import { EmployeeType, EmployeesType } from '../types';

// Contract or interface for the context ~API
type EmployeesContextProps = {
	employees: EmployeesType;
	addEmployee: (newEmployee: EmployeeType) => void;
};

// Create the Context : 
// passing data down the component tree, making it accessible
const EmployeesContext = createContext<EmployeesContextProps | null>(null);

// Create the Provider
export const EmployeesProvider = ({ children }: PropsWithChildren) => {
	// useState: manage and share stateful data in App
	const [employees, setEmployees] = useState<EmployeesType>([]);
	
  // addEmployee implementation : add to the existing array
	const addEmployee = (newEmployee: EmployeeType) => {
		setEmployees([...employees, newEmployee]);
	};
	
	// Render
	return (
		<EmployeesContext.Provider value={{ employees, addEmployee }}>
			{children}
		</EmployeesContext.Provider>
	);
};

// Custom hook ensuring that it is used in the appropriate context provider
export const useEmployeesContext = () => {
	const context = useContext(EmployeesContext);
	if (!context) {
		throw new Error('useEmployeesContext must be used inside the EmployeesProvider');
	}
	return context;
};

App

Insert components inside the EmployeesProvider

// file: App.tsx
import EmployeesList from './components/EmployeesList';
import EmployeeForm from './components/EmployeeForm';
import { EmployeesProvider } from './context/EmployeesContext';

const App = () => {
	return (
		<EmployeesProvider>
			<EmployeeForm />
			<EmployeesList />
		</EmployeesProvider>
	);
};

export default App;

List

Display the employees in a simple list. We use our custom hook to access the "employees" array.

// file: components/List.tsx
import { useEmployeesContext } from '../context/EmployeesContext';

const EmployeesList = () => {

	const { employees } = useEmployeesContext();
	
	return (
		<ul>
			{employees.map((employee, index) => {
				return (
				<li key={index}>
					{employee.name} - {employee.age} years - {employee.email} -
					{employee.dateOfBirth.toLocaleString()}
				</li>
				);
			})}
		</ul>
	);
};

export default EmployeesList;

Form

The form use the custom hook to access the "addEmployee" function. We combine all input values in a FormData structure in respect with the "EmployeeType" provided by the context.

// file: components/Form.tsx
import { useEmployeesContext } from '../context/EmployeesContext';

const EmployeeForm = () => {

	const { addEmployee } = useEmployeesContext();
	
	const handleSubmit = (e: React.FormEvent) => {
		e.preventDefault();
		const form = e.target as HTMLFormElement;
		const formData = new FormData(form);
		const newEmployee = {
			name: formData.get('name') as string,
			age: parseInt(formData.get('age') as string),
			email: formData.get('email') as string,
			dateOfBirth: new Date(formData.get('dateOfBirth') as string),
		};
		addEmployee(newEmployee); // the context is typed, no need to import type
		form.reset();
	};
	
	return (
	<form onSubmit={handleSubmit}>
		<label>Name<input type="text" name="name" /></label>
		<label>Age<input type="number" name="age" /></label>
		<label>Email<input type="email" name="email" /></label>
		<label>Date of Birth<input type="date" name="dateOfBirth" /></label>
		<button type="submit">Add Employee</button>
	</form>
	);
};

export default EmployeeForm;

The result

form

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment