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

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.

Related

Error: React Hook "useRouter" - nextJs/javascript app not building

I am using nextJS and running into a little problem here and am not sure where I am going wrong. I've built an app where everything works in dev. However, I am trying to build my app but I am receiving the error:
./pages/genre.js/[genre].js
13:18 Error: React Hook "useRouter" is called in function "genre" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use". react-hooks/rules-of-hooks
The useRouter is located is on my page called [genre].js and the code is detailed below:
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
export default function genre() {
const router = useRouter();
const { genre } = router.query;
if (genre == "Trending") {
let mymovies = useFetchTrendingCatagory();
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else if (genre == "Top Rated") {
let mymovies = useFetchTopRatedCatagory();
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else {
let mymovies = useFetchMovieGenreResults(genre);
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
}
The official for Nextjs suggests using the useRouter to access the URL parameters
Why does this error occur? What am I missing? Any workarounds?
Update
I am trying to go with the solution below.
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
const useMovies = (genre) => {
switch (genre) {
case 'Trending':
return useFetchTrendingCatagory()
case 'Top Rated"':
return useFetchTopRatedCatagory()
default:
return useFetchMovieGenreResults(genre)
}
}
export default function Genre () {
const router = useRouter();
const { genre } = router.query;
const mymovies = useMovies(genre)
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
)
}
However I am still getting the errors below while trying to build my code
**./pages/genre.js/[genre].js
15:14 Error: React Hook "useFetchTrendingCatagory" is called conditionally. React Hooks must be called in the
exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
17:14 Error: React Hook "useFetchTopRatedCatagory" is called conditionally. React Hooks must be called in the
exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
19:14 Error: React Hook "useFetchMovieGenreResults" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks**
Would not the switch statement be considered a conditional render?
Any workaround for this?
You simply need to rename your component to start with a capital letter, i.e., Genre. The reason for this, is that components must start with uppercase letters, and only components and hooks can make use of hooks (such as useRouter)
export default function Genre()
For the hook problem you can't conditionally call hooks or call them within blocks, they're a lot like import statements, they need to be called at the top of their parents (for the hook, the parent will be the function, i.e., you need to call hooks at the top of the component/function). To still do what you want to do, rather than conditionally calling the hook, you can conditionally pass in parameters using the ternary operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator). I recommed reading into hook patterns (https://www.patterns.dev/posts/hooks-pattern/):
const router = useRouter();
const { genre } = router.query;
// instead of calling the hook conditionally, change the parameter you're calling in the hook conditionally, this will fix the error you're getting with regards to conditionally calling hooks
const mymovies = useFetchTrendingCategory(genre == "Trending" || genre == "Top Rated" ? undefined : genre)
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
I can see 2 problem here.
The first one is the component name (as #Ameer said). Try to rename it to Genre.
Second one is the use of hook in a conditionally render. This is not a good practice.
You can refactor code like this:
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
export default function Genre () {
const router = useRouter();
const { genre } = router.query;
const [movies, setMovies] = useState([]) // is that an array?
useEffect(() => {
let fetchedMovies
switch (genre) {
case 'Trending':
fetchedMovies = useFetchTrendingCatagory()
case 'Top Rated"':
fetchedMovies = useFetchTopRatedCatagory()
default:
fetchedMovies = useFetchMovieGenreResults(genre)
}
setMovies(fetchedMovies)
}, [genre])
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={movies} />
</div>
)
}
or like this:
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
const useMovies = (genre) => {
switch (genre) {
case 'Trending':
return useFetchTrendingCatagory()
case 'Top Rated"':
return useFetchTopRatedCatagory()
default:
return useFetchMovieGenreResults(genre)
}
}
export default function Genre () {
const router = useRouter();
const { genre } = router.query;
const mymovies = useMovies(genre)
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
)
}

Pass parameters to another page in React JS

I'm having a problem where I want the information that is being passed on a page to be sent to another page, this other page I say is like a single page.
I've already tried to pass the parameters of this page to another with props, params, but without any success.
I believe it is something very simple, but it left me without a solution
Homepage.jsx
import React, {useEffect, useState} from 'react';
import * as Styled from './styles';
import OwlCarousel from 'react-owl-carousel';
import 'owl.carousel/dist/assets/owl.carousel.css';
import 'owl.carousel/dist/assets/owl.theme.default.css';
import { FaStar,FaInfoCircle } from "react-icons/fa";
import { NavLink } from 'react-router-dom';
import SinglePage from '../SinglePage';
export default function Home() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://api.rawg.io/api/games?key=328c7603ac77465895cf471fdbba8270')
.then((res) => res.json())
.then((data) => {
setData(data.results);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<>
<Styled.Container>
<div className="boxSite">
<div className="boxBrowse">
<div className="boxAll">
<OwlCarousel className='owl-theme' loop margin={0} items={6} center={true} dots=
{false}>
{data.map((games)=> (
<>
<div className="produto" key={games.id} layoutId={games.id}>
<div className="imagemGame" style={{backgroundImage:
`url(${games.background_image})`}}>
<div className="information">
<NavLink to={{
pathname:`/single-game/${games.slug}`,
}}
>
<span>
<FaInfoCircle/>
</span>
</NavLink>
<SinglePage name={games.name} />
</div>
<div className="classificacao">
<span> Avaliação <b> {games.rating} </b></span>
<span> <FaStar /></span>
</div>
</div>
</div>
</>
))}
</OwlCarousel>
</div>
</div>
</div>
</Styled.Container>
</>
)
}
SinglePage.jsx
import React from 'react';
import * as Styled from './styles';
export default function SinglePage(props) {
return (
<>
<h1>NAME OF THE GAME : {props.name}</h1>
</>
)
}
Yes, I stopped here, please can someone help me?
Information is appearing on the Homepage, but not on the single page
In this case, if you're using version 5 or earlier of router-dom you can pass the data via state, using history:
Change this:
import { NavLink } from 'react-router-dom';
return (
<NavLink to={{
pathname:`/single-game/${games.slug}`,
}}>
<span>
<FaInfoCircle/>
</span>
</NavLink>
)
To this:
import { useHistory } from 'react-router-dom';
const history = useHistory();
return (
<button
onClick(() => history.push(`/single-game/${games.slug}`,{
foo: 'bar',
nameGame,
}))
>
<span>
<FaInfoCircle/>
</span>
</button>
)
And on your page you can get the data via props, like:
import React from 'react';
export default function SinglePage(props) {
const { nameGame } = props.location.state;
return (
<>
<h1>NAME OF THE GAME : {nameGame}</h1>
</>
)
}
It depends on your needs.
If you need to access a lot of data over several pages, you should use a state management library like Redux.
If it's only simple data, you can pass it as query parameters to your page.
If it's a bit more complexe, you can use the session / local storage.
But it's a bit hard to know what to recommend you exactly without more info about what you want to achieve exactly.
Import component into Homepage
import SinglePage from './your-single-page-file';
Use the tag and pass a parameter on the JSX
<SinglePage name={data.variableNeeded} />
On the SinglePage function add the props parameter and use it like this:
import React from 'react';
import * as Styled from './styles';
export default function SinglePage(props) {
return (
<>
<h1>NAME OF THE GAME : {props.name}</h1>
</>
)
}
Since you only included the component that has a single data state that is mapped and renders some links I'll assume it is the games object you want to send to the linked route. Pass the data in route state via the NavLink component.
See NavLink and Link
interface LinkProps
extends Omit<
React.AnchorHTMLAttributes<HTMLAnchorElement>,
"href"
> {
replace?: boolean;
state?: any;
to: To;
reloadDocument?: boolean;
}
Route state is sent along on the state prop of a link component.
Example:
{data.map((games) => (
<div className="produto" key={games.id} layoutId={games.id}>
<div
....
>
<div className="information">
<NavLink
to={`/single-game/${games.slug}`}
state={{ games }} // <-- pass route state
>
<span>
<FaInfoCircle/>
</span>
</NavLink>
<SinglePage name={games.name} />
</div>
....
</div>
</div>
))}
In the receiving component use the useLocation hook to access the passed route state.
Example:
import { useLocation } from 'react-router-dom';
export default function SinglePage() {
const { state } = useLocation();
const { games } = state || {};
...
return (
<>
<h1>NAME OF THE GAME : {games.name}</h1>
</>
);
}

React hook component renders before API call

I need to create a React app which let's you list pokemons and types.
I fetch data from the PokeAPI. Is it a good practice to fetch it from the App component and then pass it to the child components, or is it better to fetch them from the child?
I am fetching it in the main app, I can see the fetch works because I console.log the data, but my component doesn't get it, and because of that I get a props.map is not a function in .
Here is my App.js:
import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.css";
import axios from "axios";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import PokemonList from "./components/PokemonList";
const App = (props) => {
const [pokemons, setPokemons] = useState([]);
const [types, setTypes] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const getPokemons = () => {
const axios = require("axios").default;
axios.get("https://pokeapi.co/api/v2/pokemon").then(function (response) {
console.log("Fetched pokemons");
console.log(response.data.results);
setIsLoading(false);
setPokemons(response.data.results);
});
};
const getTypes = () => {
setIsLoading(true);
const axios = require("axios").default;
axios.get("https://pokeapi.co/api/v2/type").then(function (response) {
console.log("Fetched types");
console.log(response.data.results);
setIsLoading(false);
setTypes(response.data.results);
});
};
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/pokemons" onClick={getPokemons}>
Pokemons
</Link>
</li>
<li>
<Link to="/types">Types</Link>
</li>
</ul>
</nav>
{/* A <Switch> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<Switch>
<Route path="/pokemons">
<Pokemons pokemons={pokemons} />
</Route>
<Route path="/types">
<Types />
</Route>
</Switch>
</div>
</Router>
);
};
function Pokemons(pokemons) {
return <PokemonList props={pokemons} />;
}
function Types(typeList) {
return <h2>TYPES:</h2>;
}
export default App;
Here is my PokemonList.js:
import React from "react";
import { Card } from "semantic-ui-react";
import PokeCard from "./PokeCard";
const Pokemonlist = (props) => {
let content = (
<Card.Group>
{props.map(function (object, i) {
return <PokeCard pokemon={object} key={i} />;
})}
</Card.Group>
);
return content;
};
export default Pokemonlist;
and last here is my PokeCard.js
import { Card, Image } from "semantic-ui-react";
import React from "react";
const PokeCard = (pokemon) => {
let content = (
<Card>
<Card.Content>
<Image floated="right" size="mini" src={pokemon.img} />
<Card.Header>{pokemon.name}</Card.Header>
<Card.Meta>{pokemon.base_experience}</Card.Meta>
<Card.Description>ID: {pokemon.id}</Card.Description>
</Card.Content>
</Card>
);
return content;
};
export default PokeCard;
So the basic idea is:
On the main page you click Pokemons button, which calls the fetch then renders the PokemonList component which basically just renders multiple PokeCard components from the data I fetched.
1, What am I missing here?
2, In my situation when nothing changes do I need to use useEffect?
3, When should I fetch the data, and where?
EDIT: I want to use hooks with zero classes
here is a summary of my answer
it is best to fetch some initial data in parent and then make further requests in child
component if necessary to save network usage
use the useEffect hook to fetch the results before rendering the elements
What you are missing is that you are not using props in pokemon and you should put the get call inside useEffect hook in App component because the child component is rendering before the props is passed to it and this is the reason you are getting undefined error

How to get the react navigator state outside the root react navigator?

This is the thing:
I have a react-native application on mobile, and I'm trying to do some authority check action when the user has left my app and get back. I want to avoid doing an authority check on the login screen and considering the existing application component tree, I want to get the current route for me to use. Now, I'm using 5.x version react-navigation.
const App = () => {
useEffect(() => {
const handleAppStateChange = (): void => {
// Authority check action
};
AppState.addEventListener('change', handleAppStateChange);
}, []);
return (
<Provider store={store}>
<SafeAreaProvider>
<SafeAreaView>
<RootNavigator />
<OverlayActivityIndicator />
<ActionSheet />
</SafeAreaView>
</SafeAreaProvider>
</Provider>
);
}
Now I meet the problem:
I don't know how to do to get the current route outside the root navigator.
My last choice is integrating the current route into the redux store, and that will be making my application more complex.
Any other ideas?
You can refer this for getting navigation outside components
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
//whereas
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}

If-else in React Stateless functional components

I am basically refactoring my React component to support new stateless functional component React functional component. However, I'm confused about the if-else syntax in functional component.
Old code (Working fine): What it does basically is to find if the user is currently logged in. If yes, redirect to Home page else to Landing page. CurrentUser component returns a prop currentUser and checks the current state
import React, {Component} from "react";
import currentUser from "shared/components/CurrentUser";
import Home from "../Home";
import Landing from "../Landing";
class DefaultRouteHandler extends Component {
render() {
if (!this.props.currentUser) {
return (<Landing />);
} else {
return (<Home />);
}
}
}
export default currentUser(DefaultRouteHandler);
New code: Now, how should I check the else condition in case the state of currentUser is false. As you can see right now it will always return the Home page.
import React from "react";
import currentUser from "shared/components/CurrentUser";
import Home from "../Home";
import Landing from "../Landing";
const DefaultRouteHandler = ({currentUser}) => (
<Home />
);
export default DefaultRouteHandler;
Finally in my route.jsx I am calling the aforementioned component
<IndexRoute component={DefaultRouteHandler} />
Let me know if you need more information. I can also paste my CurrentUser component code. However, I don't think it will serve any purpose for this question.
const DefaultRouteHandler = ({currentUser}) => {
if (currentUser) {
return <Home />;
}
return <Landing />;
};
I finally figured out the problem. I have to make 2 changes in my new code:
1) Use ternary operator for if-else
2) Forgot to pass currentUser wrapper in export default in order to access the actual prop value
Following code works:
import React from "react";
import currentUser from "shared/components/CurrentUser";
import Home from "../Home";
import Landing from "../Landing";
const DefaultRouteHandler = ({currentUser}) => (
!currentUser ? <Landing /> : <Home />
);
export default currentUser(DefaultRouteHandler);
thanks #Road for reminding me that I am not passing the prop from currentUser component.
This is called Conditional-Rendering
1) Use simple if-else.
const DefaultRouteHandler = ({currentUser}) => {
if (currentUser) {
return <Home />;
}
return <Landing />;
};
2) Use JavaScript Ternary Operator
const DefaultRouteHandler = ({currentUser}) => (
currentUser ? <Home /> : <Landing />
);
use condition in stateless component for showing more than one imported component.
Show condition wise header in your spa
In App.js file
var path= window.location.pathname; // lets imaging that url is "/home/x"
var pathArray = path.split( '/' );
var loc= pathArray[1];//number of part of url that is changing, here it rerurns x
injectTapEventPlugin();
export default ({ children }) => (
<MuiThemeProvider muiTheme={muiTheme}>
<div className="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--fixed-header">
{loc!=="signin" && path!=="/"?(<DashboardHeader/>):null}
{loc!=="signin" && path!=="/"?(<SideBar/>):null}
{children}
</div>
</MuiThemeProvider>
);

Categories