Using state from components not loaded with react-router-dom - javascript

I re-formatted the question with an example using the star wars API.
The problem I am having is trying to use state from PeoplePage and passing to the CounterPage while using react-router-dom. This is a small example of my real website. I am trying to narrow down the actual issue with this example. I hope this clarifies what I am working to achieve. Thanks!
import React from 'react';
import {BrowserRouter as Router,Switch,Route} from 'react-router-dom';
class ExampleApp extends React.Component{
constructor(){
super()
this.state = {
starWarsPeople:[],
stateNumber:0
}
}
numberHandler(number){
console.log('number',number)
this.setState({stateNumber: this.state.stateNumber + number})
}
componentDidMount(){
fetch('https://swapi.dev/api/people')
.then(response => response.json())
.then(data =>this.setState({starWarsPeople:data}))
}
render(){
const {starWarsPeople,stateNumber} = this.state
console.log(stateNumber);
return(
<Router>
<div>
<Switch>
/* Issue----->*/ <Route path ='/people' exact render = /*Render is where I am stuck */
{starWarsPeople.length !==0
?
<PeoplePage prop1 = {starWarsPeople} increment ={this.numberHandler.bind(this)}/>
:
<div>loading...</div>
}
/>
/* Issue----->*/ <Route path ='/counter' exact render =
/*render is where I am stuck, interval will not go to count prop when this is loaded*/
{starWarsPeople.length !==0
?
/*this example will render but not sure if its right and the interval will not work:
<Route path ='/counter' exact render = {
(props)=><CounterPage {...props} people ={starWarsPeople} key = {stateNumber}/>}/> */
<CounterPage people ={starWarsPeople} key = {stateNumber}/*I want this to re-render every time the inverval changes *//>
:
<div>loading...</div>
}
/>
</Switch>
</div>
</Router>
)
}
}
export default ExampleApp;
class PeoplePage extends React.Component {
constructor(props){
super(props)
this.state ={
number:0
}
}
componentDidMount(){
this.myInterval = setInterval(()=>{
this.setState({state:this.state+1})
this.props.increment(1);
},1000)
}
componentWillUnmount(){
clearInterval(this.myInterval);
}
render(){
return(
<div>
{this.props.prop1.results[0].name}
</div>
)
}
}
const CounterPage =({count})=>{
return(
<div>{count}</div>
)
}

I'm not entirely sure what your app code is supposed to be doing, but when I copy/pasted your code snippets into a codesandbox there were a few issues I had to address before the code would run.
Issues
Each Route's render prop wasn't returning a render function.
Each Route's path prop wasn't an absolute path.
PeoplePage's state update was incorrectly updating its state. Doesn't access the correct previous state, i.e. state isn't a number so this.state + 1 won't work.
CounterPage consumes and renders only a count prop, but isn't passed this prop.
Fixes
Routes
<Route
path="/people" // <-- fix path
exact
render={(routeProps) => // <-- use a render function to return UI
starWarsPeople.length !== 0 ? (
<PeoplePage
prop1={starWarsPeople}
increment={this.numberHandler.bind(this)}
{...routeProps} // <-- pass on route props
/>
) : (
<div>loading...</div>
)
}
/>
<Route
path="/counter" // <-- fix path
exact
render ={(routeProps) => // <-- use a render function to return UI
starWarsPeople.length !== 0 ? (
<CounterPage
people={starWarsPeople}
count={stateNumber} // <-- pass count prop
key={stateNumber}
{...routeProps} // <-- pass on route props
/>
) : (
<div>loading...</div>
)
}
/>
PeoplePage
class PeoplePage extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
componentDidMount() {
this.myInterval = setInterval(() => {
this.setState(({ number }) => ({ number: number + 1 }));
this.props.increment(1);
}, 1000);
}
componentWillUnmount() {
clearInterval(this.myInterval);
}
render() {
return <div>{this.props.prop1.results[0].name}</div>;
}
}

Related

Props are showing "undefined" when on click to send props to another component

I am building a simple react app and I am beginner. I am trying to send state as props to another component But when I access props like this.props.name then it is showing undefined.
App.js:
class Home extends React.Component {
state = {
fields : {
name : '',
}
}
updateField = evt => {
const fields = Object.assign({}, this.state.fields);
fields[evt.target.name] = evt.target.value;
console.log(evt.target.value)
this.setState({fields})
}
render() {
return(
<div>
<form>
<input
name="name"
id="name_id"
onChange={this.updateField}
value={this.state.fields.name}
/>
</form>
<Link to='/second_component'><SecondComponent name={this.state.fields} />Submit</Link>
<Routes>
<Route path='/second_component' element={<SecondComponent/>} />
</Routes>
</div>
)
}
}
class SecondComponent extends React.Component {
render() {
return (
<div style={{color: "white"}}>
{console.log(this.props.name)}
</div>
)
}
}
export default Home;
I have tried many times but it is still not working. When I try to console.log then it is showing "undefined".
Change fields[evt.target.name] = evt.target.value; to fields.name = evt.target.value; and name={this.state.fields} to name={this.state.fields.name}. Should work.

