React Components are re-rendering Infinite times - javascript

All most all components in the router file runs infinitely. Don't know what's the problem.
The ajax request sends infinite GET requests due to this issue.
All the files look like something similar to this.
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { apiCall } from "../handlers/api";
import BasicTable from "../reusable/table";
import { loadFields } from "../store/actions/actionCreators";
const header = [
"Sl.No",
"Item Code",
"Item Name",
"Item Type",
"UoM",
"HSN CODE",
"Specifications",
"Description",
"Brand",
"Price",
];
function Item(props) {
const [fields, setFields] = useState([]);
const [render, setRender] = useState(0);
console.log(fields);
// useEffect(() => {
// props
// .loadFields("/items", 0, 20)
// .then((res) => {
// console.log(res);
// })
// .catch((err) => console.log(err));
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
apiCall("get", "/product", {})
.then((data) => setFields(data.product))
.catch((err) => console.log(err));
const reRender = () => {
setRender(render + 1);
};
return (
<div>
<h1>Items</h1>
<BasicTable reRender={reRender} header={header} body={fields} />
</div>
);
}
const mapStateToProps = (state) => ({
item: state.Items.fields,
});
export default connect(mapStateToProps, { loadFields })(Item);
Routes file:
Here, all the routes are defined.
import React from "react";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
import Home from "./home";
import Supplier from "./supplier";
import Item from "./item";
import Menubar from "./menubar";
import Project from "./project";
import Fields from "./fields";
import User from "./user";
import GeneratePO from "./generatePo";
import PODetails from "./poDetails";
import AddButton from "./addButton";
import AddFields from "./addFields";
import { connect } from "react-redux";
import ViewItems from "../reusable/viewItems";
import AddMaterialForm from "../reusable/addMaterialForm";
import ViewMaterialFormItem from "../reusable/viewMaterialFormItem";
import InventoryHeader from "./inventoryHeader";
import InventoryDetails from "./inventoryDetails";
import AddIntentTable from "../reusable/addIntentTable";
import InventoryIssues from "./inventoryIssues";
import InventoryIndents from "./inventoryIndents";
import ItemForm from "../reusable/itemForm";
import POReport from "../reports/poReport";
function Routers(props) {
return (
<React.Fragment>
<Router>
<header className="width-max navbar">
<Menubar />
<AddButton />
</header>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/users">
<User />
</Route>
<Route exact path="/items">
<Item />
</Route>
<Route exact path="/suppliers">
<Supplier />
</Route>
<Route exact path="/fields">
<Fields />
</Route>
<Route exact path="/projects">
<Project />
</Route>
<Route exact path="/generatepo">
<GeneratePO />
</Route>
<Route exact path="/inventoryheader">
<InventoryHeader />
</Route>
<Route exact path="/inventorydetails">
<InventoryDetails />
</Route>
<Route path="/items/add">
<ItemForm type="POST" />
</Route>
<Route path="/users/add">
<AddFields field="/users" data={{}} type="POST" />
</Route>
<Route path="/suppliers/add">
<AddFields field="/suppliers" data={{}} type="POST" />
</Route>
<Route path="/fields/add">
<AddFields field="/fields" data={{}} type="POST" />
</Route>
<Route path="/projects/add">
<AddFields field="/projects" data={{}} type="POST" />
</Route>
<Route path="/generatepo/add">
<AddMaterialForm field="/generatepo" data={{}} type="POST" />
</Route>
<Route path="/inventoryheader/add">
<AddIntentTable field="/inventoryheader" data={{}} type="POST" />
</Route>
<Route path="/items/edit/:ID">
<ItemForm type="PUT" />
</Route>
<Route path="/users/edit/:ID">
<AddFields field="/users" data={props.user} type="PUT" />
</Route>
<Route path="/suppliers/edit/:ID">
<AddFields field="/suppliers" data={props.supplier} type="PUT" />
</Route>
<Route path="/fields/edit/:ID">
<AddFields field="/fields" data={props.field} type="PUT" />
</Route>
<Route path="/projects/edit/:ID">
<AddFields field="/projects" data={props.project} type="PUT" />
</Route>
<Route exact path="/inventory/edit">
<InventoryHeader />
</Route>
<Route path="/purchase/edit">
<GeneratePO />
</Route>
<Route path="/generatepo/edit/:ID">
<AddMaterialForm
field="/generatepo"
data={props.materialForm}
type="PUT"
/>
</Route>
<Route path="/inventoryheader/edit/:ID">
<AddIntentTable
field="/inventoryheader"
data={props.inventoryTable}
type="PUT"
/>
</Route>
<Route path="/items/view/:ID">
<ViewItems field="/items" data={props.item} />
</Route>
<Route path="/users/view/:ID">
<ViewItems field="/users" data={props.user} />
</Route>
<Route path="/suppliers/view/:ID">
<ViewItems field="/suppliers" data={props.supplier} />
</Route>
<Route path="/fields/view/:ID">
<ViewItems field="/fields" data={props.field} />
</Route>
<Route path="/projects/view/:ID">
<ViewItems field="/projects" data={props.project} />
</Route>
<Route path="/generatepo/view/:ID">
<ViewMaterialFormItem
field="/generatepo"
data={props.materialForm}
/>
</Route>
<Route path="/inventoryheader/view/:ID">
<ViewItems field="/inventoryheader" data={props.inventoryTable} />
</Route>
<Route path="/inventoryissues">
<InventoryIssues />
</Route>
<Route path="/inventoryindents">
<InventoryIndents />
</Route>
<Route path="/podetails">
<PODetails />
</Route>
<Route path="/poreport">
<POReport />
</Route>
</Switch>
</Router>
</React.Fragment>
);
}
const mapStateToProps = (state) => ({
item: state.Items.fields,
field: state.Fields.fields,
project: state.Projects.fields,
supplier: state.Suppliers.fields,
user: state.Users.fields,
materialForm: state.MaterialForm.fields,
inventoryTable: state.InventoryTable.fields,
});
export default connect(mapStateToProps, null)(Routers);
table.js file:
This is a reusable component that most of the files use dynamicly.
import React, { useState, useEffect } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Table from "#material-ui/core/Table";
import TableBody from "#material-ui/core/TableBody";
import TableCell from "#material-ui/core/TableCell";
import TableContainer from "#material-ui/core/TableContainer";
import TableHead from "#material-ui/core/TableHead";
import TableRow from "#material-ui/core/TableRow";
import Paper from "#material-ui/core/Paper";
import Button from "#material-ui/core/Button";
import VisibilityIcon from "#material-ui/icons/Visibility";
import EditIcon from "#material-ui/icons/Edit";
import DeleteIcon from "#material-ui/icons/Delete";
import { BottomScrollListener } from "react-bottom-scroll-listener";
import { connect } from "react-redux";
import { loadFields, removeFields } from "../store/actions/actionCreators";
import { useLocation, useHistory } from "react-router-dom";
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
function BasicTable(props) {
const location = useLocation();
const history = useHistory();
const [items, setItems] = useState([...props.body]);
const [limit, setLimit] = useState(20);
const classes = useStyles();
useEffect(() => {
const interval = setInterval(() => setItems([...props.body]), 500);
return () => {
clearInterval(interval);
};
}, [limit, props.body]);
const handleDelete = (data) => {
props.removeFields(location.pathname, data);
setItems(items.filter((item) => item.id !== data.id));
props.reRender();
};
const handleEdit = (id) => {
history.push(`${location.pathname}/edit/${id}`);
};
const handleView = (id) => {
history.push(`${location.pathname}/view/${id}`);
};
const handleScroll = () => {
// setItems([
// ...items,
// ...props.body.slice(
// limit,
// props.body.length > limit + 20 ? limit + 20 : props.body.length
// ),
// ]);
// setLimit(limit + 20);
};
const tableHeader = props.header.map((item, index) => {
if (index === 0) {
return (
<TableCell key={item}>
<b>{item}</b>
</TableCell>
);
} else {
return (
<TableCell key={item} align="right">
<b>{item}</b>
</TableCell>
);
}
});
const tableContent = items.map((item, index) => {
return (
<TableRow key={index}>
{Object.values(item).map((row, ind) =>
ind === 0 ? (
<TableCell key={ind} component="th" scope="row">
{row}
</TableCell>
) : typeof row === "object" ? (
String(row.name ? row.name : null)
) : (
<TableCell key={ind} align="right">
{location.pathname === "/users" && ind === 3 ? "**********" : row}
</TableCell>
)
)}
{location.pathname === "/inventoryindents" ? (
<Button variant="outlined" color="primary">
Open Indent
</Button>
) : (
<TableCell align="right">
{/* <VisibilityIcon
style={{ margin: "5px", cursor: "pointer" }}
onClick={() => handleView(item.id)}
/> */}
<EditIcon
onClick={() => handleEdit(item.id)}
style={{ margin: "5px", cursor: "pointer" }}
/>
<DeleteIcon
onClick={() => handleDelete(item)}
style={{ margin: "5px", cursor: "pointer" }}
/>
</TableCell>
)}
</TableRow>
);
});
return (
<BottomScrollListener onBottom={() => handleScroll()}>
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
{tableHeader}
<TableCell align="right">
<b>Actions</b>
</TableCell>
</TableRow>
</TableHead>
<TableBody>{tableContent}</TableBody>
</Table>
</TableContainer>
<br />
<br />
<br />
</BottomScrollListener>
);
}
const mapStateToProps = (state) => ({
state,
});
export default connect(mapStateToProps, { loadFields, removeFields })(
BasicTable
);
please help

