2

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

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Ayo Adesina
  • 2,231
  • 3
  • 37
  • 71

4 Answers4

1

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
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • This makes ALOT of sense and thank you for making a few things clear here for me. However Making those changes around useState({}); and useState([]); then updating the console.log to headerData.Menu?.PageUrl results in the page loading correctly every single time great. HOWEVER when I try and output headerData.Menu?.PageUrl in to the link. I get another error "Cannot read properties of undefined (reading 'pathname')" do I need to useEffect? – Ayo Adesina Feb 19 '22 at 10:53
  • @AyoAdesina Oh, yeah, the `Link` component needs a defined `to` prop. You can either provide a fallback value, i.e. `Menu`, or conditionally render the link when you have a defined destination, i.e. `{headerData.Menu?.PageUrl && Menu}`. Does this make sense? You may want to just conditionally render the entire header UI instead. – Drew Reese Feb 19 '22 at 10:57
  • 1
    Having a fall back seems to work, even if I put in an empty string like this:
  • Menu
  • I end up with the correct value ie "/menu" I guess that happens when the data gets loaded on the second rerender... HOWEVER this seems all very messy. Is there away I can make the page wait untill the data is avalible BEFORE trying to out put the values. All of these ? and && makes the code look YUKKY lol – Ayo Adesina Feb 19 '22 at 11:07
  • @AyoAdesina Agree to disagree, I think it makes the code beautiful and easy to use. Like I said, the alternative would be to conditionally render the entire header UI. You could start with undefined or `null` `headerData` state, so a simple ternary would suffice, i.e. `return headerData ? () : null;`. – Drew Reese Feb 19 '22 at 11:10
  • 1
    I know you shouldn't use comments to say thank you, but I what to say thanks :-) You have brought clarity to a few things for me here. But yes I think we will agree to disagree on that last point I will use this for now untill I learn a bit more about react and refactor later. Thank you a great comprehensive answer. – Ayo Adesina Feb 19 '22 at 11:15
  • After reviewing some of my other code and examples I have been doing, I have a question/comment I would like to know what you think. Am I using the wrong approach, in terms of using the context api here, should I just call my api fuction from inside the component? would that change the way this works? and mean I don't need to provide this fall back value? – Ayo Adesina Feb 19 '22 at 12:16
  • 1
    @AyoAdesina For very simple apps with minimal state, using a React context may be a bit overkill, but isn't an invalid use. The React Context API exists namely to eliminate the issue of "props drilling", that is to say, the issue created when you pass props from some component A to B, then from B to C, and then from C to D that *actually* cares and uses them. As your app and state grows in complexity, you'll want the benefit and simplicity of the Context. For example `react-redux` is an abstraction built over the Context API. – Drew Reese Feb 20 '22 at 08:52