React passing data from child component to parent component - reactjs

I am using a material UI Auto-suggest component and i would like to pass the full name to the parent component. This link is similar to my code https://codesandbox.io/s/ryn76v485m
The parent component is passing down the emailUser props
<SearchForUsers emailUser={this.emailUsers}/>
emailUsers = (user) => {
debugger
console.log(user + "trying to pass down from child")
}
The problem that i am having is that i cannot get the child component to pass the state correctly to the parent component.
At the moment i am doing the this.props.emailUser(this.state.values) after the mapping of the employees. The state is only change after the second person has been entered. I tried putting the this.props.emailUser into the onChange but that event does not update the state when the user clicks on the suggested name. Can anyone tell me how to do get the state back to the parent component correctly.
This is my child component.
class ShareForUsers extends Component {
constructor(props){
super(props);
this.state = {
menuOpen: false,
value: "",
values: []
};
}
componentDidMount() {
if (!!this.props.employees && this.props.employees.length == 0) {
this.props.listEmployees();
}
}
componentWillReceiveProps(nextProps) {
this.setState({ ...nextProps })
}
render() {
return (
<div>
<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}
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.props.employees
.filter(
employee =>
employee.user.email.toLowerCase().indexOf(this.state.value) > -1
)
.map(employee => (
<MenuItem
key={employee.user.id}
onClick={() => {
this.setState(({ values: prevValues }) => {
const values = prevValues.concat();
const idx = values.indexOf(employee.user.id);
if (idx === -1) {
values.push(employee.user.email);
} else {
values.splice(idx, 1);
}
return {
values,
value: "",
menuOpen: false
};
});
}}
>
{employee.user.email}
</MenuItem>
))}
</Paper>
) : (
""
)}
</div>
</div>
)
}
}
const shareForUsers = withStyles(styles)(ShareForUsers)
export default connect(
state => state.user,
dispatch => bindActionCreators(actionCreators, dispatch)
)(shareForUsers);
Thanks

In onChange event you can pass the value from SearchForUsers component to its parent by this:
onChange={evt => {
const value = evt.target.value;
this.setState({
value,
menuOpen: value.length > 0
});
this.props.emailUser(value);
}}

Related

RadioGroup Onchange is not firing in React

