I'm trying to learn the Context API, and what I want to achieve, is showing the Logged In user in my header, as well as manipulate my menu options based on the logged in state (Is it safe to store 'isAuthenticated' in state?)
My context class:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = {
isAuthenticated: true,
user: {
name: "Joe Smith",
email: "[email protected]"
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer;
So, extremely basic. Sets up a state, and in some child components later, I want to display the chaps name.
My App.js is the using 'Provider', so that all my components have access to this data:
import React from 'react';
import { HashRouter , Route, Switch } from 'react-router-dom';
import Home from './components/home';
import Header from './components/header';
import Accounts from './components/accounts';
import Reports from './components/reports';
import Login from './components/login';
import {Container} from 'reactstrap';
import { Provider } from './context';
function App() {
return (
<div className="App">
<Provider>
<HashRouter>
<Header />
<Container>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/accounts" component={Accounts} />
<Route exact path="/reports" component={Reports} />
<Route exact path="/login" component={Login} />
</Switch>
</Container>
</HashRouter>
</Provider>
</div>
);
}
export default App;
So in this case, 'Header' needs access to my context. My Header will show a menu (which, based on some info I'll add to the context, will show or hide options, login buttons etc).
Under the menu, I want to show a small info bar. Logged in user name, for example. So, my header class looks like this:
import React from 'react';
import Menu from './menu';
import InfoBar from './infobar';
import { Consumer } from '../context';
class Header extends React.Component {
render() {
const menuStyle = {
paddingBottom: "5px"
}
return(
<Consumer>
<div style={menuStyle}>
{value => {
console.log(value);
return (
<h1>Test</h1>
)
}}
<Menu />
<InfoBar />
</div>
</Consumer>
)
}
}
export default Header;
But the problem now happens. When I run my code, it compiles and runs, but right away, I get a runtime error:
TypeError: render is not a function updateContextConsumer C:/Storage/Scratch/ReactApp/accufinance/node_modules/react-dom/cjs/react-dom.development.js:16082
I read something about returns and multiple children, but my code seems to not have that issue. Any assistance with understanding the issue and where the problem is happening would be great. If I comment out the code in 'header', no error... but... no screen either. It seems to be occuring in the area.
This is the core principal of Context API, when a context value changed all components re-render. In order to prevent this we can use memo which will skip unnecessary re-renders of that component.
In React, the render() method is the only required method in a class component and is responsible for describing the view to be rendered to the browser window. Coupled with the clever way React operates around its virtual DOM concept, there are certain subtleties in how this method works.
React renders HTML to the web page by using a function called ReactDOM. render() .
How does React Context API work with the Render cycle? Every time the Provider Component renders, the value prop of its Context object may trigger a re-render of the component tree just as if it had passed a different prop to one of its child components.
Kent C. Dodds has a fantastic blog post that I refer to whenever I implement the Context API. If you don’t have time to read that, here’s the short version: Context is a way to share state between unrelated or distant components.
If you don’t have time to read that, here’s the short version: Context is a way to share state between unrelated or distant components. All you have to do is wrap your components in a Context.Provider and then call useContext (Context) inside that component to access your state and helper functions. Here’s our counter example with context:
But in real-world apps, many levels of intermediate components might need to be rendered between the root parent component and the target descendant components that need to access these data and functions. Our React Context also gives us a central location for our data and the functions which act on our data.
The Context Consumer uses a render prop, specifically a function as a child component, so it expects its immediate child to be a function (not a component). In your case, you can just move the div inside the function:
<Consumer>
{value => {
console.log(value);
return (
<div style={menuStyle}>
<h1>Test</h1>
<Menu />
<InfoBar />
</div>
)
}}
</Consumer>
Render props are really powerful for when you want to expose a component's internal state to its children, but when you also want to use it with different types of children.
The pattern is like this:
class Parent extends Component {
state = { name: 'Mike' }
handleChange = (e) => this.setState({ name: e.target.value })
render() {
// Here's the main difference: We expect `children` to be
// a function, and we pass name in as the argument
return children(state.name);
}
}
const InputChild = props => <input value={props.name} />
const HeaderChild = props => <h1>{props.name}</h1>
const App = () => {
return (
<Parent>
{name => {
// We could easily swap in `HeaderChild` here (or
// anything else!), passing `Parent`'s internal
// state.name to it instead:
<InputChild name={name} />
}
</Parent>
)
}
This is what makes Context work, because the Consumer doesn't have any knowledge of the components in its children, but it can expose its state (which is the value from the Provider).
The React docs have a great section on render props: https://reactjs.org/docs/render-props.html
If You're Using Functional Components and using useContext
with createContext
You Might Forget To add 'Provider' before rendering like myContext.Provider
In My Case I Usually Forgets .Provider
and writes only <myContext></myContext>
instead i've to do this... <myContext.Provider></myContext.Provider>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With