Update React Context using a REST Api call in a functional component
Asked Answered
B

2

20

I am trying to update the context of a React App using data resulted from an API call to a REST API in the back end. The problem is that I can't synchronize the function.

I've tried this solution suggested in this blog post https://medium.com/@__davidflanagan/react-hooks-context-state-and-effects-aa899d8c8014 but it doesn't work for my case.

Here is the code for the textContext.js

import React, {useEffect, useState} from "react";
import axios from "axios";

var text = "Test";

fetch(process.env.REACT_APP_TEXT_API)
    .then(res => res.json())
    .then(json => {
      text = json;
    })




const TextContext = React.createContext(text);
export const TextProvider = TextContext.Provider;
export const TextConsumer = TextContext.Consumer;

export default TextContext

And this is the functional component where I try to access the data from the context

import TextProvider, {callTextApi} from "../../../../services/textService/textContext";
function  Profile()
{

  const text = useContext(TextProvider);
  console.log(text);
  const useStyles = makeStyles(theme => ({
    margin: {
      margin: theme.spacing(1)
    }
  }));

I can see the fetch request getting the data in the network section of the browser console but the context is not getting updated.

I've tried doing this in the textContext.js.

export async function callTextApi () {
  await fetch(process.env.REACT_APP_TEXT_API)
    .then(res => res.json())
    .then(json => {
      return json;
    })
}


And I was trying to get the data in the Profile.js using the useEffect function as so

 const [text, setText] = useState(null);
  useEffect(()=> {
    setText (callTextApi())
  },[])

It's my first time using React.context and it is pretty confusing. What am I doing wrong or missing?

Broyles answered 12/9, 2019 at 12:50 Comment(0)
A
31

You have a lot of problems here. fetching and changing should happen inside Provider by modifying the value property. useContext receives an entire Context object not only the Provider. Check the following

//Context.js
export const context = React.createContext()

Now inside your Provider

import { context } from './Context'

const MyProvider = ({children}) =>{
    const [data, setData] = useState(null)
  
    useEffect(() =>{
        fetchData().then(res => setData(res.data))
    },[])
   
   const { Provider } = context
   return(
       <Provider value={data}>
           {children}
       </Provider>
   )
}

Now you have a Provider that fetches some data and pass it down inside value prop. To consume it from inside a functional component use useContext like this

import { context } from './Context'

const Component = () =>{
    const data = useContext(context)

    return <SomeJSX />
}

Remember that Component must be under MyProvider

UPDATE

  • What is { children }?

Everything that goes inside a Component declaration is mapped to props.children.

const App = () =>{
    return(
        <Button>
            Title
        </Button>
    )
}

const Button = props =>{
    const { children } = props

    return(
        <button className='fancy-button'>
            { children /* Title */}
        </button>
    )
}

Declaring it like ({ children }) it's just a shortcut to const { children } = props. I'm using children so that you can use your Provider like this

<MyProvider>
    <RestOfMyApp />
</MyProvider>

Here children is RestOfMyApp

  • How do I access the value of the Provider inside the Profile.js?

Using createContext. Let's assume the value property of your Provider is {foo: 'bar'}

const Component = () =>{
    const content = useContext(context)

    console.log(content) //{ foo : 'bar' }
}
  • How can you double declare a constant as you've done in the Provider?

That was a typo, I've changed to MyProvider

To access it from inside a class based component

class Component extends React.Component{
    render(){
        const { Consumer } = context
        return(
             <Consumer>
                 {
                     context => console.log(contxt) // { foo: 'bar' }
                 }
             </Consumer>
        )
    }
}
Android answered 12/9, 2019 at 12:58 Comment(13)
I have some question which will probably make me understand what's going on here . First, what is the {children}? Second, how do I access the value of the Provider inside the Profile.js? Third, how can you double declare a constant as you've done in the Provider?Broyles
See here for details on {children}, useContext, and the bracket syntax.Abound
@Dopocas, your implementation works well with classes but how about the functional components? How do you access the value of the context there?Broyles
Works well with functional components. Do you want to know how to access the context form inside a class based component?Android
Updated to use consumerAndroid
Yes please. Also, you're Context.js has only this line of code export const context = React.createContext()?Broyles
And I assuse that the Provider component is another .js file?Broyles
Yeap. Just another componentAndroid
I still dont understand how the context gets updated from the provider. I've tried to implement your solution and the fetchData function never gets called. On top of that Provider is never used. Im so confused right now.Broyles
@Android has explained all the different principles nicely in his reply, in a general way, now you should be able to apply those principles to your app. fetchData was just a general example. In your code, it would be callTextApi(). He just wanted to express how to call a function that fetches data as an example. The provider holds the context as its state and you can update that state by calling setText(). The provider component should wrap your application's components that want to access the context, the wrapped components are called children which @Android also addressed in his answer.Clite
Also, you questions go a bit beyond the scope of a stack overflow question and are becoming a bit broad, try and stick to the React documentation which also explains all those principles. reactjs.org/docs/context.html And as a heads up, check out my reply as you had some other minor bugs in your code regarding the handling of async functions.Clite
Thank you for this answer! Searching all over high and low throughout the internet this was the one answer / examples provided that actually made sense and worked!Infrequency
I'm glad to read this. If you need anything don't hesitate to contact me.Android
C
4

First thing that I am seeing is that you are not returning the promise within your function which will lead to setting the state to undefined.

I added the return statement below:

export async function callTextApi () {
  return await fetch(process.env.REACT_APP_TEXT_API)
    .then(res => res.json())
    .then(json => {
      return json;
    })
}

Also your last then-chain could be cleaned up a bit and I am quite sure you can remove the await statement in an async function when returning a promise. It will automatically be awaited:

export async function callTextApi () {
  return fetch(process.env.REACT_APP_TEXT_API)
    .then(res => res.json())
    .then(json => json)
}

Second step would be to have a look at your useEffect hook. You want to setText after the promise from the api call has been resolved. So you have to make the callback function of useEffect asynchronous as well.

  useEffect(async ()=> {
    const newText = await callTextApi();
    setText (newText);
  },[])

Third step, would be to look at how to properly use the context api and the useContext hook. The useContext hook takes a context as a parameter but you passed the ContextProvider as the argument.

const text = useContext(TextContext);

The context and the context-provider are two different entities in the React world. Think of the context as state and functionality that you want to share across your application (like a global state), and think about the provider as a react component that manages one context and offers this context state to it's child components.

return(
<TextContext.Provider value={/* some value */}>
    {children}
</TextContext.Provider>);

This is how a return statement of a provider component would look like and I think this code is currently missing in your application.

Clite answered 12/9, 2019 at 12:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.