I am unable to find the root cause of why onChange event is not firing after changing the check, however, when component first loads onChange gets called.
I have prepared a CodeSandbox with some sample data.
CodeSandBox link
Here is the component ,
import React from "react";
import styled from "styled-components";
import { checklists } from "./checklists";
import { questions } from "./questions";
const RadioOption = styled.input`
margin: 10px;
width: 16px;
height: 16px;
`;
const RadioGroup = (props) => {
return React.Children.map(props.children, (child) => {
if (child.type === RadioOption)
return React.cloneElement(child, {
type: "radio",
defaultChecked: props.value === child.props.value,
name: props.name,
disabled: props.disabled,
onChange: props.handleChange
});
return child;
});
};
class RadioGroupDemo extends React.Component {
state = {
checklist: "test_checklist_checklist",
questions: questions,
checklists: checklists
};
constructor(props) {
super(props);
this.onRadioChange = this.onRadioChange(this);
this.keyCount = 0;
this.getKey = this.getKey.bind(this);
}
onRadioChange = (e) => {
debugger;
console.log("handleRadios", e);
//let q = this.state.questions;
};
getKey() {
return this.keyCount++;
}
//first render
render() {
console.log(this.state.checklists);
return (
<div style={{ maxHeight: "600px", overflow: "auto" }}>
{this.state.checklist &&
this.state.checklists.questions[this.state.checklist].map(
(question) => {
//debugger;
let question_id = question.question_id;
return (
<div key={`${this.state.checklist}_${question.id}`}>
{question.title}
<div style={{ width: "120px", flex: "0 0 120px" }}>
{(() => {
if (
typeof this.state.questions[question.id] !== "undefined"
) {
return (
<RadioGroup
name={`field_question_${question.id}`}
disabled={this.state.readOnly}
value={
typeof this.state.questions[question.id].answer ==
"undefined"
? this.state.questions[question.id].answer
: Object.keys(this.state.questions)
.reduce(
(arr, key) =>
arr.concat(this.state.questions[key]),
[]
)
.find((q) => {
return q.question_id === question_id;
}).answer
}
onChange={this.onRadioChange}
>
<RadioOption key={this.getKey()} value="yes" />
<RadioOption key={this.getKey()} value="no" />
<RadioOption key={this.getKey()} value="na" />
</RadioGroup>
);
}
})()}
</div>
</div>
);
}
)}
</div>
);
}
}
export default RadioGroupDemo;
Please take a look at the codesandbox and tell me why the event is not firing.
Thanks
Issue
You pass an onChange prop to RadioGroup but access a handleChange prop that doesn't exist.
const RadioGroup = (props) => {
return React.Children.map(props.children, (child) => {
if (child.type === RadioOption)
return React.cloneElement(child, {
type: "radio",
defaultChecked: props.value === child.props.value,
name: props.name,
disabled: props.disabled,
onChange: props.handleChange, // <-- accessed as handleChange
});
return child;
});
};
In component
<RadioGroup
name={`field_question_${question.id}`}
disabled={this.state.readOnly}
value={
typeof this.state.questions[question.id].answer ==
"undefined"
? this.state.questions[question.id].answer
: Object.keys(this.state.questions)
.reduce(
(arr, key) =>
arr.concat(this.state.questions[key]),
[]
)
.find((q) => {
return q.question_id === question_id;
}).answer
}
onChange={this.onRadioChange} // <-- passed as onChange
>
In RadioGroupDemo you also don't bind this to the onRadioChange handler. This binding isn't necessary though since onRadioChange is declared as an arrow function, this is bound automatically.
constructor(props) {
super(props);
this.onRadioChange = this.onRadioChange(this); // <-- here
this.keyCount = 0;
this.getKey = this.getKey.bind(this);
}
Solution
Remove attempt to bind this to handler in constructor.
constructor(props) {
super(props);
this.keyCount = 0;
this.getKey = this.getKey.bind(this);
}
Access the correct prop.
const RadioGroup = (props) => {
return React.Children.map(props.children, (child) => {
if (child.type === RadioOption)
return React.cloneElement(child, {
type: "radio",
defaultChecked: props.value === child.props.value,
name: props.name,
disabled: props.disabled,
onChange: props.onChange // <-- onChange
});
return child;
});
};
Because in the RadioOption. You are using props.handleChange
So in RadioGroupDemo, just update onChange to handleChange
<RadioGroup
...
handleChange={this.onRadioChange}
>

How to render only 5 items in react autosuggest?

