I am working on a show page based on the match.params
from react router 4. For some reason, some functions runs 3 times which gives me the first 2 times undefined and the third time the result I want.
So I have this Show ponent which have the props "projects" from the main ponent. In this show ponent I do a find function on the array with the props projects inside of it. This must return a 1 item with the project.id
equel to match.params.id
. This is going good, and If I visit the page by clicking on a button (through the application) I get the page with the right project and properties (e.g. project.name
). But If I do a reload or visit the page by using an url in the browser, I get an error saying property name is undefined. If I look in the console I see that my console.logs are fired 3 times. Like I said, the first 2 are undefined and the third is giving me the result I want. I guess the first 2 are resulting in the error I get.
My Show ponent
import React from 'react'
import { Button } from 'reactstrap'
const Show = ({match, projects}) => {
let project = projects.find((project) => {
//Return project with the id equel to the match.params.id
return project.id == match.params.id;
});
//Both these console.logs runs 3 times when visiting a link by url
console.log(project); //this one gives me 2 times undefined, third time is the right item
console.log(match.params.id)
return(
<div className="container p-40">
<div className="projects-header">
{/* If I echo project.name it will gives me 'cannot read property 'name' of undefined' */}
<h2>Project '{match.params.id}' {/* {project.name} */}</h2>
<Button className="btn btn-primary" onClick={() => console.log('save')}>Save</Button>
</div>
<div className="project-edit">
</div>
</div>
);
};
export default Show;
The route in the parent ponent
<Route exact path="/projects/show/:id" render={(props) => <Show {...props} projects={this.state.projects} />} />
Someone with a good solution for this problem? I find it a problem that my users can't route by the url. I don't know why my show ponent fires 3 times either by url/reload.
Edit (Added parent ponent):
//Import react
import React, { Component } from 'react';
//Import custom ponents
import Sidebar from './ponents/js/Sidebar'
import Dashboard from './ponents/js/Dashboard'
import Projects from './ponents/js/Projects'
import Show from './ponents/js/projects/Show'
//Import styles
import './App.css';
//3rd party deps
import { BrowserRouter as Router, Route } from "react-router-dom";
import axios from 'axios'
class App extends Component {
constructor() {
super();
this.state = {
//Times / Time tracking
times: [],
timer: false,
currentTimer: 0,
//Current task
currentTask: {
id: 3,
title: '',
project_id: {
id: '',
name: '',
color: ''
},
date: '',
time_total: ''
},
//Projects
projects: []
}
this.addTask = this.addTask.bind(this);
this.startTimer = this.startTimer.bind(this);
this.stopTimer = this.stopTimer.bind(this);
this.addProject = this.addProject.bind(this);
}
addTask = (task) => {
let newArray = this.state.times.slice();
newArray.push(task);
this.setState({times: newArray, currentTimer: 0, timer: false});
clearInterval(this.timerID);
}
addProject = (project) => {
let newArray = this.state.projects.slice();
newArray.push(project);
this.setState({ projects: newArray });
}
startTimer() {
let sec = this.state.currentTimer;
const start = Date.now();
this.setState({ timer: true });
this.timerID = setInterval(() => {
let time = new Date() - (start - sec * 1000);
this.setState({ currentTimer: Math.round(time / 1000)});
}, 1000);
}
stopTimer() {
this.setState({ timer: false });
console.log('stopped');
clearInterval(this.timerID);
//Clear interval here
}
ponentDidMount() {
// Make a request for a user with a given ID
axios.get('/Sample.json')
.then((response) => {
this.setState({times: response.data});
});
axios.get('/Projects.json')
.then((response) => {
this.setState({projects: response.data});
});
}
render() {
return (
<Router>
<div className="page-wrapper">
<Sidebar />
<Route exact path="/" render={() => <Dashboard times={this.state.times} timer={this.state.timer} startTimer={this.startTimer} stopTimer={this.stopTimer} currentTimer={this.state.currentTimer} addTask={this.addTask} />} />
<Route exact path="/projects" render={() => <Projects projects={this.state.projects} addProject={this.addProject} />} />
<Route exact path="/projects/show/:id" render={(props) => <Show {...props} projects={this.state.projects} />} />
</div>
</Router>
);
}
}
export default App;
I am working on a show page based on the match.params
from react router 4. For some reason, some functions runs 3 times which gives me the first 2 times undefined and the third time the result I want.
So I have this Show ponent which have the props "projects" from the main ponent. In this show ponent I do a find function on the array with the props projects inside of it. This must return a 1 item with the project.id
equel to match.params.id
. This is going good, and If I visit the page by clicking on a button (through the application) I get the page with the right project and properties (e.g. project.name
). But If I do a reload or visit the page by using an url in the browser, I get an error saying property name is undefined. If I look in the console I see that my console.logs are fired 3 times. Like I said, the first 2 are undefined and the third is giving me the result I want. I guess the first 2 are resulting in the error I get.
My Show ponent
import React from 'react'
import { Button } from 'reactstrap'
const Show = ({match, projects}) => {
let project = projects.find((project) => {
//Return project with the id equel to the match.params.id
return project.id == match.params.id;
});
//Both these console.logs runs 3 times when visiting a link by url
console.log(project); //this one gives me 2 times undefined, third time is the right item
console.log(match.params.id)
return(
<div className="container p-40">
<div className="projects-header">
{/* If I echo project.name it will gives me 'cannot read property 'name' of undefined' */}
<h2>Project '{match.params.id}' {/* {project.name} */}</h2>
<Button className="btn btn-primary" onClick={() => console.log('save')}>Save</Button>
</div>
<div className="project-edit">
</div>
</div>
);
};
export default Show;
The route in the parent ponent
<Route exact path="/projects/show/:id" render={(props) => <Show {...props} projects={this.state.projects} />} />
Someone with a good solution for this problem? I find it a problem that my users can't route by the url. I don't know why my show ponent fires 3 times either by url/reload.
Edit (Added parent ponent):
//Import react
import React, { Component } from 'react';
//Import custom ponents
import Sidebar from './ponents/js/Sidebar'
import Dashboard from './ponents/js/Dashboard'
import Projects from './ponents/js/Projects'
import Show from './ponents/js/projects/Show'
//Import styles
import './App.css';
//3rd party deps
import { BrowserRouter as Router, Route } from "react-router-dom";
import axios from 'axios'
class App extends Component {
constructor() {
super();
this.state = {
//Times / Time tracking
times: [],
timer: false,
currentTimer: 0,
//Current task
currentTask: {
id: 3,
title: '',
project_id: {
id: '',
name: '',
color: ''
},
date: '',
time_total: ''
},
//Projects
projects: []
}
this.addTask = this.addTask.bind(this);
this.startTimer = this.startTimer.bind(this);
this.stopTimer = this.stopTimer.bind(this);
this.addProject = this.addProject.bind(this);
}
addTask = (task) => {
let newArray = this.state.times.slice();
newArray.push(task);
this.setState({times: newArray, currentTimer: 0, timer: false});
clearInterval(this.timerID);
}
addProject = (project) => {
let newArray = this.state.projects.slice();
newArray.push(project);
this.setState({ projects: newArray });
}
startTimer() {
let sec = this.state.currentTimer;
const start = Date.now();
this.setState({ timer: true });
this.timerID = setInterval(() => {
let time = new Date() - (start - sec * 1000);
this.setState({ currentTimer: Math.round(time / 1000)});
}, 1000);
}
stopTimer() {
this.setState({ timer: false });
console.log('stopped');
clearInterval(this.timerID);
//Clear interval here
}
ponentDidMount() {
// Make a request for a user with a given ID
axios.get('/Sample.json')
.then((response) => {
this.setState({times: response.data});
});
axios.get('/Projects.json')
.then((response) => {
this.setState({projects: response.data});
});
}
render() {
return (
<Router>
<div className="page-wrapper">
<Sidebar />
<Route exact path="/" render={() => <Dashboard times={this.state.times} timer={this.state.timer} startTimer={this.startTimer} stopTimer={this.stopTimer} currentTimer={this.state.currentTimer} addTask={this.addTask} />} />
<Route exact path="/projects" render={() => <Projects projects={this.state.projects} addProject={this.addProject} />} />
<Route exact path="/projects/show/:id" render={(props) => <Show {...props} projects={this.state.projects} />} />
</div>
</Router>
);
}
}
export default App;
Share
Improve this question
edited Mar 7, 2018 at 10:00
Giesburts
asked Mar 7, 2018 at 9:52
GiesburtsGiesburts
7,66616 gold badges52 silver badges92 bronze badges
3
- Is the parent ponent firing multiple times? For example are you setting state in the parent ponent which causing a re-render and causing child ponents to re-render as well? It may be helpful to show the parent ponent – Stretch0 Commented Mar 7, 2018 at 9:56
-
Hmm good point! I will update my question so you can take a look. I think in my lifecycle method
ponentDidMount
. – Giesburts Commented Mar 7, 2018 at 9:59 - have updated my answer with an example of how to check if variable is set – Stretch0 Commented Mar 7, 2018 at 10:22
2 Answers
Reset to default 2You are making a couple of async requests
in your ponentDidMount
of App.js
and hence when you reload, both of these requests are fired and can return the response at different times, so two setState
calls are triggered resulting in render being called 3 times(1 at initial render and 2 after setStates)
.
At the initial render
, projects is an empty array, so you get undefined
the first time in child after find
.
It may so happen that Sample.json
load after it, causing a setState call, at this point too, projects
is an empty array and hence project
is undefined in child
Now when the Projects.json
is loaded, projects
array contains array and project will be defined
So you need to add a conditional check for project
prop
return(
<div className="container p-40">
<div className="projects-header">
{/* If I echo project.name it will gives me 'cannot read property 'name' of undefined' */}
<h2>Project '{match.params.id}' {project && project.name} */}</h2>
<Button className="btn btn-primary" onClick={() => console.log('save')}>Save</Button>
</div>
<div className="project-edit">
</div>
</div>
);
In case you have multiple properties that you want to use from project, you could destructure them like
const { name, color } = project || {}
P.S. Note that this only works for one level nesting
Looks as though when your ponent first mounts, you don't have the data required to render your app pletely.
You are making your API call which sets state which calls a re-render. You are then passing the state projects
to your show ponent. So when your show ponent first tries to render, it doesn't have access to the projects data but then when the API call has finished and the final render occurs, you have your projects data.
Wrap your find function in some conditional checking to see if projects is set
Something as simple as:
const Show = ({match, projects}) => {
if(!projects) reutrn null
let project = projects.find((project) => {
//Return project with the id equel to the match.params.id
return project.id == match.params.id;
});
...