0
votes

I'm trying to use React Context to update navbar title dynamically from other child components. I created NavbarContext.js as follows. I have wrapped AdminLayout with NavContext.Provider and use useContext in Course.js to dynamically update navbar title inside useEffect. However, when I'm doing this, react throws the following error on the screen.

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

How can I use context properly so that I can update Header title from Course.js inside its useEffect?

NavbarContext.js

import React, {useState} from 'react'

export default () => {
    const [name,setName] = useState("")

    const NavContext = React.createContext({
        name: "",
        changeName: name => setName(name) 
      })

      const NavProvider = NavContext.Provider
      const NavConsumer = NavContext.Consumer

    return NavContext
}

AdminLayout.js

<NavContext.Provider>
<div className={classes.wrapper}>
      <Sidebar
        routes={routes}
        logoText={"Widubima"}
        logo={logo}
        image={image}
        handleDrawerToggle={handleDrawerToggle}
        open={mobileOpen}
        color={color}
        {...rest}
      />
      <div className={classes.mainPanel} ref={mainPanel}>
        <Navbar
          routes={routes}
          handleDrawerToggle={handleDrawerToggle}
          {...rest}
        />
        {/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}
        {getRoute() ? (
          <div className={classes.content}>
            <div className={classes.container}>{switchRoutes}</div>
          </div>
        ) : (
          <div className={classes.map}>{switchRoutes}</div>
        )}
      </div>
    </div>
</NavContext.Provider>

Navbar.js

import NavContext from "context/NavbarContext"

export default function Header(props) {
   function makeBrand() {
    var name;
    props.routes.map(prop => {
      if (window.location.href.indexOf(prop.layout + prop.path) !== -1) {
        name = prop.name;
        document.title = name;
      }
      return null;
    });

    return name;
  }

  return (
    <AppBar className={classes.appBar + appBarClasses}>
      <Toolbar className={classes.container}>
        <div className={classes.flex}>
          {/* Here we create navbar brand, based on route name */}
          <NavContext.Consumer>
            {({ name, setName }) => (
              <Button
                color="transparent"
                href="#"
                className={classes.title}
                style={{ fontSize: "1.5em", marginLeft: "-2%" }}
              >
                {makeBrand() || name}
              </Button>
            )}
          </NavContext.Consumer>
      </Toolbar>
    </AppBar>
  );
}

Course.js

import React, { useState, useEffect, useContext } from "react";
import NavContext from "context/NavbarContext"

const AdminCourse = props => {
  const context = useContext(NavContext);

  useEffect(() => {
   Axios.get('/courses/'+props.match.params.courseId).then(
     res => {
       context.changeName("hello")
       }
   ).catch(err => {
     console.log(err)
   })
    return () => {
      setCourseId("");
    };
  });

  return (
    <GridContainer>
          </GridContainer>
  );
};

export default AdminCourse;

1
It appears you are using Context improperly. Have a look at the documentation. You don't define your Provider and Consumer components ahead of time. For a dynamic context example check here.segFault

1 Answers

2
votes

i think problem is there with your NavbarContext.js. you are not exporting NavContext also. you are defining provider, consumer but you are not using them either.

here's how you can solve your problem. first create context and it's provider in a file as following. NavContext.js

import React, { useState } from "react";
const NavContext = React.createContext();

const NavProvider = props => {
  const [name, setName] = useState("");
  let hookObject = {
    name: name,
    changeName: setName
  };
  return (
    <NavContext.Provider value={hookObject}>
      {props.children}
    </NavContext.Provider>
  );
};

export { NavProvider, NavContext };

in above code first i am creating context with empty value. the i am creating NavProvider which actually contains value name as a state hook inside it.hookObject exposes state as per your naming conventions in code.

now i for testing purpose i defined two consumers.

one is where we update name in useEffect, that is ,

ConsumerThatUpdates.js

import React, { useContext, useEffect } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatUpdates = () => {
  const { changeName } = useContext(NavContext);
  useEffect(() => {
    changeName("NEW NAME");
  }, [changeName]);
  return <div>i update on my useeffect</div>;
};

export default ConsumerThatUpdates;

you can update useEffect as per your needs. another is where we use the name,

ConsumerThatDisplays.js

import React, { useContext } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatDisplays = () => {
  const { name } = useContext(NavContext);

  return <div>{name}</div>;
};

export default ConsumerThatDisplays;

and finally my App.js looks like this,

App.js

import React from "react";
import "./styles.css";
import { NavProvider } from "./NavContext";
import ConsumerThatDisplays from "./ConsumerThatDisplays";
import ConsumerThatUpdates from "./ConsumerThatUpdates";

export default function App() {
  return (
    <div className="App">
      <NavProvider>
        <ConsumerThatDisplays />
        <ConsumerThatUpdates />
      </NavProvider>
    </div>
  );
}

hope this helps!! if you want to know more about how to use context effectively, i recooHow to use React Context effectively