I'am using react autosuggest npm package to get the json data and display it. I want to display only 5 items. How to do it?
Form.js
import React from 'react'
import Autosuggest from 'react-autosuggest';
import cities from 'cities.json';
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
);
};
const getSuggestionValue = suggestion => suggestion.name;
const renderSuggestion = suggestion => (
<div>
{console.log('suggestion', suggestion)}
{suggestion.name}
</div>
);
class Form extends React.Component {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<br/>
</div>
)
}
}
export default Form;
I want to render only 5 items, otherwise, computer hangs while loading huge data. Is there any other autocomplete react npm package, since I want only cities and country list. i.e when city is inputted, automatically the city name must be suggested with its relevant country.Any solution or suggestion highly appreciated. Thanks in advance
i modified you're getSuggestions() method a little i guess this should work for you.
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,5);
};
Use the Slice method with start index and last Index
suggestions={suggestions.slice(0, 5)}
import {
React
,Avatar
,axiosbase
} from '../../import-files';
import Autosuggest from 'react-autosuggest';
import './autosuggest.css';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Paper from '#material-ui/core/Paper';
import MenuItem from '#material-ui/core/MenuItem';
let suggestions = [ { label: 'Afghanistan' } ];
function renderInputComponent(inputProps) {
const { classes, inputRef = () => {}, ref, ...other } = inputProps;
return (
<TextField
className={classes.textField}
fullWidth
variant="outlined"
InputProps={{
inputRef: node => {
ref(node);
inputRef(node);
},
classes: {
input: classes.input,
},
}}
{...other}
/>
);
}
function renderSuggestion(suggestion, { query, isHighlighted }) {
return (
<MenuItem selected={isHighlighted} component="div">
<div>
<strong key={String(suggestion.id)} style={{ fontWeight: 300 }}>
<span className="sugg-option">
<span className="icon-wrap">
<Avatar src={suggestion.Poster}></Avatar>
</span>
<span className="name">
{suggestion.Title}
</span>
</span>
</strong>
</div>
</MenuItem>
);
}
function initSuggestions(value) {
suggestions = value;
}
function getSuggestionValue(suggestion) {
return suggestion.Title;
}
function onSuggestionSelected(event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) {
console.log('HandleSuggestion() '+suggestionValue);
}
const styles = theme => ({
root: {
height: 50,
flexGrow: 1,
},
container: {
position: 'relative',
},
suggestionsContainerOpen: {
position: 'absolute',
zIndex: 998,
marginTop: theme.spacing.unit,
left: 0,
right: 0,
overflowY: 'scroll',
maxHeight:'376%'
},
suggestion: {
display: 'block',
},
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
},
divider: {
height: theme.spacing.unit * 2,
},
});
class IntegrationAutosuggest extends React.Component {
state = {
single: '',
popper: '',
suggestions: [],
};
componentDidMount() {
initSuggestions(suggestions);
}
// Filter logic
getSuggestions = async (value) => {
const inputValue = value.trim().toLowerCase();
var _filter = JSON.stringify({
filter : inputValue,
});
return await axiosbase.post(`${apiCall}`, _filter);
};
handleSuggestionsFetchRequested = ({ value }) => {
this.getSuggestions(value)
.then(data => {
if (data.Error) {
this.setState({
suggestions: []
});
} else {
const responseData = [];
data.data.itemsList.map((item, i) => {
let File = {
id: item.idEnc,
Title: item.englishFullName +' '+item.arabicFullName,
englishFullName: item.englishFullName,
arabicFullName: item.arabicFullName,
Poster: item.photoPath,
}
responseData.push(File);
});
this.setState({
suggestions: responseData
});
}
})
};
handleSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
});
};
handleChange = name => (event, { newValue }) => {
this.setState({
[name]: newValue,
});
if(event.type=='click'){
if(typeof this.props.handleOrderUserFirstNameChange === "function"){
this.props.handleOrderUserFirstNameChange(newValue);
}
this.state.suggestions.filter(f=>f.Title===newValue).map((item, i) => {
//id
//Title
// Poster
if(typeof this.props.handleUserIDChange === "function"){
this.props.handleUserIDChange(item.id);
}
});
}
};
render() {
const { classes } = this.props;
// console.log('Re-render!!');
// console.log(this.props);
// console.log(this.state.suggestions);
const autosuggestProps = {
renderInputComponent,
suggestions: this.state.suggestions,
onSuggestionsFetchRequested: this.handleSuggestionsFetchRequested,
onSuggestionsClearRequested: this.handleSuggestionsClearRequested,
onSuggestionSelected: this.props.onSelect,
getSuggestionValue,
renderSuggestion,
};
return (
<div className={classes.root}>
<Autosuggest
{...autosuggestProps}
inputProps={{
classes,
placeholder: this.props.placeHolder,
value: this.state.single,
onChange: this.handleChange('single'),
}}
theme={{
container: classes.container,
suggestionsContainerOpen: classes.suggestionsContainerOpen,
suggestionsList: classes.suggestionsList,
suggestion: classes.suggestion,
}}
renderSuggestionsContainer={options => (
<Paper {...options.containerProps} square>
{options.children}
</Paper>
)}
/>
<div className={classes.divider} />
</div>
);
}
}
export default withStyles(styles)(IntegrationAutosuggest);

MaterialUI Spinning loader help needed for React/Redux app

