Home > Back-end >  How to use useContext inside a component
How to use useContext inside a component

Time:02-20

I have a context that I have defined as 'HeaderContext' - I know its getting my data correctly becuase I can see the data in the console.log(headerData.Menu.PageUrl) line.

However I only see that when I uncomment the line and save the file after the page has loaded, so I suspect that the problem is that the data is not avalible when the page first loads. - This is my first react project so I think I'm missing something fundamental about what react is doing here.

Here is my code so far:

function Header() {

const [headerData] = useContext(HeaderContext)

console.log("heder data");
console.log(headerData.Menu.PageUrl);


return (
    <nav>
        <Link to="/">Logo here</Link>
        <ul>
            <li><Link to={headerData.Menu.PageUrl}>Menu</Link></li>
            <li><Link to="/contact">Contact</Link></li>
        </ul>
    </nav>
)}export default Header;

The error that I get is:

index.js:10 Uncaught TypeError: Cannot read properties of undefined (reading 'PageUrl')

For clarity here is the HeaderContext:

import React, { useState, createContext, useEffect } from 'react';
import API from "../../API"
export const HeaderContext = createContext();
export const HeaderProvider = (props) => {

    const [headerData, setHeaderData] = useState([]);

    const getHeader = async () => {
        const header = await API.fetchHeader();
        setHeaderData(header);
    };

    useEffect(() => {
        getHeader();
    }, []);

    return (
        <HeaderContext.Provider value={[headerData]}>
            {props.children}
        </HeaderContext.Provider >
    );}

And here is the App.js file:

function App() {
    return (
        <MenuProvider>
            <div className="App">
                <HeaderProvider>
                    <Header />
                </HeaderProvider>
                <Routes>
                    <Route path="/menu" element={<Menu />} />
                    <Route path="/menu/:id" element={<Category />} />
                    <Route path="/contact" element={<Contact />} />
                    <Route path="/more/faqs" element={<Faqs />} />
                    {/*<Home />*/}
                </Routes>
                <Footer />
            </div>
        </MenuProvider>
    );
}

enter image description here

CodePudding user response:

The initial headerData value is an empty array:

const [headerData, setHeaderData] = useState([]);

and passed in the context value also within an array:

<HeaderContext.Provider value={[headerData]}>
    {props.children}
</HeaderContext.Provider>

but you are accessing headerData value as if it were an object

console.log(headerData.Menu.PageUrl);

Since you say this works on subsequent renders it leads me to believe that headerData is actually really an object. Your Postman response confirms this.

The issue you have is that since headerData is an array the Menu property is undefined. This is ok until you try to access the PageUrl property of an undefined object.

Fix the initial headerData state value to also be an object:

const [headerData, setHeaderData] = useState({});

You might want to also pass it to the Context value as an object:

<HeaderContext.Provider value={headerData}>
    {props.children}
</HeaderContext.Provider>

Then just return it from the hook:

const headerData = useContext(HeaderContext);

and from here you'll just need to use Optional Chaining or null-checks/guard clauses to access into potentially undefined properties.

Examples:

  • headerData.Menu?.PageUrl
  • headerData.Menu && headerData.Menu.PageUrl

CodePudding user response:

usually context api return values in object however you are saying that you getting data correctly. your error is saying the data you trying to access is undefind acctualy the object property is undefind at the moment. you can access data like this:

<li><Link to={headerData?.Menu?.PageUrl}>Menu</Link></li>

CodePudding user response:

I suspect the problem is here:

   const getHeader = async () => {
        const header = await API.fetchHeader();
        setHeaderData(header);
    };

You are setting the headerData asynchronously and then pass it as value of the context provider. So the data is set later, when the fetchHeader is done. Basically you are first passing an empty array, then later when the async operation is done, you are setting it as an object I guess (I am not sure what the API returns).

Check this out to see how to use an async function in useEffect hook: https://devtrium.com/posts/async-functions-useeffect

CodePudding user response:

1st Solution: use optional chaining, that helps you to ignore the headerData attributes when they are undefined or null.

<li><Link to={headerData?.Menu?.PageUrl}>Menu</Link></li>

or 2nd Solution: you can set the initial keys of your headerData object as required by your application in the context file like this:

 const [headerData, setHeaderData] = useState({Menu: {PageUrl: ""}});

Both these methods will save your application from crashing and don't worry this is not a hack in React, it is just one of the correct ways to handle these types of issues.

CodePudding user response:

You should be getting an object when you call useContext. Like so :

const {headerData} = useContext(HeaderContext)
  • Related