Update React Context using a REST Api call in a functional component

Octavian picture Octavian · Sep 12, 2019 · Viewed 8k times · Source

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?

Answer

Dupocas picture Dupocas · Sep 12, 2019

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>
        )
    }
}