import { CircularProgress, FormControl, Input, InputLabel } from
'#material-ui/core';
function toKey(s) {
return s.split("_").map((s, i) => i > 0 ? s.slice(0,1).toUpperCase() +
s.slice(1, s.length) : s).join("")
}
Function to split the returned json object:
function toLabel(s) {
return s.split("_").map((s, i) => s.slice(0,1).toUpperCase() +
s.slice(1, s.length)).join(" ")
}
My class:
class Reports extends Component {
constructor(props) {
super(props);
this.state = {
report: '',
filename: 'my-data.csv',
isLoading: false,
tableHeaderData: [],
reports: [
{ name: 'C3 Report', id: 1, actOn: 'c3'},
{ name: 'C4 Report', id: 2, actOn: 'c4'},
{ name: 'C5 Report', id: 3, actOn: 'c5'}
],
categories: {name: 'Cat 1'},
catSelection: 'Select a Category',
repSelection: 'Select Report Type',
isReportSelected: false,
c4RptFirstInput: '',
c4RptSecondInput: ''
}
}
Not sure about this but went with convention:
componentDidMount () {
const {dispatch, id} = this.props;
}
handleChange (e) {
// this.setState({ input: e.target.value });
}
This is the plugin that I'm using to convert the page into a csv file:
csvHeader () {
const data = this.reportData()
if(data.length === 0) return []
const keys = Object.keys(data[0])
return keys.map((k) => {
const label = toLabel(k)
const key = toKey(k)
return { label, key }
})
}
csvData () {
const data = this.reportData()
if(data.length === 0) return []
const values = Object.entries(data);
const keys = Object.keys(data[0])
const rows = values.map(entries => {
const record = entries[1];
return keys.reduce((acc, key, i) => {
acc[toKey(key)] = record[key]
return acc
}, {})
});
return rows
}
Checks if report or package:
reportData(){
switch(this.state.report) {
case 'channels':
return this.props.channels
case 'packages':
return this.props.packages
default:
return []
}
}
Not sure about this placeholder function but copied it from somewhere:
placeholder () {
return (
<div>
<h1 className="display-3">Reports</h1>
<p className="lead" cursor="pointer" onClick=
{this.loadChannelData}>Svc Configuration</p>
</div>
);
}
Was experimenting with this function but wasn't sure how to use it:
componentWillReceiveProps() {
}
handleCategorySwitch = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value});
console.log(`name ${name}, value ${value}`);
}
This is where the 'subselection' of the second set of drop downs happens:
handleSubselection = (e) => {
this.setState({c4RptSecondInput: e.target.value, })
switch( e.target.value) {
case 'input3':
return this.props.ReportGetAllPackages()
}
}
handleReportSwitch = (e) => {
const selectedAction = e.target.value;
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels'
,isLoading: true
}), this.props.ReportGetAllChannels)
}
if (selectedAction == 'c4') {
this.setState(prevState => ({
report: 'packages'
}))
}
}
Render function:
render () {
const {filename, reports, catSelection, repSelection, isReportSelected,
c4RptFirstInput, c4RptSecondInput} = this.state;
return (
<div className="reports">
{this.placeholder()}
<div className="flexMode">
<span className="spanFlexMode">
<InputLabel htmlFor="catSelection"></InputLabel>
<Select value={catSelection} name={'catSelection'}
onChange={(e) => this.handleCategorySwitch(e)}>
<MenuItem value="select">Select Category</MenuItem>
<MenuItem value={'Cat1'}>Cat 1</MenuItem>
<MenuItem value={'Cat2'}>Cat 2 </MenuItem>
<MenuItem value={'Cat3'}>Cat 3 </MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Report Name:</label>
<Select value={repSelection} name="repSelection"
onChange={(e) => this.handleReportSwitch(e)}>
<MenuItem defaultValue={'select'}>Select
Report</MenuItem>
{reports && reports.map((report, index) => <MenuItem
key={index} value={report.actOn}>{report.name}</MenuItem>)}
</Select>
</span>
</div>
Below are the second set of drop downs that show up conditionally based on selection of a particular field from above select boxes:
{ this.state.report === 'packages' ? (
<div>
<span>
<label>Input 1:</label>
<Select name="c4RptFirstInput" value={c4RptFirstInput}
placeholder={'Select Provider'} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'Provider'}>Provider</MenuItem>
<MenuItem value={'Region'}>Region</MenuItem>
<MenuItem value={'Zone'}>Zone</MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Input 2:</label>
<Select name="c4RptSecondInput" defaultValue=
{c4RptSecondInput} value={c4RptSecondInput} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'input2'}>Input 2</MenuItem>
<MenuItem value={'input3'}>Input 3</MenuItem>
<MenuItem value={'input4'}>Input 4</MenuItem>
</Select>
</span>
</div>
) : null}
<div>
<CSVLink data={this.csvData()} headers={this.csvHeader()}
filename={filename} target={'_blank'}>
<GetAppIcon />
</CSVLink>
Here is where the spinning loader should do it's thing and disappear once the data is loaded - currently it just keeps on spinning and the data never gets loaded even though I can see that the data has successfully come back from the reducer:
{isLoading
? <CircularProgress />
: (
<Table id="t1">
<TableHeaders data={this.csvHeader()} />
<TableContent data={this.csvData()} />
</Table>
)}
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
ReportGetAllChannels: () => dispatch(ReportGetAllChannels()),
ReportGetAllPackages: () => dispatch(ReportGetAllPackages()),
}
}
const defaultState = ({
state: {},
channels: [],
packages: []
,isLoading: false
})
const mapStateToProps = (state=defaultState) => {
return ({
state: state,
channels: state.RptDetailsReducer.data,
packages: state.RptPackagesReducer.data
,isLoading: false
})
}
isLoading variable is not defined in your render method. I see that you defined it in your component's state and inside your reducer. I assume you are referencing one in your state (Since you said it was keep spinning it is probably the case). You set component's isLoading to true in handleSubselection you have this snippet:
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels',
isLoading: true
}), this.props.ReportGetAllChannels)
}
This code will set isLoading to true than dispatch ReportGetAllChannels. However your component's state won't be updated. I don't know what ReportGetAllChannels does but I am guessing it sets its own isLoading to false. Which is different variable.
Also you may want to read this https://overreacted.io/writing-resilient-components/#principle-1-dont-stop-the-data-flow. Once you map your state to props you usually want to pass them directly to child components.
Edit:
Quick fix: use this.props.isLoading instead of state, and set isLoading to true inside your dispatched action

