Reputation: 515
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
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