React context returns undefined - javascript

I want to use context in React to pass authentication information to child components. My context provider is in a file named AuthContext.js:
import React, { Component, createContext } from 'react';
export const AuthContext = createContext();
class AuthContextProvider extends Component {
state = {
isAuthenticated: false
};
toggleAuth = () => {
this.setState({ isAuthenticated: !this.state.isAuthenticated });
};
render() {
return (
<AuthContext.Provider value={{...this.state, toggleAuth: this.toggleAuth}}>
{this.props.children}
</AuthContext.Provider>
);
}
}
export default AuthContextProvider;
But when I'm accesing the context from the child components, it returns undefined
import { AuthContext } from "../contexts/AuthContext";
export default function ButtonAppBar() {
return(
<AuthContext.Consumer>{(authContext) => {
console.log(authContext);
}}</AuthContext.Consumer>
);
}
After checking the React Developer Tools, I came to see that ButtonAppBar is not inside AuthContextProvider. Imports are correct. Any reason for this apart from import errors?
React version: 16.9.0

If you take a look at this working codesandbox you will see that your code is correct and should be working if you did everything all right.
Reasons that can make it not work
Maybe you are importing it wrong? Please check how you import it.
Maybe ButtonAppBar isn't inside AuthContextProvider.
For the context to work, the consumer needs to be inside of the provider.
This will work
<AuthContextProvider>
<ButtonAppBar /> {// consumer inside the provider }
</AuthContextProvider>
This will not work
<AuthContextProvider>
...
</AuthContextProvider>
<ButtonAppBar /> {// consumer outside the provider }
In the screenshot from the question, you don't have AuthContextProvider wrapping the routes, so you will never be able to get the context.
You didn't provided the code correctly but I'm guessing that what you need to do is
<App>
<AuthContextProvider> {// AuthContextProvider wrapping routes so you can get the context}
<BrowserRouter>
...
</BrowserRouter>
</AuthContextProvider>
</App>

Related

Error when put <BrowserRouter> and useRoutes(routes) in A function component in React

I'm using react-router-dom in my react app.
When I use:
function App() {
return (
<BrowserRouter>{ useRoutes(routes) }</BrowserRouter>
);
}
And I got error message: Uncaught Error: useRoutes() may be used only in the context of a <Router> component.
I've searched how to fix it and change my code's structure to:
function App() {
return (
<BrowserRouter><Foo/></BrowserRouter>
);
}
function Foo() {
return useRoutes(routes)
}
The code works properly.
As a starter, I can't tell the exact difference between the two snippet above, could someone please help?
Uncaught Error: useRoutes() may be used only in the context of a <Router> component.
All this message is saying is that there needs to be a routing context provider higher in the ReactTree than this App component that is trying to consume it when using the useRoutes hook.
A clearer bad example:
function App() {
const routes = useRoutes(routes); // <-- (2) but needed here!
return (
<BrowserRouter> // <-- (1) context provided here
{routes}
</BrowserRouter>
);
}
Here you can clearly see that the router is below the App component and there is no routing context provided to App.
This is why the second snippet works, it's providing the routing context higher than the component consuming it.
function App() {
return (
<BrowserRouter> // <-- (1) context provided here
<Foo/> // <-- (2) context consumed here
</BrowserRouter>
);
}
function Foo() {
return useRoutes(routes);
}
Here's another example where the error was thrown and was fixed thanks to #drew-reese's explanation:
create-react-app builds the following TypeScript test file:
import React from 'react';
import { render, screen } from '#testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
The fix is to include BrowserRouter
import { BrowserRouter } from "react-router-dom";
and to wrap <App /> to get the routing context:
render(<BrowserRouter><App /></BrowserRouter>);

verify react functional component to be there

I'm new to react unit testing, here I have react code which works fine but need to unit test it.
I want to verify the component to be there, I have tried in two different ways and both of them does not work:
I'm using useSelector and useDispatch thats why connect(null,null).
my code:
M2SelectionCard.js:
function ConnectedSelectionCard({ classes }) {
return (
<Card data-testid="M2SelectionCardd" className={classes.selectionCard}>
<CardContent>
</CardContent>
</Card>
);
}
const M2SelectionCard = connect(null, null)(ConnectedSelectionCard);
export default M2SelectionCard;
first I did like this:
import React from "react";
import { expect } from "#jest/globals";
import { render, screen, cleanup } from "#testing-library/react";
import M2SelectionCard from "../M2SelectionCard";
test("test", () => {
render(<M2SelectionCard />);
const SelectionCardElement = screen.getByTestId("M2SelectionCardd");
expect(SelectionCardElement).toBeInTheDocument();
// expect(true).toBe(true);
});
and got error : Could not find "store" in the context of "Connect(ConnectedSelectionCard)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(ConnectedSelectionCard) in connect options.'
import React from "react";
import { expect } from "#jest/globals";
import { render, screen, cleanup } from "#testing-library/react";
import M2SelectionCard from "../M2SelectionCard";
import { Provider } from "react-redux";
import configureStore from "../../redux/store";
const store = configureStore({});
it("test", () => {
render(
<Provider store={store}>
<M2SelectionCard />
</Provider>
);
const SelectionCardElement = screen.getByTestId("M2SelectionCardd");
expect(SelectionCardElement).toBeInTheDocument();
});
after that I added store to it in test (don't know should I add it here ?):
error message:
TypeError: Cannot read property 'selectionCard' of undefined
it points to className={classes.selectionCard}
any idea ?
TypeError: Cannot read property 'selectionCard' of undefined'
pointing to className={classes.selectionCard}
This error is saying that it cannot access a selectionCard property of an undefined object, classes in this case.
Given component under test:
function ConnectedSelectionCard({ classes }) {
...
return (
<Card data-testid="M2SelectionCardd" className={classes.selectionCard}>
<CardContent>
</CardContent>
</Card>
);
}
You should still pass all the expected props, i.e. a classes prop. For this purpose an empty object is sufficient enough to access into. In other words, if classes is an empty object then classes.selectionCard evaluates to undefined instead of throwing an error.
it("test", () => {
render(
<Provider store={store}>
<M2SelectionCard classes={{}} /> // <-- pass a classes prop
</Provider>
);
const SelectionCardElement = screen.getByTestId("M2SelectionCardd");
expect(SelectionCardElement).toBeInTheDocument();
});

why my context doesn't update when the context value updates?

so I am using React's context API in my Gatsby app(which is written in React basically) to deal with user authentication. I have two components that use that context: dashboard and navBar. When I try to log in and log out, my navBar will behave differently according to my userContext, but my dashboard won't respond. Is it something related to the structure, like navBar is the direct "children" to layout, but dashboard is not? I assume not though, after all, that's why I use contextAPI then just pass a normal prop.
Here are the codes:
//layout.js
import React, { useContext, useState, createContext } from "react"
import Navbar from "../components/navBar"
import {monitorAuth} from "../firebase/firebaseService"
export const UserStateContext = createContext(null)
export const SetUserContext = createContext()
const Layout = ({ children }) => {
const [user, setUser] = useState()
console.log(user)
monitorAuth(setUser)// everytime a layout component renders, it will grab a user if it is logged inthen setUser, then I will use it in the context
return (
<>
<UserStateContext.Provider value={user}>
<SetUserContext.Provider value={setUser}>
<div>
<SEO />
<Navbar />
<main>{children}</main>
</div>
</SetUserContext.Provider >
</UserStateContext.Provider>
</>
)
}
export default Layout
import React, { useState, useContext } from "react"
import AppBar from "#material-ui/core/AppBar"
import { signOut } from "../firebase/firebaseService"
import {UserStateContext} from "./layout"
export default function NavBar() {
const user = useContext(UserStateContext)
console.log(user) // when I log in/ log out, it will console.log the right user status, user/null
const renderMenu = () => {
return (
<>
{user? (
<>
<Button onClick={signOut}>Sign Out</Button>
<Button>My profile</Button>
</>)
:<Button>Sign In</Button> }
</>
)
}
return (
<AppBar position="static" className={classes.root}>
...
{renderMenu()}
...
</AppBar>
)
}
//dashboard.js
import React, { useContext } from 'react'
import Layout from '../components/layout'
import LoggedIn from '../components/dashboard/loggedIn'
import NotLoggedIn from '../components/dashboard/notLoggedIn'
import {UserStateContext} from "../components/layout"
const Dashboard = props => {
console.log("within dashboard")
const user = useContext(UserStateContext)
console.log(user)
const renderDashboard = () =>{
return (
<>
{user? <LoggedIn /> : <NotLoggedIn />}
</>
)
}
return(
<Layout>
{renderDashboard()}
</Layout>
)
}
export default Dashboard
One more clue, I console.log user in all three components and when I refresh the page:
within dashboard
dashboard.js:17 null
layout.js:15 undefined
navBar.jsx:54 undefined
layout.js:15 [user...]
navBar.jsx:54 [user...]
layout.js:15 [user...]
That means, at first, user is not set yet, so all three components log the user as undefined, but later, layout detect the user and then updates it, so navbarknows too, but dashboard doesn't. Is it something about re-render? Thanks!
The reason it's not working is because your <Dashboard> component is not a child of the context provider. If you use React devtools, you'll see the component tree looks like
<Dashboard>
<Layout>
<UserStateContext.Provider>
<SetUserContext.Provider>
...
</SetUserContext.Provider>
</UserStateContext.Provider>
</Layout>
</Dashboard>
When the context value changes, it looks for components in its subtree that useContext. However, Dashboard is not a child, it's the parent!
If you want to follow this pattern, a solution may be to create a parent component of Dashboard and put the context there.

React Router & Global Context

I'm building an e-commerce website with React (my first ever React project) and I'm using React router to manage my pages.
I've got the following component tree structure:
<Router>
<BrowserRouter>
<Router>
<withRouter(Base)>
<Route>
<Base>
<BaseProvider>
<Context.Provider>
<Header>
<PageContent>
The standard React Router structure basically, and withRouter I've got the following:
Base.js
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { Header } from './Header';
import { Footer } from './Footer';
import Provider from '../../BaseProvider';
class Base extends Component {
render() {
return (
<Provider>
<Header/>
<div className="container">{this.props.children}</div>
<Footer />
</Provider>
);
}
}
BaseProvider.js
import React, { Component, createContext } from 'react';
const Context = createContext();
const { Provider, Consumer } = Context;
class BaseProvider extends Component {
state = {
cart: [],
basketTotal: 0,
priceTotal: 0,
};
addProductToCart = product => {
const cart = { ...this.state.cart };
cart[product.id] = product;
this.setState({ cart, basketTotal: Object.keys(cart).length });
};
render() {
return (
<Provider
value={{ state: this.state, addProductToCart: this.addProductToCart }}
>
{this.props.children}
</Provider>
);
}
}
export { Consumer };
export default BaseProvider;
This gives me a template essentially, so I just the children pages without having to include Header and Footer each time.
If I want to use my global context I'm having to import it each time, and it seems like I've done something wrong as surely I should be able to use this on any page since it's exported in BaseProvider?
If I was to visit the About page, I'd get the same component structure, but no access to the consumer without using:
import { Consumer } from '../../BaseProvider';
Why do I have to do this for each file even though it's exported and at the top level of my BaseProvider? It just seems such a bad pattern that I'd have to import it into about 20 files...
Without importing it, I just get:
Line 67: 'Consumer' is not defined no-undef
I tried just adding the contextType to base but I get: Warning: withRouter(Base): Function components do not support contextType.
Base.contextType = Consumer;
I feel like I've just implemented this wrong as surely this pattern should work a lot better.
I'd recommend using a Higher Order Component - a component that wraps other components with additional state or functionality.
const CartConsumer = Component => {
return class extends React.Component {
render() {
return (
<MyContext.Consumer>
<Component />
</MyContext.Consumer>
)
}
}
}
Then in any component where you'd like to use it, simply wrap in the export statement:
export default CartConsumer(ComponentWithContext)
This does not avoid importing completely, but it's far more minimal than using the consumer directly.

this.props.history.push works in some components and not others

I am trying to programmatically change pages using browserHistory.push. In one of my components, but not in a component that I embedded inside of that one.
Does anyone know why my project is throwing the error below only for the child component but not for the parent component?
Cannot read property 'push' of undefined
Parent Component
import React, {Component} from 'react';
import { browserHistory } from 'react-router';
import ChildView from './ChildView';
class ParentView extends Component {
constructor(props) {
super(props);
this.addEvent = this.addEvent.bind(this);
}
changePage() {
this.props.history.push("/someNewPage");
}
render() {
return (
<div>
<div>
<button onClick={this.changePage}>Go to a New Page!</button>
</div>
<ChildView /> // this is the child component where this.props.history.push doesn't work
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
setName: setName
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ParentView);
Child Component
import React, {Component} from 'react';
import { browserHistory } from 'react-router';
class ChildView extends Component {
constructor(props) {
super(props);
this.addEvent = this.addEvent.bind(this);
}
changePage() {
this.props.history.push("/someNewPage");
}
render() {
return (
<div>
<div>
<button onClick={this.changePage}>Go to a New Page!</button>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
setName: setName
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ChildView);
Router
// Libraries
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { browserHistory } from 'react-router';
// Components
import NotFound from './components/NotFound';
import ParentView from './components/ParentView';
import ChildView from './components/ChildView';
import SomeNewPage from './components/SomeNewPage';
// Redux
import { Provider } from 'react-redux';
import {createStore} from 'redux';
import allReducers from './reducers';
const store = createStore(
allReducers,
window.devToolsExtension && window.devToolsExtension()
);
const routes = (
<Router history={browserHistory}>
<div>
<Provider store={store}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/parentView" component={ParentView} />
<Route path="/someNewPage" component={SomeNewPage} />
<Route path="/childView" component={ChildView} />
<Route component={NotFound} />
</Switch>
</Provider>
</div>
</Router>
);
export default routes;
As you can see, the components are virtually exactly the same except that the child one is inside the parent one.
Note I have tried these approaches but they do not resolve the issue for me:
Uncaught TypeError: Cannot read property 'push' of undefined with correct import being available
React browserHistory.push giving Uncaught TypeError: Cannot read property 'push' of undefined
redirect to a page programmatically in react-router 2
You answered your question in your question.
As you can see, the components are virtually exactly the same except
that the child one is inside the parent one.
The very fact that the component is nested one further is your issue. React router only injects the routing props to the component it routed to, but not to the components nested with in.
See the accepted answer to this question. The basic idea is that you now need to find a way to inject the routing props to the child component.
You can do that by wrapping the child in a HOC withRouter.
export default withRouter(connect(mapStateToProps, matchDispatchToProps)(ChildView));
I hope this helps.
Using withRouter is fine, an alternative option is to pass the history as a prop from parent to child (without using withRouter), e.g:
Parent
<SignupForm history={this.props.history}/>
Child
this.props.history.push('/dashboard');
You Can try this
this.context.history.push('/dashboard')
or pass the history as a prop from parent to child component like
parent
<SignupForm history={this.props.history}/>
child
this.props.history.push('/dashboard');
or withRouter
import { withRouter } from "react-router";
export default withRouter(connect(mapStateToProps, {
...
})(Header));
This is the problem of Nested Components.
I was facing this issue in Header component.
I wanted to redirect the page to home page on clicking the logo.
this.props.history.push won't work in nested components.
So Solution is to use withRouter .
To Use withRouter just copy paste Below 2 lines in Header(You can use it in your component) :
import { withRouter } from "react-router";
export default withRouter(connect(mapStateToProps, {
...
})(Header));
If anyone is still facing similar issues please try to change the onClick to this
onClick={() => this.props.history.push("/dashboard")}
also if you are in a child component please use the <Redirect to="/dashboard"/> component

Categories