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)
Related
I have Virtualized List initial render record up to 30 ,while render the data list automatically re render 2 to 4 times and also the new data added to the list
while rendering multi times we can't able to do any action like touch or navigate to another screen
My Code
class HomeDetails extends PureComponent {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = data => {
return data.length;
};
_renderItem =({item,index}) => {
console.log(
'Rerendring',
item.accountId,
moment().format('MM/DD/YY hh:mm:ss a'),
);
return (
<View key={index} style={{height: 50, flexDirection: 'row'}}>
<Text>{`${item.accountId} ${moment().format(
'MM/DD/YY hh:mm:ss a',
)}`}</Text>
</View>
);
}
render(){
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={val => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
)
}
}
const mapStateToProps = ({post, auth, common}) => {
const {
responseRecord,
postLoading,
refreshData,
} = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);
..........................................................................
1.For initial 30 record rendering its re-render more that 2 times
2.when add more records its re-render more than 4 to 6 times
3.tried with purecomponent but no luck
code deployed in snack
https://snack.expo.dev/#pandianvpsm/cd5737
Internal, React's PureComponent implements the shouldComponentUpdate method and compares previous props values and new props or state values to avoid unnecessary re-renders.
This works well for primitive type values like numbers, strings, and booleans.
For referential types values (objects and arrays), this comparison is not always accurate. This is the behavior you have. this.props.responseRecord is an array of objects (referential types).
We can solve this problem by implementing our own componentShouldUpdate method as below:
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord.length
}
Full code below
class HomeDetails extends React.Component {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord;
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = (data) => {
return data.length;
};
_renderItem = ({ item, index }) => {
console.log(
"Rerendring",
item.accountId,
moment().format("MM/DD/YY hh:mm:ss a")
);
return (
<View key={index} style={{ height: 50, flexDirection: "row" }}>
<Text>{`${item.accountId} ${moment().format(
"MM/DD/YY hh:mm:ss a"
)}`}</Text>
</View>
);
};
render() {
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={(val) => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
);
}
}
const mapStateToProps = ({ post, auth, common }) => {
const { responseRecord, postLoading, refreshData } = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);
I am pulling categories and sub categories from an API which is working as expected. I have them in an accordion using expanded:
constructor(props) {
super(props);
this.state = {
...
expanded : false,
}
}
Then rendering categories and sub categories:
renderFilter=()=> {
const items = [];
for (let item of this.state.docCats) {
if( item.subCats) {
docItems = item.subCats.map(row => {
return<TouchableOpacity key={row.doc_sc_id} onPress={()=>this.FilterRequest(item.doc_cat_id, row.doc_sc_id)}><Text style={styles.filterCat}>{row.doc_sc_title}</Text></TouchableOpacity>
})
}
items.push(
<View key={item.doc_cat_id}>
<TouchableOpacity onPress={()=>this.FilterRequest(item.doc_cat_id, '')}>
<Text style={styles.filterCat}>{item.doc_cat_title}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={()=>this.toggleExpand()}>
<FontAwesome name={this.state.expanded ? 'chevron-up' : 'chevron-down'} size={20} color="#000" />
</TouchableOpacity>
{this.state.expanded &&
docItems
}
</View>
);
}
return items;
}
using a Toggle:
toggleExpand=()=>{
this.setState({expanded : !this.state.expanded})
}
My problem is when i use the toggleExpand it expands all the accordions at the same time. Is there a way i can open them individually by targetting the index or id?
I would prefer not to use a different class/component as i need access to the other functions and fetch requests that i have.
You should add a property to each docCats item that indicates whether its accordion should be expanded or not, then pass an id to the toggleExpand function to update this property for a specific item and the docCats array that's stored in your state. Use the expanded property of each item inside of the renderFilter function to indicate whether the accordion should be expanded or not.
Here's a simplified example, but should give you a good idea how to rewrite your code to make it work:
const items = Array.from({ length: 5 }).map((_, idx) => ({
id: idx,
title: `Accordion ${idx + 1}`
}));
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
items: items.map(item => ({
...item,
expanded: false
}))
};
}
toggleExpand = id => {
const nextItems = [...this.state.items].map(item => {
if (item.id === id) {
return {
...item,
expanded: !item.expanded
};
}
return item;
});
this.setState({
items: nextItems
});
};
renderFilter = () => {
return this.state.items.map(item => (
<div
style={{ border: `1px solid black`, width: `100`, height: `100` }}
onClick={() => this.toggleExpand(item.id)}
key={item.id}
>
<p>
{item.title} {item.expanded ? `is expanded` : `is not expanded`}
</p>
</div>
));
};
render() {
const renderedFilter = this.renderFilter();
return <div>{renderedFilter}</div>;
}
}
If you'd like to see it in action, simply click on each box to change the expanded state, here's the link to a working demo:
CodeSandbox
I have a custom component that uses Apollo and React-Select and has two mutations (see below). The react-select is multivalue and needs to be custom because I need an "isSelected" checkbox on it. Not shown in this code, but the initial options list is passed in from the parent container.
The parent Select and its mutation work as expected. However, I'm running into a couple of odd problems with the custom MultiValueContainer. The first is that the first time I select any of the check-boxes, I get an error saying that "Can't call setState (or forceUpdate) on an unmounted component." Note below that I have an empty componentWillUnmount function just to see if it gets called, which it doesn't. But (I assume) as a result of that, when the "toggleThing" mutation is called, the state doesn't have the vars necessary to complete the request. The second time I click it works as expected, with the exception of the second issue.
The second issue is that the onCompleted function on the MultiValueContainer mutation never fires, so even though the server is returning the expected data, it never seems to get back to the mutation and therefore never to the component. The onCompleted function on the parent Select works as expected.
Thanks in advance for any insights anyone might have. Perhaps needless to say, I am relatively new to react/apollo/react-select and apologize in advance for any newbie mistakes. Also, I've tried to scrub and simplify the code so apologies also for any renaming mistakes.
const UPDATE_THINGS = gql`
mutation UpdateThings(
$id: ID!
$newThings: [ThingInput]
) {
updateThings(
id: $id
newThings: $newThings
) {
id
}
}
`;
const TOGGLE_THING = gql`
mutation ToggleThing($id: ID!, $isChecked: Boolean) {
toggleThing(
id: $id
isChecked: $isChecked
) {
id
}
}
`;
class ThingList extends Component {
stylesObj = {
multiValue: base => {
return {
...base,
display: 'flex',
alignItems: 'center',
paddingLeft: '10px',
background: 'none',
border: 'none'
};
}
};
constructor(props) {
super(props);
this.state = {
selectedThings: [],
selectedThingId: '',
selectedThingIsChecked: false
};
}
onUpdateComplete = ({ updateThings }) => {
console.log('onUpdateComplete');
console.log('...data', updateThings );
this.setState({ selectedThings: updateThings });
};
onToggleThing = (thingId, isChecked, toggleThing) => {
console.log('onToggleThing, thingId, isChecked');
this.setState(
{
selectedThingId: thingId,
selectedThingIsChecked: isHighPisCheckedoficiency
},
() => toggleThing()
);
};
onToggleThingComplete = ({ onToggleThing }) => {
console.log('onToggleThingComplete ');
console.log('...data', onToggleThing );
this.setState({ selectedThings: onToggleThing });
};
handleChange = (newValue, actionMeta, updateThings) => {
this.setState(
{
selectedThings: newValue
},
() => updateThings()
);
};
isThingSelected = thing=> {
return thing.isSelected;
};
getSelectedThings = selectedThings => {
console.log('getSelectedSkills');
return selectedThings ? selectedThings.filter(obj => obj.isSelected) : [];
};
componentWillUnmount() {
console.log('componentWillUnmount');
}
render() {
const self = this;
const MultiValueContainer = props => {
// console.log('...props', props.data);
return (
<Mutation
mutation={ TOGGLE_THING }
onCompleted={self.onToggleThingComplete}
variables={{
id: self.state.selectedThingId,
isChecked: self.state.selectedThingIsChecked
}}>
{(toggleThing, { data, loading, error }) => {
if (loading) {
return 'Loading...';
}
if (error) {
return `Error!: ${error}`;
}
return (
<div className={'option d-flex align-items-center'}>
<input
type={'checkbox'}
checked={props.data.isChecked}
onChange={evt => {
self.onToggleThing(
props.data.id,
evt.target.checked,
toggleIsHighProficiency
);
}}
/>
<components.MultiValueContainer {...props} />
</div>
);
}}
</Mutation>
);
};
return (
<Mutation
mutation={UPDATE_THINGS}
onCompleted={this.onUpdateComplete}
variables={{ id: this.id, newThings: this.state.selectedThings}}>
{(updateThings, { data, loading, error }) => {
if (loading) {
return 'Loading...';
}
if (error) {
return `Error!: ${error}`;
}
return (
<div>
<Select
options={this.props.selectedThings}
styles={this.stylesObj}
isClearable
isDisabled={this.props.loading}
isLoading={this.props.loading}
defaultValue={this.props.selectedThings.filter(
obj => obj.isSelected
)}
isOptionSelected={this.isOptionSelected}
isMulti={true}
onChange={(newValue, actionMeta) =>
this.handleChange(
newValue,
actionMeta,
updateThings
)
}
components={{
MultiValueContainer
}}
/>
</div>
);
}}
</Mutation>
);
}
}
export default ThingsList;
You are redefining MultiValueContainer on every render, which is not a good practice and may cause unexpected behavior. Try moving it into separate component to see if it helps.
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);
}}
I'm trying to get a FormWarning to display when users input incorrect information, but it seems to have disappeared on me. I'm trying to control whether or not it displays with this.state.formWarning.display - when the validateInputs function runs, if it determines an input is invalid, it should change the value of display from 'none' to 'block'. I'm trying to set the style for the Component to having a display that matches this.state.formWarning.display, but I am getting an error. Is my belief that you can set the styles for a component inline via an object not correct? Getting bugs regardless. ie
export default class FormOne extends React.Component {
constructor(props) {
super(props)
this.state = {
formOne: {
shippingAddress: {
firstName: '',
lastName: '',
address1: '',
city: '',
state: '',
zip: '',
country: 'US'
},
phone: '',
email: ''
},
formWarning: {
text: '',
invalidInputID: '',
display: 'block'
},
isSubmitted: false,
submitting: false
}
this.styles = this.props.styles || {}
}
componentWillReceiveProps(nextProps) {
if(nextProps.state.stepOne &&
nextProps.state.stepOne.formOneResponse) {
let formOneResponse = nextProps.state.stepOne.formOneResponse
formOneResponse.status === "delayed" || formOneResponse.status === "success"
? this.setState({isSubmitted: true})
: alert(formOneResponse.errorMessage)
this.setState(state => ({submitting: false}))
}
}
validateInputs = (inputs) => {
let { email, phone, shippingAddress } = inputs,
shippingKeys = Object.keys(shippingAddress)
console.log('validate inputs is firing')
for(let i = 0; i < Object.keys(shippingAddress).length; i++) {
let key = shippingKeys[i], input = shippingAddress[key]
if(!input) {
return this.showFormWarning(key)
}
}
if(!phone) return this.showFormWarning('phone')
if(/\S+#\S+\.\S+/.test(email)) return
this.showFormWarning('email')
return true
}
showFormWarning = key => {
clearTimeout(this.warningTimeout)
console.log('showformwarnign is firing')
this.setState(state => ({
formWarning: {
...state.formWarning,
text: 'Please fill out this field',
invalidInputID: key,
display: 'block'
}
}))
this.warningTimeout = setTimeout(() => {
this.setState(state => ({
formWarning: {
...state.formWarning,
display: 'none'
}
}))
}, 5000)
return false
}
saveInputVal = (event) => {
let { formOne: tempFormOne } = this.state,
input = event.currentTarget
console.log('saveinputvals is firing')
if(input.name === 'phone' || input.name === 'email') {
this.setState(state => ({
formOne: {
...state.formOne,
[input.name]: input.value
}
}))
} else {
this.setState(state => ({
formOne: {
...state.formOne,
shippingAddress: {
...state.formOne.shippingAddress,
[input.name]: input.value
}
}
}))
}
}
submit = (event) => {
event.preventDefault()
if(!this.validateInputs(this.state.formOne)) return
this.setState(state => ({submitting: true}))
this.props.saveShippingData(this.state.formOne)
this.props.stepOneSubmit(this.state.formOne)
}
render() {
if (this.state.isSubmitted) return <Redirect to="/order" />
let CustomTag = this.props.labels ? 'label' : 'span',
{ inputs, saveInputVal, styles, state } = this,
{ formWarning, submitting } = state,
{ invalidInputID, text, display } = formWarning
return (
<div style={this.styles.formWrapper}>
{
typeof this.props.headerText === 'string'
? ( <h2 style={this.styles.formHeader}>
{this.props.headerText}</h2> )
: this.props.headerText.map((text) => {
return <h2 key={text} style={this.styles.formHeader}
className={'header'+this.props.headerText.indexOf(text)}>{text}</h2>
})
}
<form onSubmit={this.submit} style={this.styles.form}>
<FormOneInputs inputs={inputs} saveInputVal={saveInputVal}
CustomTag={CustomTag} styles={styles} />
<button style={this.styles.button}>{this.props.buttonText}
</button>
</form>
<Throbber throbberText='Reserving your order...' showThrobber=
{submitting} />
<FormWarning style={display: {this.state.formWarning.display}} invalidInputID={invalidInputID} text={text}/>
</div>
)
}
}
You don't need to set any CSS class. The approach is as follows:
(1) Given a component you want to render or not render depending on a variable
(2) Make a helper method that checks for the condition and returns the actual component if you want it rendered. Otherwise, do nothing (basically returns undefined)
(3) Call that method from wherever you want the component to possibly appear.
Concrete example:
class FormOne extends React.Component {
// (...) all other things omitted to focus on the problem at hand
renderFormWarning() {
if (formIsInvalid) {
return <FormWarning ... />;
}
// else won't do anything (won't show)
}
render() {
return (
{/* ... */}
{this.renderFormWarning()}
);
}
}
In the above example, replace formIsInvalid with some statement that will tell you if the form is invalid. Then, if that condition is true, it will return the FormWarning component. Otherwise, no form warning will be shown. From the render() method, all you need do is call that helper method.