01.4.24

Creating an authentication context with useContext in React

Creating an authentication context is a practical way to manage user authentication state across your entire React application. By using the Context API together with hooks like useContext and useState, you can easily share authentication state and logic across components.

There are several ways to pass information from parent components to child components in React. One way to do that is to use props. This works great with immediate children, but becomes more challenging when dealing with a deep component tree. A second way is to use the Context API to pass data deeply with Context. Context allows the parent to make information available to any component in the UI tree without using prop drilling.

Creating an authentication context

First we will use createContext to create our AuthContext. Create a file AuthContext.js somewhere in your project. I suggest you create a context folder at the same level of your App.js.

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

const AuthContext = createContext();

export function useAuth() {
  return useContext(AuthContext);
}

Here we are initializing a new context and then we define and export useAuth(). This function defines and exports the custom hook so that we can easily access the context.

Next we need to declare an AuthProvider component that will wrap our application. We will also declare a login() and logout() methods. Our methods are simple for this demonstration. They will update the user appropriately.


export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Check if user is logged in when the app loads
    fetch('/api/check-auth')
      .then(response => response.json())
      .then(data => {
        setUser(data.user); // Adjust based on your API response
        setLoading(false);
      }).catch((e) => {
        // Navigate to the login page...
      });
  }, []);

  const login = (loggedInUser) => {
    setUser(loggedInUser);
  };

  const logout = () => {
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {!loading && children}
    </AuthContext.Provider>
  );
}

Our AuthProvider uses the useEffect hook to check authentication from our server. This is particular useful in single page applications when users refresh the browser on any other route that is not the root route. You may have a different way of maintaining your authentication session, for now we are going to assume that the server is responsible for doing so.

We are also waiting to load any children with this line{!loading && children}, until we have the server’s response. For now we are not rendering anything. This would be a good opportunity render something that will improve the user experience rather than a blank screen or “loading” text.

Integrating AuthProvider with our application

The integration is quite simple. We are going to wrap our App component with our new context.

import React from 'react';
import ReactDOM from 'react-dom';
import { AuthProvider } from './AuthContext';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

Now we can use our authContext with any component. Here is an example of how you can create a login component and use it to update the authentication context.

import React, { useState } from 'react';
import { useAuth } from './AuthContext';

function LoginComponent() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const { login } = useAuth();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const result = await login(email, password);
      // Redirect the user or update the UI
    } catch (error) {
      // Handle errors (e.g., show error message)
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <button type="submit">Login</button>
    </form>
  );
}

You can import and use the context into any component: import { useAuth } from './AuthContext'. To get your current user all you have to do is call const { user } = useAuth() and you can now perform any business logic based on whether the user is logged in.

As a challenge create a ProtectedRoute component and use useAuth() to get the current user. If the user does not exist, navigate to the login page. Here is an example of how you would use it. See if you can implement it with react-router-dom.

<Routes>
  <ProtectedRoute>
    <Route path="/" element={
          <Dashboard />
      } />
  </ProtectedRoute>
  <Route path="/register" element={<Register />} /> 
  <Route path="/login" element={<Login />} />
</Routes>

Good luck!

| Posted in blog | No Comments »
01.2.24

Creating a todo application with React

The goal of this post is to teach you how to set up an app, not teach you react. You can learn React here.

If you haven’t already set up your React environment. Ensure you have create-react-app installed and then create a new project:

npx create-react-app react-todo-app
cd react-todo-app
npm start

The structure of your app

Here is an example of how you can organize your app folder structure. Whenever I start working with a new technology to build web applications I always seek to know how to best organize my code from the beginning. It saves a ton of time and headaches later.

todo-app/
|-- src/
    |-- components/
        |-- AddTodo/
            |-- AddTodo.js
            |-- AddTodo.styles.js
        |-- TodoList/
            |-- TodoList.js
            |-- TodoList.styles.js
        |-- TodoItem/
            |-- TodoItem.js
            |-- TodoItem.styles.js
    |-- context/
        |-- TodoContext.js
    |-- hooks/
        |-- useTodos.js
    |-- utils/
        |-- utilityFunctions.js
    |-- assets/
        |-- styles/
            |-- GlobalStyle.js
        |-- images/
    |-- App.js
    |-- index.js

  • App.js – The main component that holds everything together.
  • TodoList.js – Displays the list of to-do items.
  • TodoItem.js – Represents a single to-do item.
  • AddTodo.js – A form to add new to-do items.

We are going to create the last 3.

Creating the TodoList and TodoItem Components

Create a Todolist.js file, use the directory structure example to guide you on where to save it.

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

export default TodoItem;

Here we are declaring our TodoItem component and it takes two props. The todo object itself and a handler to toggle the todo’s status from pending to completed.

In the same manner create TodoList.js and paste the following code.

import TodoItem from './TodoItem';

function TodoList({ todos, toggleTodo }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} toggleTodo={toggleTodo} />
      ))}
    </ul>
  );
}

export default TodoList;

Like TodoItem, it takes two properties, a list of todos and a toggleTodo method that we are then passing down to each item. There are more advance and better ways of handling the state of the todo’s but our goal here is to get your started.

Now that our todo components are ready, lets move on to managing the state of the items.

Managing State in App.js

In App.js we are going to implement toggleTodos as well as an add method to add a todo that. We will take care of fully implementing it later.

import React, { useState } from 'react';
import TodoList from './TodoList';
import AddTodo from './AddTodo'; // We'll create this next

function App() {
  const [todos, setTodos] = useState([]);

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

  const addTodo = text => {
    setTodos([...todos, { id: Date.now(), text: text, completed: false }]);
  };

  return (
    <div>
      <AddTodo addTodo={addTodo} />
      <TodoList todos={todos} toggleTodo={toggleTodo} />
    </div>
  );
}

export default App;

There are several things going on here. First we use the useState hook manage our todos list state. useState adds a state variable that can be used to update and re-render our application anytime the items are updated such as marking one of the todos completed.

We also implemented toggleTodos, it takes the ID of the todo and toggles the completed property by looping through our list until it finds the todo with the ID.

We are using our TodoList component and passing it a list of todos and toggleTodos.

We also implemented addTodo which we will handle next.

Adding a new Todo item

For adding an item to our list of todos, we are going to create a new component. It you look at App.js we actually already used it. This component will render a form collecting the name of the todo item and call the addTodo method passed to it from App.js. In this way, our App component will take care of managing this simple state. Lets implement it.

import React, { useState } from 'react';

function AddTodo({ addTodo }) {
  const [value, setValue] = useState('');

  const handleSubmit = e => {
    e.preventDefault();
    if (!value) return;
    addTodo(value);
    setValue('');
  };

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

export default AddTodo;

Our AddTodo component, renders a form and a button. It also validates that the input field in the form actually has a value. We use useState again to manage the state. We also attached to the onChange event of the input field to update our value state variable every time the value of the input field is updated. When the user clicks the Add Todo button, we pass value to addTodo and it will take care of adding the item to our list. We also set value to an empty string leaving the form ready for the user to add another todo.

Congratulations! You’ve just built a basic to-do app with React.

| Posted in blog | No Comments »