Conditional Redirect on props status - reactjs

I want to redirect the page if the length of props.fields is zero. But the problem it is getting rendered twice so when the it is empty it gets redirected and doesn't wait for the second time.
I am new to React, it would be great if someone could help me.I have spent hours on fixing this.
import React, { Component, useEffect, useState } from 'react'
import Field from './field'
import {Redirect} from 'react-router-dom'
export default function FieldList(props) {
const [redirect,setRedirect] = useState(false);
useEffect(()=>{
if(props.fields.length===0) {
setRedirect(true)
}
else
setRedirect(false)
},[props.fields])
return (
<div>
<h1 style={{textAlign:"center", margin:"20px 0"}}>{props.text}</h1>
<div className='field-list'>
{props.fields.map((name) => (
<Field name={name} />
))}
</div>
{redirect && <Redirect to={'/'} />}
</div>
)
}

you can instead of Redirect either use a custom browser history or use react-router-dom's useHistory hook
to use useHistory hook
import {useHistory} from 'react-router-dom'
and then at theuseEffect
useEffect(()=>{
if(props.fields.length===0) {
useHistory().push('/')
}
},[props?.fields]
)
if you don't want to use that, here is what you can do to create a custom browser history
first create a file and call it History.js
add this line of code to it
import { createBrowserHistory } from "history";
export default createBrowserHistory();
and then import it to the file
import history from "FILE_PATH"
useEffect(()=>{
if(props.fields.length===0) {
history.push('/')
}
}
)

You should change your jsx to
<div>
<h1 style={{textAlign:"center", margin:"20px 0"}}>{props.text}</h1>
<div className='field-list'>
{props.fields.map((name) => (
<Field name={name} />
))}
</div>
{redirect ? <Redirect to={'/'} /> : null}
</div>

Related

React & Typescript Issue: trigger elements with InsertionObserver using props and manage them in other component

