3

I am creating a master-detail navigation just like the example here the react router example;

I have a container component OneCheckpointContainer, which renders a navigation panel CheckpointNav (the links), and a data component OneCheckpointData (this guy fetchs data upon load, and renders OneCheckpointView.

When I click one of the links in CheckpointNav nothing happens except the URL in the browser changes. It is not until I refresh the page that new data is fetched, and a new component is then rendered in the children view. (there are also no errors)

I am not sure if this bug is because the child component is also responsible for fetching data, as well as the view.

Here is how I have the routes setup:

  <Route path="/modules/:id" component={OneCheckpointContainer}>
    <Route path="/modules/:id/checkpoints/:cp_id" component={OneCheckpointData} />
  </Route>

OneCheckpointContainer

import React from 'react';
import CheckpointNav from './CheckpointNav';

class OneCheckpointContainer extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      checkpoints: null,
      user: null
    };

  }

  componentWillMount() {
    this.loadCheckpoints();
    this.context.getUser((data) => this.setState({ user: data }));
  }

  loadCheckpoints() {
    $.ajax({
      url: `/api/v1/modules/student/${this.props.params.id}/checkpoints`,
      method: 'GET',
    }).done((data) => {
      this.setState({ checkpoints: data });
    });
  }

  render() {
    return (
      <div>
        <div className="col-xs-3">
          <CheckpointNav mid={this.props.params.id} checkpoints={this.state.checkpoints} />
        </div>
        <div className="col-xs-9">
          { this.props.children || <div>No Children Yet</div>}
        </div>
      </div>
    )
  }
}

OneCheckpointContainer.displayName = OneCheckpointContainer;

OneCheckpointContainer.contextTypes = {
  getUser: React.PropTypes.func.isRequired
};

export default OneCheckpointContainer;

Here is CheckpointNav though I don't believe the bug is in here:

import React from 'react';
import NavLink from '../widgets/NavLink';

const CheckpointNav = (props) => {
  const checkpointList = props.checkpoints && props.checkpoints.length > 0 ?
    props.checkpoints.map((item) => {
      return <li key={item.cp._id} className="list-group-item">
      <NavLink className="nav-link" to={"/modules/" + props.mid + "/checkpoints/" + item.cp._id}>
      { item.cp.title}</NavLink></li>
    }) : <div>No CPS</div>;
  return (
    <div className="card one-module-card">
      <div className="card-block modules-card-body">
        <ul className="list-group tags-group">
          { checkpointList }
          <li className="list-group-item new-cp"><NavLink className=""
            to={'/post/checkpoint/' + props.mid}
          >
            New Checkpoint
          </NavLink></li>
        </ul>
      </div>
    </div>
  );

};

export default CheckpointNav;

OneCheckpointData doesn't fetch new data and render new unless I refresh

import React from 'react';
import CheckpointView from './CheckpointView';

class OneCheckpointData extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      checkpoint: null,
      user: null
    };

  }

  componentWillMount() {
    this.loadCheckpoint();
    this.context.getUser((data) => this.setState({ user: data }));
  }

  loadCheckpoint() {
    $.ajax({
      url: `/api/v1/modules/three/cp/${this.props.params.cp_id}`,
      method: 'GET',
    }).done((data) => {
      this.setState({ checkpoint: data });
    });
  }

  render() {
    return this.state.checkpoint ? <CheckpointView user={this.state.user} checkpoint={this.state.checkpoint} /> : null;
  }
}

OneCheckpointData.displayName = OneCheckpointData;

OneCheckpointData.contextTypes = {
  getUser: React.PropTypes.func.isRequired
};

export default OneCheckpointData;

** I have left out OneCheckpointView since it should be irrelavant **

laser
  • 1,378
  • 13
  • 13
fresh5447
  • 1,072
  • 2
  • 12
  • 25

4 Answers4

6

In, that case you can get the props as the parameter of loadCheckpoing(props) function and you can send the new props on the componentWillReceiveProps just like below

componentWillMount(this.props) {
  this.loadCheckpoint(this.props);
}

componentWillReceiveProps(nextprops) {
  this.loadCheckpoint(nextprops);
}

loadCheckpoint(props) {
  this.context.getUser((data) => this.setState({ user: data }));
  $.ajax({
    url: `/api/v1/modules/three/cp/${props.params.cp_id}`, //please check i have replace the this.props to props as we have props as a parameter
    method: 'GET',
  }).done((data) => {
    this.setState({ checkpoint: data });
  });
}

i will prefer to use react-redux or reflux library to handle your ajax data query but my proposed solution should do the job for you.

Md.Estiak Ahmmed
  • 1,353
  • 7
  • 11
  • I think I understand what you are going for, but you can not pass this.props in as an arg to componentWillMount – fresh5447 Aug 31 '16 at 22:09
1

The problem is that your components are fetching data only before mounting. This only works for the first time they are displayed. These components also need to fetch their data whenever they receive new properties, e.g. ids from the router after navigating to a new location.

In order to do so, the simplest way is to add a componentWillReceiveProps that compares the previous props with the new one and fetches the new data if required.

An alternative way is to add an onEnter handler in your Routes configuration to fetch the data whenever a route is changed.

Check this question's answers to see examples of both approaches.

Community
  • 1
  • 1
Manolo Santos
  • 1,820
  • 1
  • 14
  • 23
1

I think this is because you have invoked the function loadcheckpoints in componentwillMount. This is be invoked only once in the component lifecycle. Since the component is already mounted when you had clicked a link in the nav bar, the compoenntwillmount will not be invoked.Hence, no fetch of data occurs.

I guess, you should try the following approach. The onclick function of the links in CheckpointNav should be passed from OneCheckpointContainer . The loadeddata should be set to state in the container which will pass on the data to OneCheckpointData as props .

bhattshru
  • 29
  • 3
1

Two steps to diagnosis:

  1. check whether your method loadCheckpoint has been called every time when you click different link. Just leave one line like console.log(new Date().toLocaleTimeString()) in that method

  2. if step 1 success, then check whether there is a this reference issue in this part: (data) => { this.setState({ checkpoint: data }); }

BTW, you'd better put the ajax call in componentDidMount method. Refer to: https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount

Max Peng
  • 2,055
  • 1
  • 19
  • 34