最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Component functions runs multiple times react - Stack Overflow

programmeradmin9浏览0评论

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
Add a ment  | 

2 Answers 2

Reset to default 2

You 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;
    });

...
发布评论

评论列表(0)

  1. 暂无评论