React Lecture: Supplying Data to the Component Tree and Refactoring a To Do App with Context

React is one of the most popular libraries for building modern web applications. This is due to its component-based architecture, reusability, and the ease of managing complex UIs. In this tutorial, we will explore how to supply data to the component tree using React’s Context API and how to refactor it to create a simple Todo application.

1. Basic Concepts of React

React is a collection of components that make up the user interface. Components have state and props, which determine how the UI is rendered.

1.1 State and Props

State represents the internal data of a component, which can change based on user input or network requests. On the other hand, props are the data passed from a parent component to a child component.

1.2 Component Tree

A React application is arranged in a tree structure made up of multiple components. This tree structure manages the flow of data from parent to child components.

2. Introduction to Context API

The Context API is used in React to pass data to components deeply nested within the component tree. It allows data to be supplied without having to pass props through multiple levels, helping to reduce the complexity of prop drilling.

2.1 Creating Context

To create Context, you first need to use React’s createContext function.

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

const MyContext = createContext(); // Create Context

2.2 Provider and Consumer

Use the Provider component of the Context to pass data down to child components. The Provider supplies data that can be accessed by all descendant components.

<MyContext.Provider value={/* context value */}>
    {/* children */}
</MyContext.Provider>

2.3 Using Context

To use Context in a child component, you retrieve the data using the useContext hook.

const value = useContext(MyContext); // Use Context

3. Setting Up the Todo App Structure

Now, let’s create a simple Todo app using Context API. First, we will set up the basic component structure.

3.1 Basic Component Structure

The Todo app consists of the following components.

  • App: The component that wraps the entire app
  • TodoProvider: The Provider component that manages the state of the Todo list
  • TodoList: The component that renders the list of Todo items
  • TodoItem: The component that renders individual Todo items
  • AddTodo: The component for adding new Todo items

3.2 Writing the App Code

First, we will write the TodoProvider component to manage the state of the Todo list.

const TodoProvider = ({ children }) => {
    const [todos, setTodos] = useState([]);

    const addTodo = (todo) => {
        setTodos([...todos, todo]);
    };

    const removeTodo = (id) => {
        setTodos(todos.filter(todo => todo.id !== id));
    };

    return (
        <MyContext.Provider value={{ todos, addTodo, removeTodo }}>
            {children}
        </MyContext.Provider>
    );
};

Then we will write the TodoList and AddTodo components.

const TodoList = () => {
    const { todos, removeTodo } = useContext(MyContext);

    return (
        <ul>
            {todos.map(todo => (
                <TodoItem key={todo.id} todo={todo} onRemove={removeTodo} />
            ))}</ul>
    );
};

const AddTodo = () => {
    const { addTodo } = useContext(MyContext);
    const [inputValue, setInputValue] = useState('');

    const handleSubmit = (e) => {
        e.preventDefault();
        if (!inputValue) return;
        
        addTodo({ id: Date.now(), text: inputValue });
        setInputValue('');
    };

    return (
        <form onSubmit={handleSubmit}>
            <input 
                type="text" 
                value={inputValue} 
                onChange={(e) => setInputValue(e.target.value)} 
                placeholder="Enter new Todo" 
            />
            <button type="submit">Add</button>
        </form>
    );
};

3.3 Final App Structure

Finally, we will use TodoProvider in the App component to wrap the other components.

const App = () => {
    return (
        <TodoProvider>
            <h1>Todo List</h1>
            <AddTodo />
            <TodoList />
        </TodoProvider>
    );
};

4. State Management and Refactoring

State management is a crucial part of React applications. It helps clarify the flow and relationships of data later on. By using the Context API, child components can access the state directly for more efficient management.

4.1 State Management Patterns

There are various patterns for state management. When using it with the Context API, components must be designed to subscribe to it. This minimizes unnecessary re-renders.

4.2 Example of Refactoring

Refactoring allows for safely implementing additional features. For instance, let’s add logic to handle the completion status of Todo items.

const toggleTodo = (id) => {
    setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo));
};

4.3 Visualizing Completed Todo Items

To visually represent completed Todo items, we will modify the TodoItem component.

const TodoItem = ({ todo, onRemove }) => {
    return (
        <li style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
            <button onClick={() => onRemove(todo.id)}>Delete</button>
        </li>
    );
};

5. Conclusion

In this tutorial, we learned how to use React’s Context API to supply data to the component tree and how to refactor a Todo application. Context makes it easy to share data between components and helps to keep the app’s structure cleaner.

Understanding state management and data flow using React will greatly aid in building complex applications in the future. Now, I encourage you to use the Context API to create various applications. Thank you!