React components automatically re-render whenever there is a change in their state or props. A simple update of the state, causes all the User Interface (UI) elements to be re-rendered automatically. In your first file, you are making some API call which on success changing the state. Which will tell the react to re-render the component, and again it will do the API and it goes on.
Do all the side effects like API call in useEffect function with proper dependency array.

Related

How can i get some value from other page js?

I have two page: Header.js and Post.js. These pages is joined on main page - Home.js. Post.js has button "Buy". This button creates variable with value 0 or 1. This value is saved on local storage with window.localStorage.setItem(). And I Want to take with value and give to Header.js. But when I do this value isn't updated avere time, when I click "buy"
How can I make this?
window.localStorage.setItem('countcart',count);
const sumCount = async () => {
if(count === 0){
setCount(Math.max(count+1,0));
} else{
setCount(Math.max(count-1,0));
}
};
<Button className={styles.buy} onClick={sumCount} variant="contained" endIcon={<ShoppingCartIcon fontSize="small"/>}><div className={styles.buytext}>Buy</div> </Button>
If you want localStorage to update every time count is changed, you should wrap it with a useEffect:
useEffect(() => {
window.localStorage.setItem('countcart',count);
}, [count])
But, this doesn't auto-update the count value in the other component; to do that with localStorage you'd need to use the https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
But, a better way for the other component to access count would be to declare count as a state in the parent component and pass its state to the Header.js and Post.js components, e.g.:
// App.js
function App() {
const [count, setCount] = useCount(window.localStorage.getItem('count'));
useEffect(() => {
window.localStorage.setItem('countcart',count);
}, [count])
return (
<>
<Header count={count} setCount={setCount} />
<Post count={count} setCount={setCount} />
</>
)
}
import {Routes, Route} from 'react-router-dom';
import Container from '#mui/material/Container';
import { Header } from './components';
import { Home, FullPost, Registration, AddPost, Login, PostsByTag, Account } from './pages';
import { useDispatch, useSelector } from 'react-redux';
import React, { useState } from 'react';
import { fetchAuthMe, selectIsAuth } from './redux/slices/auth';
function App() {
const dispatch = useDispatch();
const [count, setCount] = useState(window.localStorage.getItem('countcart')? 0 :window.localStorage.getItem('countcart'));
const isAuth = useSelector(selectIsAuth);
React.useEffect(()=>{
dispatch(fetchAuthMe());
window.localStorage.setItem('countcart',count);
},[count])
return (
<>
<Header count={count} setCount={setCount}/>
<Container maxWidth="lg">
<Routes>
<Route path="/" element={<Home count={count} setCount={setCount}/>} />
<Route path="/posts/:id" element={<FullPost />} />
<Route path="/tags/:tag" element={<PostsByTag />} />
<Route path="/posts/:id/edit" element={<AddPost />} />
<Route path="/add-post" element={<AddPost />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Registration />} />
<Route path="/account/:id" element={<Account />} />
</Routes>
</Container>
</>
);
}
export default App;
import React from 'react';
import { Rating,IconButton, Button} from '#mui/material';
import clsx from 'clsx';
import {Link, useNavigate} from 'react-router-dom';
import DeleteIcon from '#mui/icons-material/Clear';
import EditIcon from '#mui/icons-material/Edit';
import EyeIcon from '#mui/icons-material/RemoveRedEyeOutlined';
import CommentIcon from '#mui/icons-material/ChatBubbleOutlineOutlined';
import ShoppingCartIcon from '#mui/icons-material/ShoppingCart';
import styles from './Post.module.scss';
// import { UserInfo } from '../UserInfo';
import { PostSkeleton } from './Skeleton';
import { useDispatch } from 'react-redux';
import { fetchRemovePost } from '../../redux/slices/posts';
export const Post = ({
id,
title,
createdAt,
imageUrl,
user,
viewsCount,
commentsCount,
tags,
children,
isFullPost,
isLoading,
isEditable,
count,
setCount,
}) => {
// const [count, setCount] = React.useState(0);
const dispatch = useDispatch();
const navigate = useNavigate();
if (isLoading) {
return <PostSkeleton />;
}
console.log(count);
window.localStorage.setItem('countcart',count);
const sumCount = async () => {
if(count === 0){
setCount(Math.max(count+1,0));
} else{
setCount(Math.max(count-1,0));
}
};
const onClickRemove = () => {
if(window.confirm('Do you sure want to remove post?')){
dispatch(fetchRemovePost(id));
navigate(0);
}
};
return (
<div className={clsx(styles.root, { [styles.rootFull]: isFullPost })}>
{isEditable && (
<div className={styles.editButtons}>
<Link to={`/posts/${id}/edit`}>
<IconButton color="primary">
<EditIcon />
</IconButton>
</Link>
<IconButton onClick={onClickRemove} color="secondary">
<DeleteIcon />
</IconButton>
</div>
)}
{imageUrl && (
<img
className={clsx(styles.image, { [styles.imageFull]: isFullPost })}
src={imageUrl}
alt={title}
/>
)}
<div className={styles.wrapper}>
<div className={styles.indention}>
<h2 className={clsx(styles.title, { [styles.titleFull]: isFullPost })}>
{isFullPost ? title : <Link to={`/posts/${id}`}>{title}</Link>}
</h2>
<div className={styles.ratingprice}>
<Rating
name="size-small"
value={2.5}
size="small"
precision={0.5}
readOnly
/>
<div className={styles.review}>12 отзывов</div>
</div>
<div className={styles.price}>1150 руб.</div>
{children && <div className={styles.content}>{children}</div>}
<div className={styles.postDetails}>
<ul className={styles.postDetails}>
<li>
<EyeIcon />
<span>{viewsCount}</span>
</li>
<li>
<CommentIcon />
<span>{commentsCount}</span>
</li>
</ul>
<Button className={styles.buy} onClick={sumCount} variant="contained" endIcon={<ShoppingCartIcon fontSize="small"/>}><div className={styles.buytext}>Купить</div> </Button>
</div>
</div>
</div>
</div>
);
};