How to iterate children in React using render props

I have this pseudo code for my form. Where I would like to display just fields with canAccess=true.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
<div className="nestedItem">
<Field name="firstName" />
</div>
<Field name="surName" />
</>
}
</Form>
With this code I would like to see rendered just field with firstName.
I know that I can iterate through React.Children.map() but I don't know how to iterate children when using render props.
Also there can be nested elements, so I would like to find specific type of component by name.
Thanks for help.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
{
Object.keys(props.initialValues).map(k => (
k.canAccess && <Field name={k} />
));
}
</>
}
</Form>
Edit: Your form can perform some logic and pass back filtered items to your component.
getFilteredItems = items => Object.keys(items).reduce((acc, key) => {
const item = items[key];
const { canAccess } = item;
if(!canAccess) return acc;
return {
...acc,
[key]: item
}
}, {}));
render() {
const { initialValues, children } = this.props;
const filteredItems = this.getFilteredItems(initialValues);
return children(filteredItems);
}
<Form initialValues={initialValues}>
{ props =>
<>
{
Object.keys(props).map(k => <Field name={k} />)
}
</>
</Form>
This is what I was looking for.
const Form = ({initialValues, children}) =>
props =>
<Authorized initialValues={initialValues}>
{typeof children === 'function' ? children(props) : children}
</Authorized>
const Authorized = ({initialValues, children}) => {
// Do check
React.Children.map(chidlren, x => {
if(x.type === Field ) // && initialValues contains x.props.name
return x
return null
... })
}

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);
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)

Resources