Hi I'm currenlty doing a side project for my portfolio and was wondering how did this error happen. It's a navbar component in react, with Link to other pages and button to set an active theme. Any Idea what is causing the infinite re-render? Thank you.
import React, {useEffect} from 'react'
import AppBar from '#mui/material/AppBar';
import Box from '#mui/material/Box'
import { Button } from '#mui/material';
import { Link } from 'react-router-dom';
const MenuBar = () => {
const [activeItem, setActiveItem] = React.useState('')
const handleItemClick = (e, {name}) => {
setActiveItem(name)
}
useEffect(() => {
const pathname = window.location.pathname
const path = pathname === '/' ? 'home' : pathname.substring(1)
setActiveItem(path)
}, [])
return (
<div>
<AppBar color='primary' position={'static'}>
<Box className='flex items-center justify-between'>
<Link to={'/'} onClick={setActiveItem('home')}>
<Button variant='text' sx={{ bgcolor: activeItem === 'home' ? '#FDFFA9' : '#FFD365'}}>Home</Button>
</Link>
<Box>
<Link to={'/login'} onClick={setActiveItem('login')}>
<Button variant='text'>Login</Button>
</Link>
<Link to={'/register'} onClick={setActiveItem('login')}>
<Button variant='text'>Register</Button>
</Link>
</Box>
</Box>
</AppBar>
</div>
)
}
export default MenuBar
I think the problem is with the way you assign onClick handlers
onClick={setActiveItem('home')}
during rendering you actually invoke the action, not assigning the callback, so
render -> setActiveItem -> render -> ..
please try to assign as the fn value instead:
onClick={() => setActiveItem('home')}
Related
I have researched and tried to implement some of the solutions provided but I failed when trying to achieve this. I was able to make my dropdown menu and click so each submenu will open and close when its parent is clicked. I would like to have an opened submenu to be closed when a different menu is clicked, so I donĀ“t have all of them stacked at the menu bar. Could someone point out how can I achieve this? Thank you for helping me.
Menu.js
import React from 'react'
import MenuItem from '../MenuItem';
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';
const Menu = () => {
return (
<C.Container>
<C.MenuArea>
{SidebarData.map((item, index) => {
return <MenuItem item={item} key={index} />;
})}
</C.MenuArea>
</C.Container>
)
};
export default Menu
MenuItem.js
import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import * as C from './styles';
const MenuItem = ({ item }) => {
const [opened, setOpened] = useState(false);
const showSubnav = () => setOpened(!opened);
return (
<C.NavUnlisted>
<NavLink to={item.path} onClick={item.subNav && showSubnav} activeClassName='current' exact={item.path === '/' ? true : false} >
<C.SidebarLink>
<div>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</div>
<div>
{item.subNav && opened
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</C.SidebarLink>
</NavLink>
{opened &&
item.subNav.map((item, index) => {
return (
<NavLink to={item.path} key={index} activeClassName='current' >
<C.DropdownLink>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</C.DropdownLink>
</NavLink>
);
})}
</C.NavUnlisted>
);
};
export default MenuItem;
I was able to find a solution. I added an ID for each Menu so I could change the state based upon it. I had all the menu in a single component. It didn't seem to be necessary to have a separate component for the Menu Item.
Here is my code:
import React, { useState } from 'react'
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';
import { NavLink } from "react-router-dom";
const Menu = () => {
const [open, setOpen] = useState('');
const toggle = (id) => setOpen(id);
return (
<C.Container>
<C.MenuArea>
{SidebarData.map((item, index) => (
<C.NavUnlisted key={index}>
<NavLink to={item.path} onClick={() => toggle(item.navId)} activeClassName='current' exact={item.path === '/' ? true : false}>
<C.SidebarLink>
<div>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</div>
</C.SidebarLink>
</NavLink>
{open === item.navId && (
<div>
{item.subNav.map((item, index) => (
<NavLink to={item.path} key={index} activeClassName='current' >
<C.DropdownLink>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</C.DropdownLink>
</NavLink>
))}
</div>
)}
</C.NavUnlisted>
))}
</C.MenuArea>
</C.Container>
)
};
export default Menu
Try to close the menu when you click outside your menu component, if it's a solution you're interested in you can learn more about how to achieve this in react there :
https://stackoverflow.com/a/42234988/16956436
An elegant way to handle this would be to keep track of the currently opened submenu in the Menu component and displaying/hiding the submenus depending on a prop passed down from the parent component.
import React from 'react'
import MenuItem from '../MenuItem';
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';
const Menu = () => {
const [currentlyOpen, setCurrentlyOpen] = useState(null);
return (
<C.Container>
<C.MenuArea>
{SidebarData.map((item, index) => {
return <MenuItem item={item} key={index} isOpen={index === currentlyOpen} onClick={() => setCurrentlyOpen(index)} />;
})}
</C.MenuArea>
</C.Container>
)
};
export default Menu
You would then call handleClick with the respective index in MenuItem.js.
import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import * as C from './styles';
const MenuItem = ({ item, onClick: handleClick }) => {
const [opened, setOpened] = useState(false);
const showSubnav = () => setOpened(!opened);
return (
<C.NavUnlisted>
<NavLink to={item.path} onClick={item.subNav && handleClick} activeClassName='current' exact={item.path === '/' ? true : false} >
<C.SidebarLink>
<div>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</div>
<div>
{item.subNav && opened
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</C.SidebarLink>
</NavLink>
{opened &&
item.subNav.map((item, index) => {
return (
<NavLink to={item.path} key={index} activeClassName='current' >
<C.DropdownLink>
{item.icon}
<C.SidebarLabel>{item.title}</C.SidebarLabel>
</C.DropdownLink>
</NavLink>
);
})}
</C.NavUnlisted>
);
};
export default MenuItem;
I'm trying to create a notifications area. I show a notification icon, and when the user clicks on it, I show the list of notifications.
Here's a codesandbox
The problem is that I can't mix it with ClickAwayListener.
When I use ClickAwayListener it's not shown at all.
How should I fix this?
HeaderAction.js
import Tooltip from "#material-ui/core/Tooltip";
import Fade from "#material-ui/core/Fade";
import Collapse from "#material-ui/core/Collapse";
import React, { useState } from "react";
import ClickAwayListener from "#material-ui/core/ClickAwayListener";
import Icon from "#material-ui/core/Icon";
const HeaderAction = ({ icon, title, component }) => {
const Component = component || (() => <div>NA</div>);
const [showComponent, setShowComponent] = useState(false);
const handleClick = () => {
setShowComponent(!showComponent);
};
return (
<>
<Tooltip title={title || ""}>
<div onClick={() => handleClick()}>
<Icon>{icon}</Icon>
</div>
</Tooltip>
{/* This part is not working */}
{/* <ClickAwayListener onClickAway={() => setShowComponent(false)}>
<div>
<Fade in={showComponent}>
<div>
<Component />
</div>
</Fade>
</div>
</ClickAwayListener> */}
<Fade in={showComponent}>
<div>
<Component />
</div>
</Fade>
</>
);
};
export { HeaderAction };
When you click the icon button, handleClick is called and the showComponent state is set to true, but then onClickAway from ClickAwayListener is also called and set the showComponent state to false again. The fix is simple, don't let the onClickAway handler execute by stopping the propagation after clicking the button:
<div
onClick={(e) => {
e.stopPropagation();
handleClick();
}}
>
I've implemented infinite scroll in React app, however, I get Warning: Encountered two children with the same key <...>. Keys should be unique so that components maintain their identity across updates. <...>.
If I replace key={movie.id} by key={i}, infinite scroll stops working.
This is the code of my component:
import{ useContext, useRef, useEffect } from "react";
import { Card, Grid, CardActionArea, CardMedia } from '#material-ui/core';
import FavoriteBorderIcon from '#material-ui/icons/FavoriteBorder';
import { MoviesContext } from "../../services/context";
import { Movie } from "../../services/movies.service";
import '../../App.scss';
import './Catalog.scss';
import noImage from '../../images/no-image-available.png';
import loadingSpinner from '../../images/loading-spinner.gif';
import { NavLink } from 'react-router-dom';
import useIntersectionObserver from '../../services/useIntersectionObserver';
import { fetchMovies } from "../../services/movies.service";
const posterBaseUrl = "https://image.tmdb.org/t/p/w300";
const CatalogCards = () => {
const { movies, updateMovies, searchedMovie, moviesPage, setMoviesPage, setSelectedMovie, setIsMoviePageFirstTimeOpened } = useContext(MoviesContext);
const loadingRef = useRef<HTMLDivElement | null>(null);
const entry = useIntersectionObserver(loadingRef, {})
const isVisible = !!entry?.isIntersecting;
const SetSelectedMovieId = (id: number) => {
setIsMoviePageFirstTimeOpened(true);
setSelectedMovie(id);
}
useEffect (
() => {
if ( isVisible ) {
setMoviesPage(moviesPage+1);
fetchMovies(String(moviesPage))
.then(nextPage => {
updateMovies((movies: Movie[]) => movies.concat(nextPage));
})
.catch(() => updateMovies([]))
}
},
[isVisible]
);
return (
<div >
<Grid container spacing={1} className="container-content">
{
movies.length > 0
?
movies.map((movie, i) => (
<Grid item key={movie.id}>
<NavLink to={'/movie/' + movie.id}>
<Card className="card-list" onClick={() => SetSelectedMovieId(movie.id)} >
<CardActionArea>
<CardMedia
component="img"
alt={"Poster of " + movie.title}
image={movie.poster_path ? posterBaseUrl + movie.poster_path : noImage}
title={movie.title}
/>
</CardActionArea>
</Card>
</NavLink>
</Grid>
))
:
searchedMovie ?
<div className="">Try a different phrase...</div>
:
<CardMedia
component="img"
image={loadingSpinner}
className="loading-spinner"
/>
}
</Grid>
<div ref={loadingRef}>...</div>
</div>
);
}
export default CatalogCards;
fetchMovies() method fetches info about movies from an API.
useIntersectionObserver is a custom hook that helps to check if ref={loadingRef} appears in the screen and more movies should be fetched.
How can I solve it?
Thanks!
I have a page ("/paymentsucess"). In this page, I am using the react-countdown-circle-timer component (https://github.com/vydimitrov/react-countdown-circle-timer#props-for-both-reactreact-native). Upon reaching this page ("/paymentsuccess"), countdown beings. After countdown reaches zero, I want to redirect user back to home page ("/").
To achieve this, for the CountdownCircleTimer component, when onComplete, I set the state initialMount to false.
<CountdownCircleTimer
onComplete={() => {
setInitialMount(false);
return [true, 1500];
}}
isPlaying
duration={2}
colors={[
["#004777", 0.33],
["#F7B801", 0.33],
["#A30000", 0.33],
]}
>
{renderTime}
</CountdownCircleTimer>
Given that initialMount is changed, my useEffect (in the paymentsuccess component) will kick in and redirect users to "/". I am using history.push("/") here.
useEffect(() => {
if (initialMount !== true) {
console.log("On change in status,");
console.log(initialMount);
history.push("/");
}
}, [initialMount, history]);
I was able to redirect user back to "/" successfully. But upon reaching "/", they got redirected back to /paymentsuccess again. And then from /paymentsuccess it goes back to / and then the loops continue. Infinite loop.
Any idea what I am doing wrong here? :( I need to stop this loop and lands user back to / and stops there.
I am using Router from react-router-dom and createBrowserHistory from history.
Below is the full code for my paymentsuccess component
import React, { useEffect, useStatem} from "react";
import { useHistory } from "react-router-dom";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import { CountdownCircleTimer } from "react-countdown-circle-timer";
function PaymentSuccess() {
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
}));
const classes = useStyles();
const history = useHistory();
const [initialMount, setInitialMount] = useState(true);
useEffect(() => {
console.log("On component mount, status is");
console.log(initialMount);
}, []);
useEffect(() => {
return () => {
console.log("On component unmount, status is");
setInitialMount(true);
console.log(initialMount);
};
}, []);
useEffect(() => {
if (initialMount !== true) {
console.log("On change in status,");
console.log(initialMount);
history.push("/");
}
}, [initialMount, history]);
const renderTime = ({ remainingTime }) => {
if (remainingTime === 0) {
return <div className="timer">Starting...</div>;
}
return (
<div className="timer">
<div className="value">{remainingTime}</div>
</div>
);
};
return (
<div className={classes.root}>
<Grid container spacing={0}>
<Grid item xs={6} sm={6}>
<Paper
className="paymentSuccessLeftPaper"
variant="outlined"
square
></Paper>
</Grid>
<Grid item xs={6} sm={6}>
<Paper className="paymentSuccessRightPaper" variant="outlined" square>
<h1>Payment Success</h1>
<CountdownCircleTimer
onComplete={() => {
setInitialMount(false);
return [true, 1500];
}}
isPlaying
duration={2}
colors={[
["#004777", 0.33],
["#F7B801", 0.33],
["#A30000", 0.33],
]}
>
{renderTime}
</CountdownCircleTimer>
</Paper>
</Grid>
</Grid>
</div>
);
}
export default PaymentSuccess;
I have checked my "/" page and I don't think there is any logic there redirecting to "/paymentsuccess". The page ("/") code is as below.
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Button from "#material-ui/core/Button";
import Link from "#material-ui/core/Link";
function LandingPage() {
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
minHeight: "100%",
padding: theme.spacing(0),
textAlign: "center",
color: theme.palette.text.secondary,
},
}));
const classes = useStyles();
return (
<div className={classes.root}>
<Grid container spacing={0}>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips</h1>
<Button variant="contained" color="primary">
<Link href="/photo10">SELECT</Link>
</Button>
</Paper>
</Grid>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips and GIF</h1>
<Button variant="contained" color="primary">
<Link href="/photogif12">
SELECT
</Link>
</Button>
</Paper>
</Grid>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips and Boomerang</h1>
<Button variant="contained" color="primary">
<Link href="/photoboomerang12">
SELECT
</Link>
</Button>
</Paper>
</Grid>
</Grid>
</div>
);
}
export default LandingPage;
Thank you all in advance! Appreciate all the help and advise
UPDATE
Below is Router code
import React from "react";
import { Router, Switch } from "react-router-dom";
import LandingPage from "./components/LandingPage";
import Photo10 from "./components/Photo10";
import PhotoGIF12 from "./components/PhotoGIF12";
import PhotoBoomerang12 from "./components/PhotoBoomerang12";
import PaymentSuccess from "./components/PaymentSuccess";
import DynamicLayout from "./router/DynamicLayout";
import { history } from "./helpers/history";
const App = () => {
return (
<Router history={history}>
<div className="App">
<Switch>
<DynamicLayout
exact
path="/"
component={LandingPage}
layout="LANDING_NAV"
/>
<DynamicLayout
exact
path="/photo10"
component={Photo10}
layout="PHOTO10_PAGE"
/>
<DynamicLayout
exact
path="/photogif12"
component={PhotoGIF12}
layout="PHOTOGIF12_PAGE"
/>
<DynamicLayout
exact
path="/photoboomerang12"
component={PhotoBoomerang12}
layout="PHOTOBOOMERANG12_PAGE"
/>
<DynamicLayout
exact
path="/paymentsuccess"
component={PaymentSuccess}
layout="PAYMENTSUCCESS_PAGE"
/>
</Switch>
</div>
</Router>
);
};
export default App;
Below is the code for the DynamicLayout component
import React from "react";
const DynamicLayout = (props) => {
const { component: RoutedComponent, layout } = props;
const actualRouteComponent = <RoutedComponent {...props} />;
switch (layout) {
case "LANDING_NAV": {
return <>{actualRouteComponent}</>;
}
case "PHOTO10_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PHOTOGIF12_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PHOTOBOOMERANG12_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PAYMENTSUCCESS_PAGE": {
return <>{actualRouteComponent}</>;
}
default: {
return (
<>
<h2>Default Nav</h2>
{actualRouteComponent}
</>
);
}
}
};
export default DynamicLayout;
This problem occurs when you changes value of variable in useEffect and that variable also added in useEffect dependency array, So when ever your useEffect change the value due to presence of that variable in dependency array, it again called the useEffect thus a infinite loop occurs
so just remove 'history' variable from your dependency array
i'm new to react(hooks typescript) world, here i have an icon when user clicks that it should go to '/menu' and when it is again clicked it should go to previous page, and if again clicked it should go to '/menu' so basically it should work like toggle.
am i doing this wrong :
import { useHistory } from "react-router-dom";
const [menu, setMenu] = useState(false);
const history = useHistory();
<Box onClick={() => setMenu(!menu)}>
{menu ? (
<Link to={history.goBack}>
<MenuIcon fontSize="large" />
</Link>
) : (
<Link to="/menu">
<MenuIcon fontSize="large" />
</Link>
)}
</Box>
If I'm not wrong, Link can not be used this way. It expects the route not the function. How about using a div and placing onClick handler where you can navigate back through history.goBack()?
The to property expect a string as value (path). if you don't know what the previous route is, I recommend you to create a context to store the previous route value:
const RouterContext = React.createContext();
const RouterProvider = ({children}) => {
const location = useLocation()
const [route, setRoute] = useState({
to: location.pathname,
from: location.pathname
});
useEffect(()=> {
setRoute((prev)=> ({to: location.pathname, from: prev.to}) )
}, [location]);
return <RouterContext.Provider value={route}>
{children}
</RouterContext.Provider>
}
then:
const route = useContext(RouterContext);
<Box onClick={() => setMenu(!menu)}>
<Link to={menu ? route.from : "/menu" }>
<MenuIcon fontSize="large" />
</Link>
</Box>
Don't forget to add RouterProvider under Router component:
<Router>
<RouterProvider>
...
</RouterProvider>
</Router>
Working example (About component has the go back option)