Search code examples
reactjsreact-hooksjsxreact-contextuse-context

How to use useContext inside a component


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


Solution

  • 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