Small premise: I'm not a great Typescript expert
Hi everyone, I'm working on my personal site, I decided to develop it in Typescript to learn the language.
My component tree is composed, as usual, of App.tsx which render the sub-components, in this case Navbar.jsx and Home.jsx.
Below is the App.jsx code:
import './App.css';
import { BrowserRouter as Router, useRoutes } from 'react-router-dom';
import Home from './components/Home';
import Navbar from './components/Navbar';
import { useState } from 'react';
function App(){
const [navbarScroll,setNavbarScrool]=useState(Object)
const handleLocationChange = (navbarScroll : boolean) => {
setNavbarScrool(navbarScroll)
return navbarScroll
}
const AppRoutes = () => {
let routes = useRoutes([
{ path: "/", element: <Home handleLocationChange={handleLocationChange}/> },
{ path: "component2", element: <></> },
]);
return routes;
};
return (
<Router>
<Navbar navbarScroll={navbarScroll}/>
<AppRoutes/>
</Router>
);
}
export default App;
Here, instead, the Home.jsx code:
import { useInView } from 'react-intersection-observer';
import HomeCSS from "../styles/home.module.css"
import mePhoto from "../assets/me.png"
import { useEffect, useState } from 'react';
interface AppProps {
handleLocationChange: (values: any) => boolean;
}
export default function Home(props: AppProps){
const { ref: containerChange , inView: containerChangeIsVisible, entry} = useInView();
useEffect(()=>{
props.handleLocationChange(containerChangeIsVisible)
//returns false at first render as expected
console.log("Home "+containerChangeIsVisible)
},[])
return(
<>
<div className={`${ HomeCSS.container} ${containerChangeIsVisible? HomeCSS.container_variation: ''}`}>
<div className={HomeCSS.container__children}>
{/* when i scroll on the div the css change (this works)*/}
<h1 className={`${ HomeCSS.container__h1} ${containerChangeIsVisible? HomeCSS.container__h1_variation: ''}`}>My<br/> Name</h1>
<p>Computer Science student.</p>
</div>
<img src={mePhoto} className={HomeCSS.image_style}/>
</div>
<div ref={containerChange} style={{height:800,background:"orange"}}>
<p style={{marginTop:20}}>HIII</p>
</div>
</>
)
}
And Navbar.jsx:
import NavbarCSS from "../styles/navbar.module.css"
import acPhoto from "../assets/ac.png"
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
interface NavbarScroolProp{
navbarScroll:boolean
}
export default function Navbar(props:NavbarScroolProp){
const [scrollState,setScrollState]=useState(false)
const [pVisible,setpVisible] = useState('')
useEffect(()=>{
setTimeout(() => {
setpVisible("")
}, 3000)
setpVisible("100%")
},[])
//returns false also when should be true
console.log(props.navbarScroll)
return (
<>
{/*the props is undefined so the css doesn't change, i need to do this*/}
<nav className={`${props.navbarScroll?NavbarCSS.nav__variation:NavbarCSS.nav}`}>
<div className={NavbarCSS.nav_row}>
<div className={NavbarCSS.nav_row_container}>
<img src={acPhoto} className={NavbarCSS.image_style}/>
<p className={NavbarCSS.p_style} style={{maxWidth: pVisible}}>My name</p>
</div>
<div className={NavbarCSS.nav_row_tagcontainer}>
<Link className={NavbarCSS.nav_row_tag} to="/"> Home</Link>
<Link className={NavbarCSS.nav_row_tag} to="/"> About</Link>
<Link className={NavbarCSS.nav_row_tag} to="/"> Contact</Link>
</div>
</div>
</nav>
</>
);
}
In my application I want to change the background color whenever the div referring to the InsertionObserver ( I use "useInView" hook , from :https://github.com/thebuilder/react-intersection-observer) is displayed. The problem is that the div in question is in the Home.jsx component and I need to change the color of the divs in the navbar as well when the div in Home is triggered(or other components in case I need to in the future).
The question is: How can I dynamically trigger DOM elements of other components (to then perform certain operations) using the InsertionObserver ?
As you can see from the code I tried to create Props, but everything returns undefined and doesn't involve any changes.
I've tried without useEffect, without using the useInView hook, passing the object instead of the boolean value, but I can't find any solutions to this problem.
You would be of great help to me.
PS: I would like to leave the Navbar.jsx component where it is now, so that it is visible in all components.
Any advice or constructive criticism is welcome.

Problem fetching prince on Coingecko (React)

I'm trying to play with Coingecko API just to get better at React but I'm already stuck ahah
The general app is very simple for now, index is displaying cards of all tokens with ticker and price. And each card is clickable to go on a more detailed page.
App.js
import axios from "axios";
import React, { useEffect, useState } from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import SingleCoin from "./pages/SingleCoin";
function App() {
const [coinsData, setCoinsData] = useState([]);
useEffect(() => {
axios
.get(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=false&price_change_percentage=1h%2C24h%2C7d%2C14d%2C30d%2C200d%2C1y"
)
.then((res) => setCoinsData(res.data));
}, []);
const Home = () => {
return (
<>
<h2>Home</h2>
<div className="app">
{coinsData.map((coin) => {
return (
<div className="coin-card" key={coin.id}>
<h2>{coin.id}</h2>
<p>
Ticker: <span className="symbol">{coin.symbol}</span>
</p>
<p>Prix: {coin.current_price} $</p>
<Link to={`/pages/${coin.id}`}>Plus d'infos</Link>
</div>
);
})}
</div>
</>
);
};
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/pages/">Single Coin</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/pages/:id"
element={<SingleCoin />}
handler={coinsData.id}
/>
</Routes>
</div>
</Router>
);
}
export default App;
SingleCoin.jsx
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import SinglePrice from "../components/SinglePrice";
const SingleCoin = () => {
const { id } = useParams();
const [SingleCoin, setSingleCoin] = useState({});
useEffect(() => {
axios
.get(
`https://api.coingecko.com/api/v3/coins/${id}`
)
.then((res) => setSingleCoin(res.data));
}, [id]);
return (
console.log(SingleCoin),
<article>
{/* <div className="img">
<img src={SingleCoin.image.thumb} alt="logo" />
</div> */}
<h1>{SingleCoin.symbol}</h1>
<p>{SingleCoin.coingecko_rank}</p>
<p>{SingleCoin.market_data.current_price.usd}</p>
{/* <SinglePrice /> */}
<br />
</article>
);
};
When I write SingleCoin.market_data.current_price.usd for the first time, it works perfectly. But if I just go back to Home and go to the exact same token, it shows this error:
Uncaught TypeError: Cannot read properties of undefined (reading
'current_price')
I don't understand why the first time it works ans then it bugs. I'm probably missing something with my weak logic ahah.
A big thanks in advance guys!
PS : Little update: I see that my SingleCoin const is empty when I refresh, even when I leave the page and come back. So I'm probably doing something wrong with my useEffect but I'm definitely too dumb to see it
Ok, that's crazy how much it helps me to ask questions sometimes ahah.
I think I found the way (not sure it is the best way but it works)
I just add optional chaining operator to the price and it works :
<p>{SingleCoin?.market_data?.current_price?.usd}</p>
Not sure this is the best way to proceed but if it can helps others, here is a solution!

