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
Related
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>
);
}
Here is App.js
import React, {Component} from 'react';
import {
BrowserRouter,
Route,
Switch,
Redirect
} from 'react-router-dom';
import Search from './Search';
import Nav from './Nav';
import '../index.css';
import axios from 'axios';
import apiKey from './Config';
import NotFound from './NotFound';
import PhotoList from './PhotoList';
class App extends Component {
state= {
pictures: []
}
componentDidMount() {
this.getImages()
}
getImages=(query='cats')=> {
axios.get(`https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&per_page=24&page=1&format=json&nojsoncallback=1`)
.then(res=> {
const pictures=res.data.photos.photo
this.setState({pictures});
}).catch((error)=> {
console.log("There was an error parsing your data", error);
})
}
render() {
console.log(this.state.pictures);
return (
<div className="container">
<Search />
<Nav getImages={this.getImages} />
<Switch>
<Route exact path="/" render={()=> <Redirect to={'/cats'} />} />
<Route path='/cats' render={()=> <PhotoList getImages={()=>this.getImages} query='cats' data={this.state.pictures}/>} />
<Route path='/dogs' render={()=> <PhotoList getImages={()=>this.getImages} query='dogs' data={this.state.pictures} />} />
<Route path='/computers' render={()=> <PhotoList getImages={()=>this.getImages} query='computers' data={this.state.pictures} />} />
<Route component={NotFound}/>
</Switch>
</div>
)
}
}
export default App;
Here is PhotoList.js
import React, {Component} from 'react';
import Photo from './Photo';
class PhotoList extends Component {
handleImages=()=> {
this.props.getImages(this.props.query);
}
componentDidMount() {
this.handleImages();
console.log(this.props.data)
}
componentDidUpdate() {
console.log(this.props.data)
}
render() {
return (
<div className="photo-container">
<h2>Results</h2>
<ul>
{this.props.data.map((photo,index)=>
<Photo
farm={photo.farm}
server={photo.server}
id={photo.id}
secret={photo.secret}
key={index}
/>
)}
</ul>
</div>
);
}
}
export default PhotoList;
I've passed the getImages function into PhotoList that fetches data and changes the main App's state. The function takes a query string (cats, dogs, or computers). Then the state data is passed down as props and mapped over in the PhotoList component.
My website still only displays cats, even when I type in a different path (ex /dogs, /computers), yet when I console log the query string, I'm clearly entering different values into it. So why am I still getting cats shown? I know by default the query is equal to cats, but when I call the function in PhotoList it should be set to a different value with the query prop.
What am I doing wrong?
I agree with the first answer that checking for changes and calling the function again might solve your problem, since getImages() only runs when the component mounts. I have some more advice that might help you organize your code better according to common practices and help you avoid this problem altogether, just by not necessitating the confusing passing around of variables between components.
It makes the most sense to have getImages() part of the PhotoList component. This is because you always need to call that function when you render a PhotoList. It's not essential to the App component. Read more about this kind of concept here: https://reactjs.org/docs/thinking-in-react.html
Since you are using react router, what you can do is grab the search keyword from the path, from the PhotoList component. This would look like declaring the query as <Route path='/:query' ... > and then in the rendered component (PhotoList), grabbing the parameter with this.props.match.query. Read more: https://tylermcginnis.com/react-router-url-parameters/
If you do this, you can set default values for props using PropTypes and defaultProps. read more: https://blog.bitsrc.io/understanding-react-default-props-5c50401ed37d
The issue is, componentDidMount from PhotoList component executes only once when component first time mounts with query cats.
For next route change it will not execute componentDidMount, and your will not get new data but the old one.
In your componentDidUpdate you have to check if you are getting new props and again call your handleImages function which calls your getImages function,
componentDidUpdate(prevProps) {
console.log(this.props.data)
if(prevProps.query !== this.props.query){
this.handleImages();
}
}
Another issue is getImages function is not getting called.
getImages={()=>this.getImages}
You should do this,
getImages={this.getImages}
I have a list of items, in a table in React. When I click on the table Link, the url updates, but the component doesn't get rendered. If I refresh the component is there.
I have read that some React-router-dom versions have some problems, and there are a lot of solutions that are being discussed here:
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md
That although is for the Beta Versions. I assume I need to do something with the higher order function withRouter from react-router-dom, but it doesn't work for me. On top of that, withRouter has been set up globally in the App.
const AppHeader = withRouter(({ history, location, ...props }) => (
<Header {...props} currentRoute={location.pathname} />
));
The thing is, my App has several applications. One Angular and 2 React ones. Do I need to set it up, on each application as well? This is the Parent Component with the router for the application the Links aren't working.
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import 'react-tagsinput/react-tagsinput.css';
import PolicyRoutes from 'dataprotection/policies/containers/PolicyRoutes';
import './styles.scss';
const AppRouter = () => (
<Switch>
<Route exact path="/policies" component={PolicyRoutes} />
<Redirect to="/policies" />
</Switch>
);
AppRouter.propTypes = {};
function mapStateToProps() {
return {};
}
export default connect(mapStateToProps)(AppRouter);
I have tried 2 solutions. The first is to actually wrap my component withRouter like so:
<Route exact path="/policies" component={withRouter(PolicyRoutes)} />
And the second is to wrap withRouter, the connect function. Both aren't working.
This is the component not working:
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import OverviewScreen from '../OverviewScreen';
import PolicyDetailsScreen from '../PolicyDetailsScreen';
const PolicyRoutes = ({ match }) => (
<Switch>
<Route exact path={`${match.url}/details/:policyId`} component={PolicyDetailsScreen} />
<Route exact path={match.url} component={OverviewScreen} />
<Redirect to={match.url} />
</Switch>
);
PolicyRoutes.propTypes = {
match: PropTypes.object
};
export default PolicyRoutes;
Can anyone help? I don't know what the problem is...
I have a React Component called ContentBar that holds a Route to display dynamic content:
//ContentBar.js
var React = require('react')
import ContentBarRoute from '../../../../routes/contentbar.routes'
const ContentBar = () =>
(
<ContentBarRoute />
)
export default ContentBar
I've placed this ContentBar in my root App structure:
//App.js
<div className="logBar">
<ErrorBoundary>
<Responsive minWidth={960}>
<ContentBar />
</Responsive>
</ErrorBoundary>
</div>
And I've created a route for a new menu in the ContentBarRoute component which I'm loading in the ContentBar:
//ContactBarRoute.react.js
const ContentBarRoute = () => (
<main>
<Switch>
<Route exact path="/logbar"component={LogBar}/>
<Route path="/user/:number/settings" />
<Route path="/user/:number/profile" />
<Route path="/user/add" component={UserAddMenu} />
</Switch>
</main>
)
When I try to link to /user/add from another component though, I'm not able to update the route from another component:
//Contactlist.react.js
<div className="contact-list useradd">
<Button as={Link} to="/user/add" className="btn-useradd">
<FontAwesome className="icon-adduser" tag="i" name="plus" />
</Button>
</div>
Can someone help me see what I'm doing wrong here? There's not a lot of information about routing between components, I found one answer in my research but it was slightly different: React-Router-4 routing from another component
The problem is that my routes and links are in separate areas of the hierarchy, whereas that guy's components were all close together.
Update:
The other example doesn't talk about rendering new components in place of old ones where one component is totally separate from the other:
Here is my router definition it exists in the class that sends the App to the html div:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
//import { createStore } from 'redux'
import { HashRouter } from 'react-router-dom'
import configureStore from '../tools/store.enhancer';
import App from '../javascripts/entry';
//import rootReducer from '../app/reducers/index'
//let store = createStore(rootReducer)
const store = configureStore();
render((
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>
), document.getElementById('root'));
The behavior I expect is that the existing component is switched out for the user-add component, but the actual behavior is that nothing happens when I click the button, and I get an error saying
Hash history cannot PUSH the same path; a new entry will not be added to the history stack
I'm using React Router to create a multi page app. My main component is <App/> and it renders all of the routing to to child components. I'm trying to pass props via the route, and based on some research I did, the most common way for child components to tap into props passed down is via the this.props.route object that they inherit. However, this object is undefined for me. On my render() function in the child component, I console.log(this.props) and am return an object that looks like this
{match: Object, location: Object, history: Object, staticContext: undefined}
Doesn't look like the props I expected at all. Here is my code in detail.
Parent Component (I'm trying to pass the word "hi" down as a prop called "test" in all of my child components):
import { BrowserRouter as Router, HashRouter, Route, Switch } from 'react-router-dom';
import Link from 'react-router';
import React from 'react';
import Home from './Home.jsx';
import Nav from './Nav.jsx';
import Progress from './Progress.jsx';
import Test from './Test.jsx';
export default class App extends React.Component {
constructor() {
super();
this._fetchPuzzle = this._fetchPuzzle.bind(this);
}
render() {
return (
<Router>
<div>
<Nav />
<Switch>
<Route path="/" exact test="hi" component={Home} />
<Route path="/progress" test="hi" component={Progress} />
<Route path="/test" test="hi" component={Test} />
<Route render={() => <p>Page not found!</p>} />
</Switch>
</div>
</Router>
);
}
}
Child:
import React from 'react';
const CodeMirror = require('react-codemirror');
import { Link } from 'react-router-dom';
require('codemirror/mode/javascript/javascript')
require('codemirror/mode/xml/xml');
require('codemirror/mode/markdown/markdown');
export default class Home extends React.Component {
constructor(props) {
super(props);
console.log(props)
}
render() {
const options = {
lineNumbers: true,
theme: 'abcdef'
// mode: this.state.mode
};
console.log(this.props)
return (
<div>
<h1>First page bro</h1>
<CodeMirror value='code lol' onChange={()=>'do something'} options={options} />
</div>);
}
}
I'm pretty new to React so my apologies if I'm missing something obvious.
Thanks!
You can pass props to the component by making use of the render prop to the Route and thus inlining your component definition. According to the DOCS:
This allows for convenient inline rendering and wrapping without the
undesired remounting explained above.Instead of having a new React
element created for you using the component prop, you can pass in a
function to be called when the location matches. The render prop
receives all the same route props as the component render prop
So you can pass the prop to component like
<Route path="/" exact render={(props) => (<Home test="hi" {...props}/>)} />
and then you can access it like
this.props.test
in your Home component
P.S. Also make sure that you are passing {...props} so that the
default router props like location, history, match etc are also getting passed on to the Home component
otherwise the only prop that is getting passed down to it is test.