How to use params.(key) to get specific data for each route?

I want to build a car list app that will show a list of cars, and when I click on a car's details button, it will route it to another component/page that will show the details of cars.
I can get the key (vin for me) but I want to get the details of the car for each key(vin) on this page.It's like http://localhost:3000/cars/WAUZZZ4G6FN052847= key. So when a car's key will show up, all details will come due to their key number. Thank you.
index.html
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter, Routes, Route, Outlet, } from "react-router-dom";
import DummyComponent from "./components/DummyComponent";
import CarDetails from "./pages/CarDetails";
import { Home } from "#mui/icons-material";
import Cars from "./pages/Cars";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/cars" element={<Outlet />} >
<Route path="list" element={<Cars />} />
<Route path=":vin" element={<CarDetails />} />
</Route>
<Route path="Home" element={<Home />} />
<Route path="DummyComponent" element={<DummyComponent />} />
</Routes>
</BrowserRouter>
);
Cars.js
import axios from "axios";
import React, { useEffect, useState } from "react";
import List from '#mui/material/List';
import ListItem from '#mui/material/ListItem';
import ListItemButton from '#mui/material/ListItemButton';
import ListItemText from '#mui/material/ListItemText';
import { Link } from "react-router-dom";
const App = () => {
const [cars, setCars] = useState([])
const getCarData = async () => {
try {
const data = await axios.get("https://react-challenge-api.azurewebsites.net/vehicles")
setCars(data.data)
}
catch (e) {
console.log(e)
}
}
useEffect(() => {
getCarData()
}, [])
return (
<div className="App">
<List sx={{ width: '100%', maxWidth: 600, bgcolor: 'background.paper' }}>
{cars.map((car) => (
<ListItemButton key={car.vin}>
<ListItem
key={car.vin}
disableGutters
secondaryAction={
<ListItemButton >
<Link to={`/cars/${car.vin}`}>details</Link>
</ListItemButton>
}
>
<ListItemText key={car.vin} primary={car.model_variant} />
</ListItem>
</ListItemButton>
))
}
</List >
</div >
);
};
export default App;
CarDetails.js (I want to show each data in this component, I used params but I don't know how to get data due to params.
import { useParams } from "react-router-dom";
const CarDetails = () => {
let params = useParams();
return (
<>
<h1>car</h1>
<ul>
this is your {params.vin}
</ul>
</>
)
}
export default CarDetails;
I would suggest moving the car data fetching into a layout route and pass the cars state down in an Outlet context.
Example:
Cars - Handles fetching the car data and passes the cars state along to nested routes via the Outlet component's context.
const Cars = () => {
const [cars, setCars] = useState([]);
const getCarData = async () => {
try {
const data = await axios.get(
"https://react-challenge-api.azurewebsites.net/vehicles"
);
setCars(data.data);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
getCarData();
}, []);
return <Outlet context={{ cars }} />;
};
CarList - Reads the cars state from the Outlet context and renders the list.
const CarList = () => {
const { cars } = useOutletContext();
return (
<List sx={{ width: "100%", maxWidth: 600, bgcolor: "background.paper" }}>
{cars.map((car) => (
<ListItemButton key={car.vin}>
<ListItem
key={car.vin}
disableGutters
secondaryAction={
<ListItemButton>
<Link to={`/cars/${car.vin}`}>details</Link>
</ListItemButton>
}
>
<ListItemText key={car.vin} primary={car.model_variant} />
</ListItem>
</ListItemButton>
))}
</List>
);
};
CarDetails - Takes the vin route path param and the cars array from the outlet context and searches for a matching car object.
const CarDetails = () => {
const { vin } = useParams();
const { cars } = useOutletContext();
const car = cars.find((car) => car.vin === vin);
if (!car) {
return "No car matches this VIN";
}
return (
<>
<h1>{car.model_variant}</h1>
<ul>
<li>Body: {car.body_type}</li>
<li>Doors: {car.doors}</li>
<li>Fuel: {car.fuel_type}</li>
<li>VIN: {car.vin}</li>
<li>Registration #: {car.regno}</li>
<li>Transmission: {car.transmission_type}</li>
</ul>
</>
);
};
Routes
<Routes>
<Route path="/" element={<App />} />
<Route path="/cars" element={<Cars />}> // <-- layout route renders outlet
<Route path="list" element={<CarList />} />
<Route path=":vin" element={<CarDetails />} />
</Route>
<Route path="Home" element={<Home />} />
<Route path="DummyComponent" element={<DummyComponent />} />
</Routes>

Unable to display information after using React Routes with "Lifting state up"

I'm learning React Router from textbook and facing a problem when changing from Switch to Routes with the new function of Lifting State Up. I can't display text after clicking the 'News' link. Here shows the code:
/* index.jsx */
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
root.render(
<HashRouter>
<Routes>
<Route path='/home' element={<Home />} />
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/news' element={<News />} />
</Routes>
</HashRouter>,
);
/* About.jsx */ <- good code
const About = () => (
<div>
<Menu />
<h1>這裡是關於我們 This is about us.</h1>
</div>
);
export default About;
/* News.jsx */ <- Problem comes in.
const News = () => {
const [news] = useState([
{ id: 1, name: 'News1', describe: 'A wins!' },
{ id: 2, name: 'News2', describe: 'B wins!' },
]);
return (
<div>
<Menu />
<Routes>
<Route path='/news/*' element={<><p>hello!</p><NewsList
news={news} /></>} />
<Route path='newsReader/:id' element={<><NewsReader news ={news} /></>} />
</Routes>
</div>
);
};
export default News;
It should be like this:
I believe the problem lies in <Route path> but can't find the solution.
The code <NewsList> and <NewsReader> is here:
const NewsList = props => (
<ul>
{
props.news.map(theNews => (
<li key = {theNews.id}>
<Link to = {`/news/newsReader/${theNews.id}`}>
{theNews.name}</Link>
</li>
))
}
</ul>
);
export default NewsList;
const NewsReader = (props) => {
const {id:targetNewsId} = useParams();
const targetNews = props.news.find(theNews => (
String(theNews.id) === String(targetNewsId)
));
return(
<div>
<h1>你正在閱讀 You are now reading {targetNews.name}</h1>
<p>{targetNews.describe}</p>
</div>
)
}
export default NewsReader;
Thank you very much for your answer.
For nested routes, you need to follow the below approach
// index.js -> /news/*
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
root.render(
<HashRouter>
<Routes>
<Route path='/home' element={<Home />} />
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/news/*' element={<News />} /> // here you need to /*
</Routes>
</HashRouter>,
);
For the News.jsx you need to have relative path to /news like below
const News = () => {
const [news] = useState([
{ id: 1, name: 'News1', describe: 'A wins!' },
{ id: 2, name: 'News2', describe: 'B wins!' },
]);
return (
<div>
<Menu />
<Routes>
<Route path='/*' element={<><p>hello!</p><NewsList
news={news} /></>} />
<Route path='newsReader/:id' element={<><NewsReader news ={news} /></>} />
</Routes>
</div>
);
};
export default News;

React Router V6 nested Routes with i18n

I have a question about React Router V6 nested with i18n.
This is my first multi-language service.
const MainPage:React.FC = () => {
const lang = i18n.language;
return (
<>
<Wrapper>
<Routes>
{/* Main */}
<Route path={`/`} element={<Home />}>
<Route path={`${lang}`}>
<Route path={`service`}>
<Route path={'slack'} element={<Slack />} />
</Route>
</Route>
{/* <Route path={`service/dooray`}element={<Dooray />} /> */}
{/* <Route path={`contact`} element={<Contact />} /> */}
{/* <Route path={`app/sign-in`} element={<SignIn />} /> */}
{/* <Route path={`app/sign-up`} element={<SignUp />} /> */}
{/* <Route path={`app/mail-code`} element={<MailCode />} /> */}
{/* <Route path={`app/password/reset`} element={<PwdReset />} /> */}
{/* <Route path={`policies/privac`} element={<Privacy />} /> */}
{/* <Route path={`policies/terms`} element={<Terms />} /> */}
</Route>
{/* <Route path={`*`} element={<>NOT FOUND</>} /> */}
{/* test */}
</Routes>
</Wrapper>
<ParentModal />
</>
If I enter localhost:3000/en, there is an error 'This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.'
How can I fix it..
I want /en => go to english page, /jp => go to japanese page
const MainPage:React.FC =() => {
...
<Route path={`/`} element={<Home />}>
<Route path={`/${lang}/*`}>
<Route path={`service`}>
<Route path="slack" element={<Slack />} />
</Route>
</Route>
</Route>
}
const Home:React.FC = () => {
return (
<>
... UI, JSX
<Outlet />
</>
)
}
I add a <Outlet />. But if I entered '/ko/service/slack', render <Home /> now
<Route path={`/`} element={<Home />}>
<Route path="service">
<Route path="slack" element={<Slack />} />
<Route path="dooray" element={<Dooray />} />
</Route>
</Route>
nested-routes doesn't work.. :(
I had the exact same useCase (localize react router v6) and came up with the following LangRouter repository link:
const LangRouter = () => {
const { i18n } = useTranslation(),
{ pathname, search, hash } = useLocation(),
navigate = useNavigate(),
availableLocales = ['en', 'ar'],
defaultLocale = (
getDefaultLanguage() === 'en' || getDefaultLanguage() === 'ar' ? getDefaultLanguage() : 'en'
) as string,
pathnameLocale = pathname.substring(1, 3).toLowerCase(),
[locale, setLocale] = useState(defaultLocale),
loaderTimerRef = useRef<any>(),
[isLoading, setIsLoading] = useState(true);
useEffect(() => {
loaderTimerRef.current = setTimeout(() => {
setIsLoading(false);
clearTimeout(loaderTimerRef.current);
}, 300);
}, []);
useEffect(() => {
if (availableLocales.includes(pathnameLocale)) {
updateLocale(pathnameLocale);
} else if (pathname === '/') {
updateLocale(defaultLocale);
}
// eslint-disable-next-line
}, [pathname]);
useEffect(() => {
let lang = defaultLocale;
if (availableLocales.includes(pathnameLocale)) {
lang = pathnameLocale;
setLanguageHandler(lang);
} else if (pathname === '/') {
setLanguageHandler(lang);
}
// eslint-disable-next-line
}, [locale]);
const setLanguageHandler = (lang: string) => {
if (lang === 'en') {
i18n.changeLanguage('en-US');
} else {
i18n.changeLanguage('ar-SA');
}
};
const updateLocale = (newLocale: string) => {
const newPath = `/${newLocale}` + pathname.substring(3);
if (locale !== newLocale) {
if (newPath === `/${newLocale}/` || newPath === `/${newLocale}` || pathname === '/') {
navigate(getHomePageUrl(newLocale));
} else {
navigate(`${newPath}${hash}${search}`);
}
setLocale(newLocale);
} else if (newPath === `/${newLocale}/` || newPath === `/${newLocale}` || pathname === '/') {
if (isAuthenticated()) {
navigate(getHomePageUrl(newLocale));
} else {
navigate(getLoginPageUrl(newLocale));
}
}
};
if (isLoading) {
return (
<div className="loader-wrapper">
<LoadingIcon />
</div>
);
}
return (
<LocaleContext.Provider value={{ locale, setLocale: updateLocale }}>
<Routes>
<Route path={`/${locale}`} element={<App />}>
{publicRoutes.map((el, i) => (
<Route
key={i}
path={el.path(locale)}
element={
<PublicRouteGuard
restricted={el.restricted}
redirect={el.redirect ? el.redirect(locale) : undefined}
>
{el.element}
</PublicRouteGuard>
}
/>
))}
{privateRoutes.map((el, i) => (
<Route
key={i}
path={el.path(locale)}
element={
el.permissions ? (
<RestrictedRouteGuard requiredPermissions={el.permissions}>
{el.element}
</RestrictedRouteGuard>
) : (
<PrivateRouteGuard>{el.element}</PrivateRouteGuard>
)
}
>
{el.children &&
el.children.map((innerEl, innerI) => (
<Route key={innerI} path={innerEl.path(locale)} element={innerEl.element} />
))}
</Route>
))}
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</LocaleContext.Provider>
);
};
export default LangRouter;
Issue
The error 'This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.' means the parent route isn't rendering an Outlet component for the nested routes to be rendered into. The route rendering the Home component doesn't appear to be rendering an Outlet.
Solution
Update the Home component to render an Outlet. Note that Route components without an element prop will render an Outlet by default.
Example:
import { Outlet } from 'react-router-dom';
const Home = () => {
...
return (
<>
... home page UI/JSX ...
<Outlet />
</>
);
};
...
const MainPage:React.FC = () => {
const lang = i18n.language;
return (
<>
<Wrapper>
<Routes>
{/* Main */}
<Route path="/" element={<Home />}>
<Route path={lang}> // <-- renders Outlet by default
<Route path="service"> // <-- renders Outlet by default
<Route path="slack" element={<Slack />} />
</Route>
</Route>
...
</Route>
...
{/* test */}
</Routes>
</Wrapper>
<ParentModal />
</>
);
};
Update
If the Home and Slack components are separate and independent, then move the Home component into an index route and simplify the routing to the Slack component.
<Routes>
<Route path="/">
<Route index element={<Home />} />
<Route path={`${lang}/service/slack`} element={<Slack />} />
</Route>
</Routes>

Protected route not working correctly with React and Firebase

I'm building a small app with firebase and react and currently working on implementing the authentication. I've set the onAuthStateChanged in my app component as a side effect and whenever user is logged in it should be redirected to a desired component from ProtectedRoute.
This works correctly but unfortunately when refreshing the page the ProtectedRoute is not rendering correct component and is just firing redirection.
I get what is happening: on refresh user is empty and only after then it change so I would expect to see a screen flicker and a proper redirection.
Could you please look at below code and maybe tell me how to fix this behavior?
App component:
const App = () => {
const [authUser, setAuthUser] = useState<firebase.User | null>(null);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
authUser ? setAuthUser(authUser) : setAuthUser(null);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute exact path={ROUTES.HOME} component={Home} />
<ProtectedRoute exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</div>
</Router>
</AuthUserContext.Provider>
);
};
Protected Route:
interface Props extends RouteProps {
component?: any;
children?: any;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
...rest
}) => {
const authUser = useContext(AuthUserContext);
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};
Found the fix. Had to add the flag checking for user authentication status (default value of that flag is set to true). Flag needs to be passed to ProtectedRoute as prop and if is True then render some loading component:
App component:
const App = () => {
const [authUser, setAuthUser] = useState(false);
const [authPending, setAuthPending] = useState(true);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
setAuthUser(!!authUser);
setAuthPending(false);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Switch>
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.HOME}
component={Home}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.ACCOUNT}
component={Account}
/>
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</Switch>
</div>
</Router>
</AuthUserContext.Provider>
);
};
ProtectedRoute:
interface Props extends RouteProps {
component?: any;
children?: any;
pendingAuth: boolean;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
pendingAuth,
...rest
}) => {
const authUser = useContext(AuthUserContext);
if (pendingAuth) {
return <div>Authenticating</div>;
}
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};

Categories

Resources