I have a problem with sending state from Child to Parent.
After clicking on Menu in Main component I want to change state active and send this active to Sidebar component, because I want to hide/show Sidebar depends on active class from css. It is easy in Vanilla JS but in React I am little confused.
Main:
import Menu from "./Menu";
import "./Styles.css";
import teamsdb from "./teamsdb";
const Main = ({ name }) => {
return (
<div>
<Menu />
<h1>Main</h1>
<h4>{teamName}</h4>
</div>
);
};
Menu:
const Menu = () => {
const [active, setActive] = useState(false);
const changeMenu = () => {
setActive(!active);
};
return <button onClick={changeMenu}>Menu</button>;
};
export default Menu;
Sidebar:
const Sidebar = () => {
const [searchTerm, setSearchTerm] = React.useState("");
const handleChange = e => {
setSearchTerm(e.target.value);
console.log("Search: ", e.target.value);
};
return (
<div className={active ? 'sidebar active' : 'sidebar'}>
<div className="sidebar__header">
Header
<button>Colors</button>
</div>
<div className="sidebar__search">
<input
type="text"
placeholder="Search"
value={searchTerm}
onChange={handleChange}
/>
</div>
<Teams search={searchTerm} />
</div>
);
};
App.js
import React from "react";
import "./App.css";
import Sidebar from "./components/Sidebar";
import Main from "./components/Main";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
function App() {
return (
<div className="App">
<div className="app__body">
<Router>
<Sidebar />
<Switch>
<Route path="/team/:teamId">
<Main />
</Route>
<Route exact path="/">
<Main />
</Route>
</Switch>
</Router>
</div>
</div>
);
}
You should lift your state up the first common ancestor component, in your case: App. This way you can use it in all its descendants by passing the state and the mutation function (setActive) as prop:
App // Put active state here, pass down active and setActive as prop
^
|
+----+-----+
| |
+ +
Sidebar Main // Pass down active and setActive as prop
^
|
|
Menu // Use setActive to modify state in App
This is explained in React documentation: https://reactjs.org/docs/lifting-state-up.html
With the context api and hooks prop drilling isn’t required anymore. You can simply wrap the parent and child in a context provider and leverage react’s useContext hook for managing state across the 2 components. kent c dodds has a good article with examples here
Related
When I navigate back and forth between routes, React Router re-renders memoized routes causing useEffect(() => []) to re-run and data to re-fetch. I'd like to prevent that and instead keep existing routes around but hidden in the dom. I'm struggling with "how" though.
The following is sample code for the problem:
import React, { useEffect } from "react";
import { BrowserRouter as Router, Route, Routes, useNavigate } from "react-router-dom";
export default function App() {
return (
<Router>
<Routes>
<Route path={"/"} element={<MemoizedRouteA />} />
<Route path={"/b"} element={<MemoizedRouteB />} />
</Routes>
</Router>
);
}
function RouteA() {
const navigate = useNavigate()
useEffect(() => {
alert("Render Router A");
}, []);
return (
<button onClick={() => { navigate('/b') }}>Go to B</button>
);
};
const MemoizedRouteA = React.memo(RouteA)
function RouteB() {
const navigate = useNavigate()
useEffect(() => {
alert("Render Router B");
}, []);
return (
<button onClick={() => { navigate('/') }}>Go to A</button>
);
}
const MemoizedRouteB = React.memo(RouteB)
Sandbox: https://codesandbox.io/s/wonderful-hertz-w9qoip?file=/src/App.js
With the above code, you'll see that the "alert" code is called whenever you tap a button or use the browser back button.
With there being so many changes of React Router over the years I'm struggling to find a solution for this.
When I navigate back and forth between routes, React Router re-renders
memoized routes causing useEffect(() => []) to re-run and data to
re-fetch. I'd like to prevent that and instead keep existing routes
around but hidden in the dom. I'm struggling with "how" though.
Long story short, you can't. React components rerender for one of three reasons:
Local component state is updated.
Passed prop values are updated.
The parent/ancestor component updates.
The reason using the memo HOC doesn't work here though is because the Routes component only matches and renders a single Route component's element prop at-a-time. Navigating from "/" to "/b" necessarily unmounts MemoizedRouteA and mounts MemoizedRouteB, and vice versa when navigating in reverse. This is exactly how RRD is intended to work. This is how the React component lifecycle is intended to work. Memoizing a component output can't do anything for when a component is being mounted/unmounted.
If what you are really trying to minimize/reduce/avoid is duplicate asynchronous calls and data fetching/refetching upon component mounting then what I'd suggest here is to apply the Lifting State Up pattern and move the state and useEffect call into a parent/ancestor.
Here's a trivial example using an Outlet component and its provided context, but the state could be provided by any other means such as a regular React context or Redux.
import React, { useEffect, useState } from "react";
import {
BrowserRouter as Router,
Route,
Routes,
Outlet,
useNavigate,
useOutletContext
} from "react-router-dom";
export default function App() {
const [users, setUsers] = useState(0);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then(setUsers);
}, []);
return (
<Router>
<Routes>
<Route element={<Outlet context={{ users }} />}>
<Route path={"/"} element={<RouteA />} />
<Route path={"/b"} element={<RouteB />} />
</Route>
</Routes>
</Router>
);
}
function RouteA() {
const navigate = useNavigate();
return (
<div>
<button onClick={() => navigate("/b")}>Go to B</button>
</div>
);
}
function RouteB() {
const navigate = useNavigate();
const { users } = useOutletContext();
return (
<div>
<button onClick={() => navigate("/")}>Go to A</button>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} : {user.email}
</li>
))}
</ul>
</div>
);
}
I have two child components in my parent component with routing set up. Child1 component's button updates state of the parent component and I want to pass the updated version of that state to child2 component as props. The problem is, although the state obviously updates in the parent component(I checked it), it doesn't get passed to the child2 component(the older version of state gets passed instead). I think it's because the getdData function(in the parent component) executes after the child2 component gets rendered. How can I solve this issue so that correct state gets passed?
Here's the code:
Parent component:
import './App.css';
import Questions from './components/questions';
import DropDownDifficulty from './components/dropDownDifficulty';
import {useState} from 'react'
import axios from 'axios';
import {BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
function App() {
const [data, setdata] = useState([])
const getdData = (values)=>{
setdata(values)
}
return (
<div className="App">
<Router>
<Route exact path='/'>
<DropDownDifficulty sendData={getdData}/>
</Route>
<Route exact path = '/quiz'>
<Questions specs={data}/>
</Route>
</Router>
</div>
);
}
export default App;
Child1 component's button:
<Button disabled={buttonDisabled} className='button' variant="contained" color="primary" onClick={()=>sendData([category, Difficulty])}>
Start Quiz
</Button>
child2 component:
import React from 'react'
import Question from './question'
export default function Questions({specs}) {
console.log(specs)
return (
<div>
</div>
)
}
Simple Solution :
1.Check for Correct value before passing it down to props
<div className="App">
<Router>
<Route exact path='/'>
<DropDownDifficulty sendData={getdData}/>
</Route>
{data?.length > 0 && <Route exact path = '/quiz'>
<Questions specs={data}/>
</Route>}
</Router>
</div>
You can use useEffect and listen to props and show value only after the correct data comes
import {useEffect} from 'react'
import Question from './question'
export default function Questions({specs}) {
const [showIfCorrect,setShowIfCorrect]=useState(false)
useEffect(()=>{
if(specs){
// Check if it is correct
setShowIfCorrect(true)
}
},[specs])
return (
<div>
{showIfCorrect && <div>Correct Question</div>}
</div>
)
}
use Context Api and update state from sibling component(DropDownDifficulty) and pass it to the Questions component
For How to implement in context api refer the code below
I am using React in Laravel and I found a problem that I can't refresh or reload a page in React. So to solve this problem I found many suggestions like use historyApiFallback, 404 page and many other ways But I found none of them useful to me now.
I know I can't do this because React has no system for it because of server- and client-side routing. Then i found a demo project where they used Redux and I can refresh their page. I got the demo project where i can use any component and refresh them how many times I want. So there is a file name with Base.js and I am not understanding this file why he used it what is doing. Let me share the file and where it was used.
Base.js
import React from 'react';
import { connect } from 'react-redux';
import Header from './components/Header';
const Base = ({ children }) => (
<div>
<Header />
<main>{children}</main>
</div>
);
const mapStateToProps = (state) => ({
isAuthenticated: state.Auth.isAuthenticated,
});
export default connect(mapStateToProps)(Base);
Header.Js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import {
Nav,
NavItem,
NavLink,
UncontrolledDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
import * as actions from '../store/actions';
class Header extends Component {
handleLogout = (e) => {
e.preventDefault();
this.props.dispatch(actions.authLogout());
};
render() {
return (
<header className="d-flex align-items-center justify-content-between">
<h1 className="logo my-0 font-weight-normal h4">
<Link to="/">Laravel React</Link>
</h1>
{this.props.isAuthenticated && (
<div className="navigation d-flex justify-content-end">
<Nav>
<NavItem>
<NavLink tag={Link} to="/archive">
Archive
</NavLink>
<NavLink tag={Link} to="/Myfile">
Myfile
</NavLink>
</NavItem>
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>
Account
</DropdownToggle>
<DropdownMenu right>
<DropdownItem>Settings</DropdownItem>
<DropdownItem divider />
<DropdownItem onClick={this.handleLogout}>
Log Out
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
</Nav>
</div>
)}
</header>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.Auth.isAuthenticated,
});
export default connect(mapStateToProps)(Header);
Public.js
import PropTypes from 'prop-types';
import { Route } from 'react-router';
import Base from '../Base';
const PublicRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => (
<Base>
<Component {...props} />
</Base>
)}
/>
);
PublicRoute.propTypes = {};
export default PublicRoute;
split.js
import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router';
import { connect } from 'react-redux';
import Base from '../Base';
const SplitRoute = ({
component: Component,
fallback: Fallback,
isAuthenticated,
...rest
}) => (
<Route
{...rest}
render={(props) => (isAuthenticated ? (
<Base>
<Component {...props} />
</Base>
) : (
<Base>
< Fallback {...props} />
</Base>
))}
/>
);
SplitRoute.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({
isAuthenticated: state.Auth.isAuthenticated,
});
export default connect(mapStateToProps)(SplitRoute);
Now it has authenticated system so I understand it but why it is using base function and what it is doing? I am not understanding.
What it looks like is that the Base.js is a container for the Header and any rendered children (passed props). This is a good practise in react to separate logic and make it more readable. So when he imports Base into the Public.js file, it will render the Header and the component he is passing to it from the public function props.
Think of it like the skeleton of the layout, by importing Base it will always render the header and any logic inside of the header file, and whatever he is passing down to it. As you can see he is passing different components to it depending on whether isAuthenticated is true or false. If it is false, they are rendering Base and passing a fallback component - this will render inside of the main tag within the Base function.
Here's a Link component that I made:
const Restaurants = () => {
const meatList = require('./Meat-Shops.json');
return meatList.map((restaurant) => {
return (
<Link to={`/menu/${restaurant.store_name}`}>
<div className="restaurant-container">
<div className="image-container">
<img src={restaurant.image} alt={restaurant.alt} />
</div>
<div className="info-container" >
<h4>{restaurant.store_name}</h4>
<h5>Pick-up</h5>
</div>
</div>
</Link>
);
})
}
When pressed, it will take me (or render) this component:
import React, { useState } from 'react';
import { useParams } from 'react-router-dom'
const Menu = () => {
console.log(useParams());
return (
<div>
<p>Hello world</p>
</div>
)
}
export default Menu
What I am trying to do is to access restaurant.store_name (the URL parameter, variable at the end of the template string of the 'to' prop of the Link component) within the Menu component by using useParams(). But when I invoke useParams(), I get the following error:
error_message_invalid_hook. What I am doing wrong? Also, here is the file that contains the Router tags, if needed:
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Menu from './Menu';
import Restaurants from './Restaurants';
import NavBar from "./NavBar"
const Pages = () => {
return (
<Router>
<NavBar />
<Switch>
<Route exact path="/">
<Restaurants />
</Route>
<Route path="/menu/:store_name" children={<Menu />} />
</Switch>
</Router>
)
}
export default Pages;
And here is the error message when I set:
const { menu } = useParams();
within the body of the 'menu' component (error message: error_message_for_useParams).
You can also check out all of my code for the components (the Menu and Restaurants components) here on my GitHub: https://github.com/Toriong/quick_eat_carnivore_search_and_delivery
You're close to the solution here. In this situation useParams() is looking at the :store_name, so you would destructure store_name from the params: const { store_name } = useParams() inside of your functional component.
I currently have an App component, which is the parent to another component "Cart".
I actually use React Router for the routing, and as such my code is :
class App extends Component { // (PARENT)
state = {
likedClass : ["1"]
}
render() {
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/cart" component={() => (<Cart likedClass={this.state.likedClass} /> )}/>
</Switch></div></Router>)} // (etc...)
and a child component (cart)
class Cart extends Component {
render() {
return (
<div className="Cart">
<Header></Header>
<section>
<ul>
{this.props.likedClass.map((course,index) => <CartComponent key={index} ID={course} />)}
</ul>
</section>
<Contact></Contact>
<Footer></Footer>
</div>
)
}
}
export default Cart;
My problem is when I update my state in my App component, my Cart component does not see any change (I change the state with some functions in another children "Course" not shown here for clarity).
I used the React debogger to see that my App state indeed changes when I press the button in my "Course" Children, but still using the React debogger, the state of my Cart never changes and is always showing the initial state..
I am pretty new to React, what am I doing wrong ? Thanks!
Edit :
As asked, the code I use to change the state is in my "course" component, to which I pass a function as a prop, so in my Course component I have :
<button onClick={(e) => {this.props.addLike(course.id)}} className="btn btn-secondary module__button"> LIKER LE COURS</button>
and the function "addLike" is passed through props in the App component as such :
data_cours.map( (card,id) => {
return (<Route exact path={`/${card.categorie}/${card.id}`} key={id} component={() => <Course
id={`${card.id}`}
addLike={this.addLikedClassHandler}
/>} />)
} )
}
may be you missed something. Please check this example. it works perfectly.
App Component
import React, {useEffect, useState} from 'react';
import {BrowserRouter as Router, Switch, Route,} from "react-router-dom";
import history from "./history";
import HelloComponent from "./HelloComponent";
class App extends React.Component {
state = {
likedClass: ["khabir"]
};
render() {
return (
<div>
<Router history={history}>
<Switch>
<Route exact path="/hello"
component={() => (<HelloComponent likedClass={this.state.likedClass} />)}/>
</Switch>
</Router>
</div>
)
}
}
export default App;
history.js
import {createBrowserHistory} from "history";
export default createBrowserHistory();
Hello Component
import React from "react";
export default function HelloComponent({likedClass}) {
return (
<div>
<ul>
{likedClass}
</ul>
</div>
);
}