skip to Main Content

I’m trying to create a context whose default value will come from an external source. I would need to do something similar to doing the fetch outside of an asynchronous function (I don’t think this is possible). The question is: how to perform fetch in this scenario?

import React, { createContext, useState } from "react";
import { Usuario } from "../typings/types";


interface UsuariosContextType {
    usuarios: Usuario[]
    setUsuario?: React.Dispatch<React.SetStateAction<Usuario[]>>;
}

// I have no ideia how to do it the right way
const usuarios_data = await fetch(`http://localhost:8080/usuarios`, { cache: 'no-store' })
const usuarios_array = await usuarios_data.json()
const usuarios_lista: Usuario[] = usuarios_array

export const UsuariosContext = createContext<UsuariosContextType>({ usuarios: usuarios_lista })

export const UsuariosProvider = (children: React.ReactNode) => {

    const [usuarios, setUsuario] = useState<Usuario[]>([])

    return (
        <UsuariosContext.Provider value={{ usuarios, setUsuario }}>
            {children}
        </UsuariosContext.Provider >
    )
}

2

Answers


  1. To asynchronously load data and set it as a context value, you will need to use useEffect to load your data and a useState to control loading state (which you might use later to display a spinner).

    In the snippet below, we make an API call in useEffect once the context component mounts. In the useEffect, we also set isLoading to true. Once the request is finished and parsed, we use finally to set isLoading to false.

    import React, { createContext, useState, useEffect } from "react";
    import { Usuario } from "../typings/types";
    
    
    interface UsuariosContextType {
        usuarios: Usuario[]
        setUsuario?: React.Dispatch<React.SetStateAction<Usuario[]>>;
        isLoading: boolean;
    }
    
    export const UsuariosContext = createContext<UsuariosContextType>({ usuarios: [], isLoading: false })
    
    export const UsuariosProvider = (children: React.ReactNode) => {
    
        const [usuarios, setUsuario] = useState<Usuario[]>([]);
        const [isLoading, setIsLoading] = useState(false);
    
        useEffect(() => {
          setIsLoading(true);
    
          fetch(`http://localhost:8080/usuarios`, { cache: 'no-store' })
          .then((data) => data.json())
          .then(parsedData => setUsuario(parsedData))
          .finally(() => setIsLoading(false));
        }, []);
    
        return (
            <UsuariosContext.Provider value={{ usuarios, setUsuario, isLoading }}>
                {children}
            </UsuariosContext.Provider >
        )
    }
    Login or Signup to reply.
  2. In React, data fetching must be done inside an asynchronous function, which is not possible outside the component’s lifecycle methods. When initializing a context with external data, the fetch operation should be performed after the component mounts. To handle this, React provides hooks like useEffect() that allow you to run side effects (like fetching data) when the component is rendered.

    I would recommend familiarizing yourself with the async nature of React and effects handling via useEffect() hook.

    1. In react you fetch the external data within the effect hook that runs right after your component mounts, inside of the component lifecycle.
    2. You also need to leverage useState() hook to store the fetched data.
    3. useEffect() executes the fetch call and saves data into a state, that
      will be represented withing your defined context.

    Complete Solution

    import React, { createContext, useState, useEffect, ReactNode } from "react";
    import { Usuario } from "../typings/types";
    
    interface UsuariosContextType {
        usuarios: Usuario[];
        setUsuario?: React.Dispatch<React.SetStateAction<Usuario[]>>;
    }
    
    export const UsuariosContext = createContext<UsuariosContextType>({ usuarios: [] });
    
    export const UsuariosProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
        const [usuarios, setUsuario] = useState<Usuario[]>([]);
    
        useEffect(() => {
            // Fetch the data when the component mounts
            const fetchUsuarios = async () => {
                try {
                    const response = await fetch(`http://localhost:8080/usuarios`, { cache: 'no-store' });
                    if (!response.ok) {
                        throw new Error("Failed to fetch usuarios");
                    }
                    const data: Usuario[] = await response.json();
                    setUsuario(data);
                } catch (error) {
                    console.error("Error fetching usuarios:", error);
                }
            };
    
            fetchUsuarios();
        }, []); // Empty dependency array ensures this runs only once on mount
    
        return (
            <UsuariosContext.Provider value={{ usuarios, setUsuario }}>
                {children}
            </UsuariosContext.Provider>
        );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search