Great Khan 2016
Great Khan 2016

Reputation: 515

Issue with rendering component

I have component where i want to search for a list of users. The issue that i am having is that my this.state.employees is not called before render. I am using the componentDidMount to get my list of employees, see below:

 componentDidMount() {
    if (!!this.props.employees && this.props.employees.length == 0) {
       this.props.listEmployees();
      }
    }

So at the moment by component is returning no employees. Does anyone know the best way of doing this.

class ShareForUsers extends Component {
  constructor(props){
    super(props);
    this.state = {
      props,
      menuOpen: false,
      value: "",
      values: []
    };
  }

    componentDidMount() {
    if (!!this.props.employees && this.props.employees.length == 0) {
       this.props.listEmployees();
      }
    }

  componentWillReceiveProps(nextProps) {
    this.setState({ ...nextProps })
  }
  render() {
    if (!!this.state.employees) return <p>no employees</p>;
    console.log(this.state.employees)
    return (
      <div>
         {persons}
        {this.renderUsers}
        <TextField
          fullWidth
          value={this.state.value}
          InputProps={{
            startAdornment: this.state.values
              .concat()
              .sort(({ label: aLabel }, { label: bLabel }) => {
                if (aLabel < bLabel) return -1;
                else if (aLabel > bLabel) return 1;
                return 0;
              })
              .map(chip => (
                <InputAdornment
                  component={Chip}
                  label={chip.label}
                  onDelete={() => {
                    const value = chip;
                    this.setState(({ values: prevValues }) => {
                      const values = prevValues;
                      const idx = values.indexOf(value);
                      if (idx === -1) {
                        values.push(value);
                      } else {
                        values.splice(idx, 1);
                      }

                      return {
                        values
                      };
                    });
                  }}
                />
              ))
          }}
          onChange={evt => {
            const value = evt.target.value;
            this.setState({
              value,
              menuOpen: value.length > 0
            });
          }}
          onFocus={() =>
            this.setState(({ value }) => ({
              menuOpen: value.length > 0
            }))
          }
          onBlur={() => this.setState({})}
        />
        <div>
          {this.state.menuOpen ? (
            <Paper
              style={{
                position: "absolute",
                zIndex: 100,
                width: "100%"
              }}
            >
            {this.state.employees
                .filter(
                  employee =>
                  employee.user.email.toLowerCase().indexOf(this.state.value) > -1
                )
                .map(employee => (
                  <MenuItem
                    key={employee.value}
                    onClick={() => {
                      this.setState(({ values: prevValues }) => {
                        const values = prevValues.concat();
                        const idx = values.indexOf(employee);
                        if (idx === -1) {
                          values.push(employee);
                        } else {
                          values.splice(idx, 1);
                        }

                        return {
                          values,
                          value: "",
                          menuOpen: false
                        };
                      });
                    }}
                  >
                    {employee.label}
                  </MenuItem>
                ))}
            </Paper>
          ) : (
            ""
          )}
        </div>
      </div>
    )
  }
}

const shareForUsers = withStyles(styles)(ShareForUsers)
export default connect(
  state => state.user,
  dispatch => bindActionCreators(actionCreators, dispatch)
)(shareForUsers);

Upvotes: 0

Views: 57

Answers (1)

dubes
dubes

Reputation: 5514

You don't need to keep the employees in your state. You can still use the this.props.employees in your render method.

Be especially wary of the componentWillReceiveProps lifecycle method, it has been marked for removal in future release of React. It was enabling many anti-patterns in eyes of React community, so they provided a replacement lifecycle getDerivedStateFromProps. I highly recommend reading the official blog post on this lifecycle method.

Some notes on your code:

constructor(props){
    super(props);
    this.state = {
      props,
      menuOpen: false,
      value: "",
      values: []
    };
}

Note 1:

You are putting the "props" in your state. This is absolutely not necessary. Wherever you need to access state, you will also have access to props. So, no benefit of putting it inside your state and potential downsides of unwanted/unexpected state changes because an unrelated prop is changing.

Note 2:

Bad initial state. If you really need (for some reason), to have employees in your state. Then you must initialize them properly in the constructor.

Note 3:

You're missing a feedback for the user that you are "loading/fetching" the data. As of now, when you render, first a user will see: No Employees Found and then when data has been fetched, they will magically see the screen.

If your flow was "synchronous" in nature, it might have been ok, but you are having an async behaviour, so you must also prepare for following states of your component:

Loading|Error|Loaded(No Data)|Loaded(data found)

Upvotes: 1

Related Questions