How to use react-router-dom with Context API V6?

I am changing the value in PC component but it is not reflected in the BR1 component. If I don't use react-router-dom, everything works fine, but I need the routes.
App.js code
import React, { createContext, useState } from 'react';
import './App.css';
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import BR1 from './Components/BR1';
import PC from './Components/P_c1'
import BR from './Components/BR';
export const BRcontext = createContext();
function App() {
const [value, setValue] = useState(false)
return (
<div>
<BRcontext.Provider value={{value, setValue}}>
<Router>
<Routes>
<Route path='/PC' element={<PC/>} />
<Route path='/BR1' element={<BR1/>} />
<Route path='/BR' element={<BR/>} />
</Routes>
</Router>
</BRcontext.Provider>
</div>
);
}
export default App;
PC code
import React, { useContext } from 'react'
import './Profile.css';
import { BRcontext } from '../App';
export default function Profile() {
const {value, setValue} = useContext(BRcontext);
return (
<div>
<div className='container mt-5'>
<div className='row'>
<div>
<h3 className='mt-5'>Send Request</h3>
<button className='btn btn-success mt-3 ps-3 pe-3' onClick={()=>{setValue(true)}}>Request</button>
</div>
</div>
</div>
</div>
)
}
BR1 code
import React, { useContext } from 'react'
import BR from './BR'
import { BRcontext } from '../App'
import { Link } from 'react-router-dom';
export default function BR1() {
const {value} = useContext(BRcontext);
// let navigate = useNavigate();
return (
<div>
{console.log(value)} //this remains false
{value ? <Link to="/BR"/>: console.log('hello there!')}
</div>
)
}
In BR1 code, I want the value to become true when a button in the PC component is clicked
Link - https://codesandbox.io/s/great-star-bzhuvw?file=/src/App.js
It seems there's no way to navigate from /PC to /BR1 unless changing the browser URL directly, and by doing this, you lose the current context value because it's in memory. If you intend to keep this behaviour, you should consider persisting the context value every time you change it and initialize it with the previously persisted one.
An example using the browser's local storage:
// Helper function to read the storaged value if it exists
function getPersistedValue() {
const serializedValue = localStorage.getItem('value')
try {
if (!serializedValue) {
throw new Error('No previously persisted value found')
}
return JSON.parse(serializedValue)
} catch {
return false
}
}
// Using the helper function to initialize the state
const [value, setValue] = useState(getPersistedValue())
// Synchronizing the persisted value on local storage with the in-memory one
useEffect(() => {
localStorage.setItem('value', JSON.stringify(value))
}, [value])
If you want, I forked your Code Sandbox and applied these changes: https://codesandbox.io/s/router-context-forked-uqhzye.

Clicking on a Card component should create a new route and display further information