How to rerender child component every time the parent is rerendered?

This is my render method in App.js:
render() {
console.log('APP Mounted', this.state.searchedBooks)
return (
<div className="app">
<Route exact path='/' render={()=> (
<ListBooks
books={this.state.books}
handleChangeCategory={(choice) => this.handleChangeCategory(choice)}
/>
)}/>
<Route path='/search' render={({ history }) => (
<SearchBook
handleChangeCategory={(choice) => this.handleChangeCategory(choice)}
handleSearchBook={(query) => this.handleSearchBook(query)}
searchedBooks={this.state.searchedBooks}
/>
)}/>
</div>
)}
I have searchedBook state that keeps updating when a new query arrives, and therefore also rerenders App.js. The log at the top of the render() method works as expected.
The problem is, that my child component SearchBook is not rerendering when App.js is rerendered and therefore doesn't receive the latest this.state.searchedBooks
In SearchBook.js I added the componentDidMount() method to reset the state:
class SearchBook extends Component {
state = {
query : '',
books : []
}
componentDidMount(){
console.log('Mounted')
this.setState(() => ({
books: this.props.searchedBooks
}))
}
updateQuery = (query) => {
this.setState(() => ({
query: query
}))
}
onChangeQuery = query => {
this.updateQuery(query)
this.props.handleSearchBook(query)
}
render(){
const { query, books } = this.state;
return()
}}
But this doesn't work as the whole SearchBook component doesn't get rerendered after my searchedBooks state is updated in App.js.
When I manually access a different URL (here the home route) and go back to the /search route, obviously the component gets rerendered and it works.
So how do I make the child of App.js rerender as well?

Refresh Component on route parameter change

