1

I want to load all companies via AJAX request into a state property when the user clicks on the select box.

This is the code:

import React, { Component } from 'react';
import SelectOption from './SelectOption';

class CreateFreightEntryModal extends Component {

    constructor(props) {
        super(props);
        this.state = {
            freights: props.freights,
            onClose: props.onClose,
            onClick: props.onClick,
            companies: [],
        };
    }

    loadCompanies(event) {
        $.ajax({
            type: "POST",
            context:this,
            dataType: "json",
            async: true,
            url: "../data/get/json/companies",
            data: ({ 
                _token : window.Laravel.csrfToken,
            }),
            success: function (data) {
                var arr = $.map(data, function(el) { return el; });
                this.setState({
                    companies: arr
                })
            }
        });
    }

    render() {
        return (
            <div className="modal fade" id="createFreightEntryModal" tabIndex="-1" role="dialog">
                <div className="modal-dialog" role="document">
                    <div className="modal-content">
                        <div className="modal-header">
                            <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                            <h4 className="modal-title">New freight entry</h4>
                        </div>
                        <div className="modal-body">
                            <div>
                                <div>
                                    <form onSubmit={this.add.bind(this)}>
                                        <div className="row">
                                            <div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
                                                <strong>Create a new freight entry:</strong>
                                            </div>
                                        </div>
                                        <div className="row">
                                            <div className="col-xs-4 col-sm-4 col-md-4 col-lg-4">
                                                Company
                                            </div>
                                            <div className="col-xs-8 col-sm-8 col-md-8 col-lg-8">
                                                <div className="form-group" onClick={this.loadCompanies.bind(this)}>
                                                    <select className="selectpicker show-tick form-control" data-live-search="true" data-title="Please select" ref="Firma" required>
                                                    {
                                                        this.state.companies.map((company)=> {
                                                          return (
                                                            <SelectOption value={company.Nummer} displayValue={company.Bezeichnung} key={company.id} />
                                                          );
                                                        })
                                                    }
                                                    </select>
                                                </div>
                                            </div>
                                        </div>

                                        <div className="row">
                                            <div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
                                                <div className="form-group">
                                                    <button type="submit" className="btn btn-success"><span className="glyphicon glyphicon-floppy-disk"></span> Save </button>
                                                </div>
                                            </div>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                        <div className="modal-footer">
                            <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

export default CreateFreightEntryModal

When I add the componentWillReceiveProps(nextProps) method, I get this error. This error occurs when the page is loaded and not when I click on the select box!

componentWillReceiveProps(nextProps) {
    this.setState({
        companies: nextProps.companies,
    });
}

TypeError: this.state.companies is undefined

This is the part where the error occurs:

...
this.state.companies.map((company)=> {
...

How can I solve this issue? Thanks for your help in advance.

dns_nx
  • 3,651
  • 4
  • 37
  • 66
  • Do a `console.log( data )` in the `success` callback to see what you actually get from the backend. – pawel Aug 03 '17 at 15:16
  • 1
    Obviously, because `nextProps.companies` is `undefined`. Since you don't show us where you're using the component, we can't help you more than that, because we can't see the props you're using when you use it. Remember: props are not state. In fact, [the documented purpose](https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops) of `componentWillReceiveProps` is to give you a chance to make props state if you want to. – T.J. Crowder Aug 03 '17 at 15:16
  • @pawel: It won't be there, because that's **state**, not props. – T.J. Crowder Aug 03 '17 at 15:16
  • You can see all used props in the constructor. @pawel I forgot to tell that this error occurs when the page is loaded and not even when I click on the select box! – dns_nx Aug 03 '17 at 15:20
  • Oooh it may be simpler than I thought. The success callback has the `jQuery` scope, should have been defined as `success: data => { this.setState() }` – pawel Aug 03 '17 at 15:21
  • Check `console.log(arr)` before `setState` in success. – Andrii Starusiev Aug 03 '17 at 15:23
  • @Andrew The error comes up when the page loads, not when the ajax result is loaded. – dns_nx Aug 03 '17 at 15:25
  • @dns_nx is `nextProps.companies` the same `arr` in ajax, as you expected? Or from where it come? – Andrii Starusiev Aug 03 '17 at 15:29

1 Answers1

2

Using this construct:

componentWillReceiveProps(nextProps) {
    this.setState({
        companies: nextProps.companies,
    });
}

you update state.companies every time the component receives ANY props, even when there are no companies in the props. And when the nextProps don't have companies it is set to undefined.

Let's illustrate it this way:

{
  let props = { freights : [ 'a', 'b', 'c' ] }
  this.setState({ companies : props.companies }) 
  /* state.companies are now undefined, because props.companies are undefined */
}

Fix:

componentWillReceiveProps(nextProps) {
    if( nextProps.companies ){
      this.setState({
          companies: nextProps.companies,
      });
    }
}

BTW the problem with success callback scope I have mentioned in a comment may still apply.

pawel
  • 35,827
  • 7
  • 56
  • 53
  • Ok, thanks. The error has gone now, but the companies are still not added to my select box. I changed also the jquery scope. The ajax result seems to be okay. But this maybe another problem. Anyway, thanks for your help. – dns_nx Aug 03 '17 at 15:31
  • The ajax problem may be related to `this` not being the component in the `success` callback, because in jQuery it's the `jqXHR` request object. So you try to execute `setState` on the wrong thing. Refactor the success callback to an arrow function. – pawel Aug 03 '17 at 15:33
  • On that ajax problem: http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback – T.J. Crowder Aug 03 '17 at 15:34
  • 1
    In the jquery ajax i set `context: this`. I've just checked that. In the `success` I see als props, refs and state variables, so I assume this is the correct `this`. – dns_nx Aug 03 '17 at 15:41
  • @dns_nx sorry, I missed that you set the context. In this case I really have no better idea than trying to force rendering after the state is set, i.e. `this.setState({ companies : arr }, this.render)` and see what happens. – pawel Aug 03 '17 at 15:45
  • It seems to be a problem with the bootstrap-select box. I can see that in the original `select` box all entries are there, but I think, I have to re-render the bootstrap-select box. – dns_nx Aug 04 '17 at 09:41