I'm new to react-router v6
I have 4 components, App, CardList, Card and CardInfo. There is data (an array of objects, each object represents a movie) coming from an API that gets saved in App.js with useState hook.
Within CardList, I use map to iterate over the array to generate a bunch of Card components and passing in data via props.
What I want now is to be able to click on any Card component and for it to navigate to a different route, e.g. localhost:3000/1 (for Card with the id of 1), localhost:3000/2 (for Card with the id of 2) etc. and within each route that corresponds to the Card id, there would be a box/modal (CardInfo.js) component with further information about the movie.
I'm trying to accomplish this with react-router-dom (version 6).
It looks like within the CardList.js or Card.js component you would need to create links (<Link>) and routes (<Route>) (both which are equal to the number of movies in the data) on the fly with the .map function and wrapping the Card component in <Link> and <Route> tags. Something like
{items.map(movie => (
<Route path="/:id" element={<Card items={movies} />} exact>
<Link to={`/${movie.id}`}>
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
</Link>
</Route>
))}
Obviously that doesn't work.
App.js:
import './App.css';
import CardList from './features/Card/CardList';
import CardInfo from './features/Card/CardInfo';
import { useState, useEffect } from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom';
function App() {
const [movies, setMovies] = useState([]);
useEffect(() => {
const getData = fetch('https://api.com/movies')
.then(data => data.json())
.then(items => { setMovies(items) })
}, [])
return (
<BrowserRouter>
<Routes>
<Route path="/:id" element={<CardInfo items={movies} />} exact></Route>
</Routes>
<CardList items={movies} />
</BrowserRouter>
);
}
export default App;
CardList.js:
import React from 'react'
import Card from './Card'
import "./CardList.css";
const CardList = ({ items }) => {
return (
<div className="cardList">
{items.map(movie => (
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
))}
</div>
)
}
export default CardList
Card.js:
import React from 'react'
import "./Card.css";
import { Link } from 'react-router-dom';
function Card(props) {
return (
<Link to={`/${props.id}`}>
<div className="card">
<div>
<img src={props.img} />
<h2>{props.name}</h2>
</div>
</div>
</Link>
)
}
export default Card
CardInfo.js:
import React from 'react'
function CardInfo(props) {
return (
<div>
<p>{props.description}</p>
</div>
)
}
export default CardInfo
your code structure is correct. You just need to useLink, you don't need en external Route component for every card you map.
Here is a link for further information. Hope you find it helpful.
https://stackoverflow.com/a/57059249/17715977

rendering component, after another distant component renders

In navigation menu app, down the component tree, there is a dropdown menu component DropdownMenu2, with menu items, which are <NavLinks> components. Every time an item is clicked, it points to one of the <Route>s in main App. Every <Route> is a page, containing Infofield component. So every time <NavLink> is clicked, Infofield is rendered.
My puzzle is: I need the HeaderLogo component be rendered, everytime Infofield is rendered (HeaderLogo contains animation). I failed when constructing useEffect hook in Infofield. That hook was intended to contain custom hook, producing a variable with changing state. That hook could be then lifted up to App, from there variable would be passed to HeaderLogo, inline to the key property. If that idea is legit, I'm experiencing difficulties with construction of custom hook inside of useEffect. Maybe (probably) there is a better way...
Apps most basic structure looks like this:
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import "./NaviMain.css";
import NaviMainButton from "./NaviMainButton";
import NaviMainButtonDrop2 from "./NaviMainButtonDrop";
const NaviMain = () => {
return (
<nav>
<ul>
<NaviMainButtonDrop2 />
</ul>
</nav>
)
}
export default NaviMain
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
const NaviMainButtonDrop2 = () => {
return (
<li>
<a>
title
</a>
<DropdownMenu2 />
</li>
)
}
export default NaviMainButtonDrop2
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
const DropdownMenu2 = () => {
return (
<div className=dropdown-holder-us>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
Info (one of the <Route>'s )
import InfoField from "../components/InfoField"
const Info = () => {
return (
<section className="intro-index">
<InfoField text={"welcome"} />
</section>
)
}
export default Info
HeaderLogo
import "./HeaderLogo.css";
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
From what I can gather you simply want to "rerun" an animation in the HeaderLogo component when the path changes. Import and use the useLocation hook and use the pathname value as a React key on the header element with the animation to want to run when it mounts. The idea here is that when the React key changes, React will remount that element.
Example:
import { useLocation } from "react-router-dom";
import "./HeaderLogo.css";
const HeaderLogo = () => {
const { pathname } = useLocation();
return (
<header>
<h1 key={pathname} className="head-main">
learning curve
</h1>
</header>
);
};
export default HeaderLogo;
This is a classic job for a global state. You can declare a boolean state, i.e showHeader, and add conditional rendering to the tag.
The global state variable showHeader will be changed each time you click on a dropdown item, and in the App functional component you should listen for a change in this variable. (For example, using Redux, you'll use useSelector(state=>state.showHeader) in App.
For an example, this is the App component with conditional rendering for the HeaderLogo. In order for this to be useable, you need to build a Redux store and reducer functions. Read the official Redux docs for more
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useSelector } from 'react-redux';
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
const showHeader = useSelector(state=>state.showHeader)
return (
<Router>
<div className="App">
{showHeader ? <HeaderLogo /> : null}
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
</Router>

Resources