Why the component doesn't update the props when changing the route param?
Trying to use setState inside componentWillReceiveProps but it doesn't even fire?
export default class HomePage extends React.PureComponent {
movies = require('../../movieList.json');
state = {
match: this.props.match,
id: null
}
componentWillReceiveProps(np) {
if(np.match.params.id !== this.props.match.params.id) {
console.log('UPDATING PROPS')
this.setState({ match: np.match })
}
}
render() {
const { match } = this.props;
const movie = this.movies[match.params.id || movieOrder[0]];
const nextMovie = getNextMovie(match.params.id || movieOrder[0]);
const prevMovie = getPrevMovie(match.params.id || movieOrder[0]);
return (
<>
<h1>{movie.title}</h1>
<Link to={nextMovie}>Next</Link>
<Link to={prevMovie}>Prev</Link>
</>
);
}
}
nextMovie and prevMovie get the id which should be set inside link. Unfortunatelly it sets only during 1st render. When clicked I can see the url change, but no updates are fired
Here is the component holding the switch
export default class App extends React.PureComponent {
state = {
isLoading: true,
}
componentDidMount() {
PreloaderService.preload('core').then(() => {
console.log('LOADED ALL');
console.log('PRELOADER SERVICE', PreloaderService);
this.setState({ isLoading: false });
console.log('Preloader assets', PreloaderService.getAsset('dog-video'))
});
}
render() {
const { isLoading } = this.state;
if(isLoading) {
return(
<div>
is loading
</div>
)
}
return (
<div>
{/* <Background /> */}
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/:id" component={props => <HomePage {...props} />} />
<Route component={NotFoundPage} />
</Switch>
<GlobalStyle />
</div>
);
}
}```
Seems to be working with my codesandbox recreation.
I had to improvise a bit with the missing code:
What's the value of movieOrder[0]? Is 0 a valid value for match.params.id?
How do getNextMovie and prevMovie look like?
Also, you're not using the Home component's state in your render logic, so I think, it should render the correct movie even without them.
There are several things off here. componentWillReceiveProps is considered unsafe and is deprecated. Try using componentDidUpdate instead. Also your class component needs a constructor. As it is now state is defined as a static variable.
constructor(props) {
super(props);
this.state = {
match: this.props.match,
id: null
}
}
componentDidUpdate(np) {
if(np.match.params.id !== this.props.match.params.id) {
console.log('UPDATING PROPS')
this.setState({ match: np.match })
}
}
Last, double check to make sure your logic is correct. Right now the Next and Prev buttons link to the same thing:
const nextMovie = getNextMovie(match.params.id || movieOrder[0]);
const prevMovie = getPrevMovie(match.params.id || movieOrder[0]);
<Link to={nextMovie}>Next</Link>
<Link to={prevMovie}>Prev</Link>
As an alternative, if you want to stick with componentWillReceiveProps you need to unbind the current movie, and rebind it with a new one. Here is a code example doing the exact same thing with users.

Button click won't update state in my React app

A button click shall filter my job-card array to only one category. E.g. button "Marketing" should filter to those jobs from array who have prop "jobstags: Marketing". I used a very similar procedure like for my input which filters jobs perfectly.
I can console log my event (the button click) with the according value ("Marketing"). But it still doesn't filter correctly...
In my app I did this:
export default class App extends Component {
state = {
jobs: jobs,
searchfield: '',
jobtags: ''
}
onSearchChange = event => {
this.setState({ searchfield: event.target.value })
}
onClickChange = event => {
console.log(event.target.value)
this.setState({ jobtags: event.target.value })
}
render() {
const filteredJobs = this.state.jobs.filter(job => {
return (
job.position
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.company
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.jobtags.toLowerCase().includes(this.state.jobtags.toLowerCase())
)
})
// this.save()
if (this.state.jobs.length === 0) {
return <Loading>Loading...</Loading>
} else {
return (
<Router>
<React.Fragment>
<Route
exact
path="/"
render={() => (
<Home
jobs={filteredJobs}
searchChange={this.onSearchChange}
clickChange={this.onClickChange}
/>
)}
/>
onClickChange is what should update the state of tags
In my Home component I then simply pass the value on to the Categories component:
<Categories clickChange={clickChange} />
Finally it arrives in my Categories component where I say:
export default class Categories extends Component {
render() {
const { clickChange } = this.props
return (
<Wrapper>
<button value="Marketing" onClick={clickChange}>
<img
alt="Button"
src={require('/Users/markus/Documents/q4-2018/jobs-app/src/img/computer.png')}
/>
Frontend
</button> ...
Any ideas? Thx!
maybe you have to bind the "this" of "onClickChange", for example in the constructor of your App class.
Example :
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
jobs: jobs,
searchfield: '',
jobtags: ''
};
this.onClickChange = this.onClickChange.bind(this);
and it will work I think
You will have to bind it. Add this line to your constructor:
this.onClickChange = this.onClickChange.bind(this);

How can I wrap the Children of a Parent component in a HOC and render them in React?

Let me start by saying that this example is very simple and can be solved with React.cloneElement. But I want more freedom and the project will be more complex, so I'd like to find a solution.
I would also like to understand what I'm missing :/
I want to be able to augment the children of a Parent component with props and methods (hence the HOC). It would start from here:
<Parent>
<aChild />
<anotherChild />
<yetAnotherChild />
</Parent>
And this is the Parent component (called Sequence in my project), so far:
import React, { Component } from 'react';
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends Component {
constructor(props) {
super(props);
this.state = {
pointer: 0,
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Children = React.Children.map(this.props.children, Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
// do stuff
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
export default Sequence;
I get the following error:
You can play with the code here: https://codesandbox.io/s/6w1n5wor9w
Thank you for any help!
This answer will not solve your problem but maybe gives a hint why this is not possible. At first I was surprised why your code does not work, even though I'm not an experienced React developer it seems ok map this.props.children through with React.Children.map and return the desired Component with your HOC. But it did not work.
I tried to debug it a little bit and did some search. I've learned props.children actually contains the elements itself not the instances of components. Even, React.Children.map does not have any effect on this.
Here is a working snippet proves that your problem is not related with the HOC. I've used an array of components instead of mapping through props.children.
class Line1 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 1</div>;
}
}
class Line2 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 2</div>;
}
}
class Line3 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 3</div>;
}
}
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends React.Component {
constructor(props) {
super(props);
this.state = {
pointer: 0
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Arr = [ Line1, Line2, Line3 ];
this.Children = this.Arr.map(Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
this.next();
}
next() {
// Clearly, render the next element only if there is, a next element
if (this.state.pointer >= this.Arr.length - 1) {
return;
}
this.setState({ pointer: this.state.pointer + 1 });
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
ReactDOM.render(
<Sequence />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
You are returning <Child /> instead of Child in Sequence.js render method. Here is my edited copy - codesandbox

Categories

Resources