codewithjohn.dev
Published on

Using React Context with TypeScript

In this article, we will explore how to use the React Context API with TypeScript by building a to-do app from scratch.

What is the React Context API?

React Context is one of the features of React that was introduced in React v.16.3. It allows you to share data on a global level between components without passing props explicitly, or it avoids prop drilling. It also helps us implement state management in our React apps without needing extra libraries such as Redux. When React Context API combined With TypeScript, it provides even stronger type checking and a better development experience. To get the most out of this tutorial, it is helpful to have a basic understanding of React and TypeScript.

To get started, let's create a new file called TodoContext.tsx and define the shape of our todo item, which has an id (number), text (string), and completed (boolean) property.

Next, let's define the shape of our context value. It has an array of todos, as well as two functions: addTodo, which adds a new todo to the list, and toggleTodo, which toggles the completed status of a todo.

// Define the shape of our todo item
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

// Define the shape of our context value
interface TodoContextValue {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: number) => void;
}

The next step is to import createContext from React and create TodoContext with an initial value that matches the TodoContextValue interface.

import React, { createContext, useState } from 'react'

// Create the context with an initial value
const initialValue: TodoContextValue = {
  todos: [],
  addTodo: () => {},
  toggleTodo: () => {},
}
export const TodoContext = createContext<TodoContextValue>(initialValue)

Once we have created the context, we will create the TodoProvider component and define the state for our todos using the useState hook. We also define the addTodo to add a new todo to the state and toggleTodo functions to update the state.

Next, we create the context value by combining the state and functions into an object.

Finally, we will render the TodoProvider component, providing the context value to the TodoContext.Provider component. We also render the children prop, which represents the components that will consume the context. The final result is shown below.

import React, { createContext, useState } from 'react';

// Define the shape of our todo item
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

// Define the shape of our context value
interface TodoContextValue {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: number) => void;
}

// Create the context with an initial value
export const TodoContext = createContext<TodoContextValue>({
  todos: [],
  addTodo: () => {},
  toggleTodo: () => {},
});

// Create a provider component
export const TodoProvider: React.FC = ({ children }) => {
  // Define the state for our todos
  const [todos, setTodos] = useState<Todo[]>([]);

  // Add a todo to the list
  const addTodo = (text: string) => {
    const newTodo: Todo = {
      id: Date.now(),
      text,
      completed: false,
    };
    setTodos([...todos, newTodo]);
  };

  // Toggle the completed status of a todo
  const toggleTodo = (id: number) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  // Create the context value
  const contextValue: TodoContextValue = {
    todos,
    addTodo,
    toggleTodo,
  };

  // Render the provider with the context value
  return (
    <TodoContext.Provider value={contextValue}>{children}</TodoContext.Provider>
  );
};

Until now we have our context set up, The next step is to consume the data that our Context provides so let's create a new file called TodoList.tsx:

import React, { useContext } from 'react'
import { TodoContext } from './TodoContext'

const TodoList: React.FC = () => {
  // Consume the todo context
  const { todos, toggleTodo } = useContext(TodoContext)

  return (
    <ul>
      {todos.map((todo) => (
        <li
          key={todo.id}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          onClick={() => toggleTodo(todo.id)}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  )
}

export default TodoList

In the above code we have created TodoList component then we import TodoContext from our TodoContext.tsx then we consume the todo context using the useContext hook from React.

To complete our todo app, let's create a component that allows users to add new todos. Create a new file called AddTodoForm.tsx:

import React, { useState, useContext } from 'react'
import { TodoContext } from './TodoContext'

const AddTodoForm: React.FC = () => {
  // Consume the todo context
  const { addTodo } = useContext(TodoContext)
  const [text, setText] = useState('')

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (text.trim() !== '') {
      addTodo(text)
      setText('')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a new todo"
      />
      <button type="submit">Add</button>
    </form>
  )
}

export default AddTodoForm

In the above code we have created AddTodoForm component and we define a state for the input text using the useState hook as well as handleSubmit function that adds a new todo to the list when the form is submitted.

Finally, to use our context in our app, we have to import TodoProvider inside the App component and wrap our components with the 'TodoProvider' to make the context available to them.

import React from 'react'
import { TodoProvider } from './TodoContext'
import TodoList from './TodoList'
import AddTodoForm from './AddTodoForm'

const App: React.FC = () => {
  return (
    <TodoProvider>
      <h1>Todo App</h1>
      <TodoList />
      <AddTodoForm />
    </TodoProvider>
  )
}

export default App

Conclusion

In this article, we explored how to use React Context API with TypeScript. As you have seen, using React Context with TypeScript slightly different to plain JavaScript. With all the pieces in place, you can now run your todo app with React Context and TypeScript.

I hope this article has provided you with a clear understanding of how to create and use React Context with TypeScript, using a todo app as an example. Happy coding!

Like what you are reading?