Evgeny Malkov
Evgeny Malkov

Reputation: 477

React.js, rerender Child Component by onChange event

I'm a newbie at React.js, but I like to study something new and stuck at this task. I have two hierarchical components, and child component doesn't rerendered when I'm updating state of parent.

I have element in parent with onChange event. When change happens I'm requesting JSON from REST and setting state at parent. Then in this method I'm calling render of child component with data property consisting of changed state. And nothing happens. It happening only ONCE. Next options selecting doesn't do nothing.

Could someone more experienced take a quick look at my code and explain me how it must be realised.

import React, { Component } from 'react';
import ReactDOM from "react-dom";

import $ from "jquery";
import './App.css';
import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
import cafeList from "./cafeList.json";

class App extends Component {
  render() {
    return (
      <div className="App container">
        <div className="App-header">
          <h2>Dining Rooms Menu </h2>
        </div>
        <CafeSelect />
      </div>
    );
  }
}

class CafeSelect extends Component{
  constructor(props) {
    super(props);
    this.state = {
      cafeList: cafeList,
      goods_for_current_cafe: []
    };
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(){
    var self = this;
    var cur_val_cafe = $('#select-cafe-type').find(":selected").val();
    console.log(cur_val_cafe);
    $.ajax({
      url: 'https://example.com/rest',
      type: 'POST',
      data: {
        auth: 'authkey',
        getcafeguide: 1,
        idpoint: cur_val_cafe,
      },
    })
    .done(function(res) {
      var data = $.parseJSON(res);
      self.setState({
        goods_for_current_cafe: data
      });

      console.log(self.state);
      ReactDOM.render(
        <GoodsGroup
          data={self.state.goods_for_current_cafe}
        />,
        document.getElementById("goods-group")
      );
    })
  }

  render() {
    return(
      <div>
        <div className="row">
            <label className="col-lg-2">Choose dining room</label>
            <div className="control-wrapper col-lg-10">
              <select className="form-control" id="select-cafe-type" onChange={this.handleChange}>
                {cafeList.map(function(cafe, index){
                      return <option key={ index } value={cafe.IDPoint}>{cafe.Point}</option>;
                })}
              </select>
            </div>
          </div>
        <div id="goods-group"></div>
        <div id="goods-list"></div>
      </div>
    )
  }
}

class GoodsGroup extends Component{
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(){
    var cur_group_val = $('#select-goods-type').find(":selected").val();
    var matches = $.grep(this.props.data, function(e) { return e.GoodsGroup == cur_group_val });
    ReactDOM.render(
      <GoodsList
        data={matches}
      />,
      document.getElementById("goods-list")
    );
  }

  componentWillMount() {
    console.log(this.props.data);
    let _ = require('underscore');
    var data = this.props.data;

    let categories = _.keys(_.countBy(data, function(data) {
      return data.GoodsGroup;
    }));
    this.setState({
       categories: categories,
       goods: this.props.data
    });

  }

  render() {

    return (
      <div className="row">
        <label className="col-lg-2">Choose goods category</label>
        <div className="control-wrapper col-lg-10">
          <select className="form-control" id="select-goods-type" onChange={this.handleChange}>
          {this.state.categories.map(function(group, index){
                    return <option key={ index } value={group}>{group}</option>;
              })}
          </select>
        </div>
      </div>
    )
  }
}


export default App;

Upvotes: 0

Views: 4261

Answers (2)

Sanjeev
Sanjeev

Reputation: 21

componentWillMount - lifecycle method will work only ONCE i.e. when mounting happens and also state changes inside componentWillMount will not trigger re-rendering Refer https://facebook.github.io/react/docs/react-component.html#componentwillmount For your scenario, the mounting has already happened during first selection.

try to use below,

Option 1: use shouldComponentUpdate and move category fetch method under render()

getCategories() {
  const data = this.props.data;
  return _.keys(_.countBy(data, (data) => data.GoodsGroup))
}

shouldComponentUpdate(nextProps) {
  return !R.equals(this.props, nextProps)// using ramda for comparison 
}

render() {
  const categories = this.getCategories()

return (
    <div className="row">
        <label className="col-lg-2">Choose goods category</label>
        <div className="control-wrapper col-lg-10">
            <select className="form-control" id="select-goods-type" onChange={this.handleChange}>
                {categories.map(function (group, index) {
                    return <option key={index} value={group}>{group}</option>;
                })}
            </select>
        </div>
    </div>
)

Option 2: else use componentWillReceiveProps()

getCategories(props) {
  const data = props.data;
  return _.keys(_.countBy(data, (data) => data.GoodsGroup))
}

componentWillMount() {
 const categories = this.getCategories(this.props)
 this.setState({
   categories: categories,
   goods: this.props.data
  })
}

componentWillReceiveProps(nextProps) {
  const categories = this.getCategories(nextProps)
  this.setState({
   categories: categories,
   goods: nextProps.data
  })
}

render() {

return (
    <div className="row">
        <label className="col-lg-2">Choose goods category</label>
        <div className="control-wrapper col-lg-10">
            <select className="form-control" id="select-goods-type" onChange={this.handleChange}>
                {this.state.categories.map(function (group, index) {
                    return <option key={index} value={group}>{group}</option>;
                })}
            </select>
        </div>
    </div>
)

Upvotes: 1

Gibby
Gibby

Reputation: 221

This is the mvp change to get this working.

In your GoodsGroup component, change your componentWillMount function to the following

prepareData() {
    console.log(this.props.data);
    let _ = require('underscore');
    var data = this.props.data;

    return _.keys(_.countBy(data, function(data) {
        return data.GoodsGroup;
    }));
}

and your render function will have to change to

render() {
    const categories = this.prepareData();
    return (
        <div className="row">
            <label className="col-lg-2">Choose goods category</label>
            <div className="control-wrapper col-lg-10">
                <select className="form-control" id="select-goods-type" onChange={this.handleChange}>
                    {categories.map(function (group, index) {
                        return <option key={index} value={group}>{group}</option>;
                    })}
                </select>
            </div>
        </div>
    )
}

Explanation

Taken from ReactDom.render,

If the React element was previously rendered into container, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React element.

Since the GoodsGroup component already exists on rerender, the componentWillMount function won't be called. So we change it so that is always called on render.

Upvotes: 1

Related Questions