简体   繁体   中英

React context state property is undefined

I'm trying to build a web application menu dynamically using react's new context API, json as data, Providers, Consumers etc. I can't figure out an error I get. But first here's some code:

Context file includes:

import React from 'react';

export const MenuContext = React.createContext();

class MenuProvider extends React.Component {
  state = {
    menu: {}
  };
  actions = {
    fetchMenu: async() => {
      const response = await fetch('/admin/json/menu.json');
      const body = await response.json();
      this.setState({
        menu: body
      });
    }
  }
  componentDidMount() {
    this.actions.fetchMenu();
  }
  render() {
    return(
      <MenuContext.Provider value={{state: this.state, actions: this.actions}}>
        {this.props.children}
      </MenuContext.Provider>
    )
  }
}

export default MenuProvider;

The menu component consumes this context as follows:

import React from 'react';
import {v1 as uuid} from 'uuid';

import MenuProvider, {MenuContext} from './contexts/menu';

import MenuDropdown from './parts/menu-dropdown';
import MenuItem from './parts/menu-item';

export default class SideMenu extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return(
      <header>
        <MenuProvider>
          <MenuContext.Consumer>
            {({state}) => (
              <React.Fragment>
                <div className="cnt-menu-head">
                  <h6>{state.menu.heading}</h6>
                </div>
                <div className="cnt-menu">
                  <ul className="nav nav-pills flex-column">
                    {state.menu.sections.map((section, i) => {
                      if (section.type === 'link') {
                        return <MenuItem exact={section.exact} linkTo={section.linkTo} linkText={section.linkText}/>
                      } else if (section.type === 'dropdown') {
                        var keyedLinks = section.links.map((link) => {
                          link.key = uuid();
                          return link;
                        });
                        return <MenuDropdown key={uuid} linkText={section.linkText} links={keyedLinks}/>
                      }
                    })}
                  </ul>
                </div>
              </React.Fragment>
            )}
          </MenuContext.Consumer>
        </MenuProvider>
      </header>
    );
  };
};

menu.json file has this data:

{
  "heading": "Menu Heading",
  "sections": [
    {
      "type": "link",
      "linkText": "A Link",
      "linkTo": "/",
      "exact": true
    },
    {
      "type": "dropdown",
      "linkText": "System",
      "links": [
        {
          "linkText": "System Table",
          "linkTo": "/table/system",
          "exact": false
        },
        {
          "linkText": "Add System",
          "linkTo": "/form/addsystem",
          "exact": false
        }
      ]
    }
  ]
}

Versions of packages I use in package.json are:

"devDependencies": {
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-preset-es2016": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "react": "^16.4.1",
    "react-dom": "^16.4.1",
    "react-router-dom": "^4.3.1",
    "uuid": "^3.3.0"
},

And .babelrc file is:

{
  "presets": [
    ["es2016"],
    "react"
  ],
  "plugins": [
    "transform-class-properties"
  ]
}

Compilation of this application runs without errors, though I get a javascript error in console when I run it:

Uncaught TypeError: Cannot read property 'map' of undefined

The property in question here is state.menu.sections which I want to loop through and render based on type. If I comment out the part I loop state.menu.sections with map, the part <h6>{state.menu.heading}</h6> works flawlessly and renders <h6>Menu Heading</h6> without any errors. What may be the problem when I loop the sections array?

Try this code:

import React from 'react';

export const MenuContext = React.createContext();

class MenuProvider extends React.Component {
  state = {
    menu: { heading: '', sections: [] }
  };
  actions = {
    fetchMenu: async() => {
      const response = await fetch('/admin/json/menu.json');
      const body = await response.json();
      this.setState({
        menu: body
      });
    }
  }
  componentDidMount() {
    this.actions.fetchMenu();
  }
  render() {
    return(
      <MenuContext.Provider value={{state: this.state, actions: this.actions}}>
        {this.props.children}
      </MenuContext.Provider>
    )
  }
}

export default MenuProvider;

The difference is menu in state , instead of empty menu object, you need to provide it with default values.

NOTE: I not sure about the error, but I think it is because javascript try to use map on state.menu.sections before the componentDidMount set new state to your state (so the state.menu.sections will be undefined by default)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM