React , setState(...):, called setState() on an unmounted component ???
setState(...): This usually means you called set state() on an unmounted component.
Warning:
setState(...): Can only update a mounted or mounting component.
This usually means you called set state() on an unmounted component.
This is a no-op. Please check the code for the TestModal component.
I just can't figure out, what's wrong with this?
```jsx
class TestModal extends Component {
constructor(props, context) {
super(props, context);
this.state = {
loading: false,
visible: true
};
}
onTest = () => {
// copy state, do test fetch
this.testOK();
};
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields(
(err, values) => {
if (!err) {
console.log('Received values of form: ', values);
console.log('fetch url = \n', values.textarea);
// fetch data
let url = values.textarea;
this.props.checkTestCommands(url);
this.onTest();
}else{
throw new Error(Error.name, Error.message);
}
}
);
};
testOK = () => {
this.setState({
loading: true
});
this.props.hideModal();
setTimeout(() => {
this.setState({
loading: false,
visible: false
});
}, 1000);
}
testCancel = () => {
this.setState({
visible: false
});
this.props.hideModal();
}
render() {
const {getFieldsValue, getFieldValue, setFieldsValue, getFieldDecorator} = this.props.form;
const formItemLayout = {
labelCol: {
span: 6
},
wrapperCol: {
span: 14
}
};
return (
<div>
{/* query object */}
{/* no footer */}
<Modal
title="命令行"
onOk={this.testOK}
onCancel={this.testCancel}
visible={this.state.visible}
footer={[]}
>
{/* loading={this.state.loading} */}
<Form
onSubmit={this.handleSubmit}
layout="horizontal">
<FormItem
label="测试命令行"
hasFeedback
{...formItemLayout}>
{
getFieldDecorator('textarea', {
rules: [
{
required: false,
message: ' url 长度必须 30 个字符之间'
}
],
initialValue: `http://10.1.5.31:8080/http/report/query?{"ApiName":"JY.Topic.Market_profile.Investors_data_statistics.AccountStatistics"}`,
})(
<Input
type="textarea"
placeholder="请先点击 “开始测试” 按钮!"
rows="10"
cols="70"
/>
)
}
</FormItem>
{/* onClick={this.props.checkTestCommands} */}
<FormItem style={{textAlign: "center"}}>
<Button
onClick={this.onTest}
type="primary"
htmlType="submit"
icon="hourglass"
style={{margin: "auto 10px"}}
loading={this.state.loading}>
开始测试
</Button>
<Button
onClick={this.testCancel}
size="large"
style={{margin: "auto 10px"}}
icon="close">
关闭
</Button>
{/* ref="submit_btn" */}
</FormItem>
</Form>
</Modal>
</div>
);
}
}
```
this is a new version code, but it still doesn't work?
class TestModal extends Component {
constructor(props, context) {
super(props, context);
this.state = {
loading: false,
visible: true
};
}
onTest = () => {
// copy state, do test fetch
this.testOK();
};
/* componentWillUnmount(){
this.props.hideModal();
this.setState({
loading: false,
visible: false
});
} */
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields(
(err, values) => {
if (!err) {
console.log('Received values of form: ', values);
console.log('fetch url = \n', values.textarea);
// fetch data
let url = values.textarea;
this.props.checkTestCommands(url);
this.onTest();
}else{
throw new Error(Error.name, Error.message);
}
}
);
};
testOK = () => {
this.setState({
loading: true
});
setTimeout(() => {
this.setState({
loading: false,
visible: false
});
this.props.hideModal();
}, 1000);
}
testCancel = () => {
this.setState({
visible: false
});
this.props.hideModal();
}
render() {
const {getFieldsValue, getFieldValue, setFieldsValue, getFieldDecorator} = this.props.form;
const formItemLayout = {
labelCol: {
span: 6
},
wrapperCol: {
span: 14
}
};
return (
<div>
{/* query object */}
{/* no footer */}
<Modal
title="命令行"
onOk={this.testOK}
onCancel={this.testCancel}
visible={this.state.visible}
footer={[]}
>
{/* loading={this.state.loading} */}
<Form
onSubmit={this.handleSubmit}
layout="horizontal">
<FormItem
label="测试命令行"
hasFeedback
{...formItemLayout}>
{
getFieldDecorator('textarea', {
rules: [
{
required: false,
message: ' url 长度必须 30 个字符之间'
}
],
initialValue: `http://10.1.5.31:8080/http/report/query?{"ApiName":"JY.Topic.Market_profile.Investors_data_statistics.AccountStatistics"}`,
})(
<Input
type="textarea"
placeholder="请先点击 “开始测试” 按钮!"
rows="15"
cols="500"
/>
)
}
</FormItem>
{/* onClick={this.props.checkTestCommands} */}
<FormItem style={{textAlign: "center"}}>
<Button
onClick={this.onTest}
type="primary"
htmlType="submit"
icon="hourglass"
style={{margin: "auto 10px"}}
loading={this.state.loading}>
开始测试
</Button>
<Button
onClick={this.testCancel}
size="large"
style={{margin: "auto 10px"}}
icon="close">
关闭
</Button>
{/* ref="submit_btn" */}
</FormItem>
</Form>
</Modal>
</div>
);
}
}
a screencut gif!
In your testOK method you are calling setState after 1sec delay when the TestModal form is already closed (that is the component is already unmounted). This is a noop.
this is where you have the problem
this.props.hideModal();
setTimeout(() =>
{
this.setState({
loading: false,
visible: false
});
}, 1000);
you hide the modal and after 1 second you try to change the state
here is one of my solutions!
Hooray, it works now! Just need to remove the duplicate set visible=false, it should be always visible=true;
because it should be call parent component's methods to realize show/hide itself!
Thanks to everybody, who made his answer to this question!
```jsx
class TestModal extends Component {
constructor(props, context) {
super(props, context);
this.state = {
loading: false,
visible: true
};
}
onTest = () => {
// copy state, do test fetch
this.testOK();
};
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields(
(err, values) => {
if (!err) {
console.log('Received values of form: ', values);
console.log('fetch url = \n', values.textarea);
// fetch data
let url = values.textarea;
this.props.checkTestCommands(url);
// this.onTest();
}else{
throw new Error(Error.name, Error.message);
}
}
);
};
testOK = (e) => {
// e.preventDefault();
console.log(`testOK e`, e);
this.setState({
loading: true
});
setTimeout(() => {
this.setState({
loading: false
});
this.props.hideModal();
}, 1000);
}
testCancel = () => {
this.props.hideModal();
}
render() {
const {getFieldsValue, getFieldValue, setFieldsValue, getFieldDecorator} = this.props.form;
const formItemLayout = {
labelCol: {
span: 6
},
wrapperCol: {
span: 14
}
};
// destructuring assignment
const {visible, loading} = this.state;
return (
<div>
{/* query object */}
{/* no footer */}
<Modal
title="命令行"
onOk={this.testOK}
onCancel={this.testCancel}
visible={visible}
footer={[]}
>
{/* loading={this.state.loading} */}
<Form
onSubmit={this.handleSubmit}
layout="horizontal">
<FormItem
label="测试命令行"
hasFeedback
{...formItemLayout}>
{
getFieldDecorator('textarea', {
rules: [
{
required: false,
message: ' url 长度必须 30 个字符之间'
}
],
initialValue: `10.1.5.31:8080/http/report/query?{"SecuCode":"000011","Names":"阳琨","ApiName":"fund.f9.fund_profile.FundManager.BasicInformations","WriteType":"json"}`,
})(
<Input
type="textarea"
placeholder="请先点击 “开始测试” 按钮!"
rows="15"
cols="500"
/>
)
}
</FormItem>
{/* onClick={this.props.checkTestCommands} */}
<FormItem style={{textAlign: "center"}}>
<Button
onClick={this.onTest}
type="primary"
htmlType="submit"
icon="hourglass"
style={{margin: "auto 10px"}}
loading={loading}>
开始测试
</Button>
<Button
onClick={this.testCancel}
size="large"
style={{margin: "auto 10px"}}
icon="close">
关闭
</Button>
{/* ref="submit_btn" */}
</FormItem>
</Form>
</Modal>
</div>
);
}
}
```
Related
These are my codes for the snackbar and it wasn't working whenever I'll click the button. I wanted the snackbar to appear once I'll click the button "confirm". Almost all of the examples I have seen are in a functional component, so how can I make the Snackbar work as expected in a class component?
class name extends Component {
constructor() {
super();
this.state = { orders: [], open: false };
}
handleOpen = () => this.setState({ open: true });
handleClose = () => this.setState({ open: false });
columns = [
{
name: "Confirm",
options: {
customBodyRender: (value, tableMeta) => {
return (
<FormControlLabel
value={value}
control={
<Button>
confirm
</Button>
}
onClick={(e) => {
try {
//firestore codes
);
} catch (err) {
console.log(err);
}
this.handleOpen();
}}
/>
);
},
},
},
];
//code for options
//data fetching codes
render() {
const { open } = this.state;
return this.state.orders ? (
<div>
//muidatatable codes
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
open={open}
onClose={this.handleClose}
autoHideDuration={2000}
// other Snackbar props
>
Order Confirmed
</Snackbar>
</div>
) : (
<p>Loading...</p>
);
}
}
Ignoring a few syntax errors, you should check if there are any orders by using the length and not just by mere existence of the array as you have initialized an empty array this.state.orders will always result in true. Instead use this.state.orders.length > 0 ? to check if there are any orders or not.
Snackbar's child(ren) should be wrapped in components and not just strings directly, for using string directly you can use message prop of Snackbar.
Also, it's a standard to write class's name starting with an upper-case letter.
Here's a working code: Material UI Snackbar using classes
import React, { Component } from "react";
import { FormControlLabel, Button, Snackbar } from "#material-ui/core";
import MuiAlert from "#material-ui/lab/Alert";
export default class Name extends Component {
constructor() {
super();
this.state = { orders: [], open: false };
}
handleOpen = () => this.setState({ open: true });
handleClose = () => this.setState({ open: false });
handleClick = () => this.setState({ orders: [1], open: true });
columns = [
{
name: "Confirm",
options: {
customBodyRender: (value, tableMeta) => {
return (
<FormControlLabel
value={value}
control={<Button>confirm</Button>}
onClick={(e) => {
try {
//firestore codes
} catch (err) {
console.log(err);
}
this.handleOpen();
}}
/>
);
}
}
}
];
//code for options
//data fetching codes
render() {
const { open } = this.state;
return (
<>
<Button variant="outlined" onClick={this.handleClick}>
Open snackbar
</Button>
{this.state.orders.length > 0 ? (
<div>
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
open={open}
onClose={this.handleClose}
autoHideDuration={2000}
// other Snackbar props
>
{/* <span
style={{
background: "#000",
color: "#fff",
padding: "20px 5px",
width: "100%",
borderRadius: "5px"
}}
>
Order Confirmed
</span> */}
<MuiAlert
onClose={this.handleClose}
severity="success"
elevation={6}
variant="filled"
>
Success Message
</MuiAlert>
</Snackbar>
</div>
) : (
<p>loading...</p>
)}
</>
);
}
}
The following changes are made to make it work:
Removed Order Confirmed and used message prop of Snackbar
Passed values to orders array in constructor
Passed true in open variable.
Below is the working code for snack bar.
import React, { Component } from "react";
import Snackbar from "#material-ui/core/Snackbar";
class SnackBarSof extends Component {
constructor() {
super();
this.state = { orders: [1, 2], open: true };
}
handleOpen = () => this.setState({ open: true });
handleClose = () => this.setState({ open: false });
render() {
console.log(this.state.orders);
console.log(this.state);
const { open } = this.state;
return this.state.orders ? (
<div>
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
open={open}
onClose={this.handleClose}
message="order confirmed"
autoHideDuration={2000}
></Snackbar>
</div>
) : (
<p>Loading...</p>
);
}
}
export default SnackBarSof;
I am working on react app which uses redux for state management. In this app i have to implement search page. In which when user types on search box the app display the product related to that text input.
I have api endpoint like this api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. I tried implement this but i am not able filter this. I haven't worked on this before and couldn't find good tutorial for this.
Here is my code
searchAction.js
export const searchProduct =(value) => dispatch => {
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_LOAD
});
new _rest().get(`/buyer/product/filter?q=${value}`)
.then(res => {
console.log(res);
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_SUCCESS,
payload: res
})
}).catch(err => {
console.log(err)
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_ERROR,
error: err
})
});
}
searchActionreducer.js
const initialState = {
isFetching: false,
searchData: {},
isFetched: false,
isError: null
}
export default (state=initialState, action) =>{
switch (action.type) {
case searchActionTypes.SEARCH_PRODUCT_LOAD:
return {
...state,
isFetching: true
};
case searchActionTypes.SEARCH_PRODUCT_SUCCESS:
return {
...state,
isFetching: false,
searchData: action.payload.data._embedded,
isFetched: true
};
case searchActionTypes.SEARCH_PRODUCT_ERROR:
return {
...state,
isFetched: false,
};
default:
return {
...state
}
}
}
searchPage.js
class SearchPage extends React.Component {
state = {
inputValue: '',
}
componentDidMount() {
this.props.searchProduct('');
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('component update', prevState)
if(this.state.inputValue !== prevState.inputValue) {
this.props.searchProduct(this.state.inputValue);
}
}
render() {
console.log('SearchPage Result', this.props.product);
const {product} = this.props;
const {inputValue} =this.state;
const updateInputValue = (e) => {
e.preventDefault();
this.setState({
inputValue: e.target.value
})
}
return(
<Grid container>
<div style={{width: '100%', display:'flex', justifyContent: 'center' }}>
<form noValidate autoComplete="off">
<TextField value={inputValue} onChange={updateInputValue} id="standard-basic" label="Search Product" />
</form>
</div>
<Grid container spacing={8} style={{padding: '30px'}}>
{product && product.productResourceList &&
product.productResourceList.map((data, index) => {
return(
<Grid item xs={6}>
<MainCards key={data.productId} data={data} />
</Grid>
)
})}
</Grid>
</Grid>
)
}
}
const mapStateToProps =(state) => {
return {
product: state.search.searchData
}
}
const mapDispatchToProps = {
searchProduct,
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchPage);
Here In componentDidMount Initially when component mounts i made display all the product. After that in componentDidUpdate i trying get filtered data from api buy passing this.state.inputValue as value of q in api endpoint api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. But component is not updating at all.
If you successfully log in and have got the token, then the page will redirect to home and after I have successfully logged in and got the token, see the picture taken .
.
the code is in the login folder, thank you for helping .
const FormItem = Form.Item;
class Login extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: this.props.isAuthenticated,
errorMessage: null,
isErorloaded: false
};
}
handleRegisterGoogle = request => {
this.props.loginWithGoogle(this.props.history, request);
};
handleSubmit = e => {
try {
e.preventDefault();
this.props.form.validateFields(async (err, values) => {
if (!err) {
console.log("Received values of form: ", values);
await this.props.loginWithForm(values);
}
});
} catch (error) {
console.log(error);
}
};
render() {
console.log(this.props.token);
const { form } = this.props;
const { getFieldDecorator } = form;
const { errorMessage, isErorloaded,isAuthenticated } = this.state;
if (isAuthenticated === true) {
return <Redirect to="/" />;
}
console.log(errorMessage);
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<FormItem className="login-form__input-text">
{getFieldDecorator("email", {
rules: [
{
type: "email",
required: true,
message: "Please input your email!"
}
]
})(
<Input
size={"large"}
prefix={
<Icon type={"user"} style={{ color: "rgba(0,0,0,.25)" }} />
}
placeholder={"Email"}
/>
)}
</FormItem>
<FormItem className="login-form__input-text">
{getFieldDecorator("password", {
rules: [
{
required: true,
message: "Please input your password!"
}
]
})(
<Input
size={"large"}
prefix={
<Icon type={"lock"} style={{ color: "rgba(0,0,0,.25)" }} />
}
placeholder={"Password"}
type="password"
/>
)}
</FormItem>
<FormItem className="login-form__checkBox">
{getFieldDecorator("remember", {
valuePropName: "checked",
initialValue: true
})(<Checkbox>{strings.login_remember_me}</Checkbox>)}
<a className="login-form__forgot" href="/">
{strings.login_forgot_password}
</a>
<div>
<Button
size={"large"}
htmlType="submit"
className="login-form__button__submit"
>
<h4>{strings.login_enter}</h4>
</Button>
<div className="login-form__error-box">
{isErorloaded ? (
<p className="login-form__error-notif"> {errorMessage}</p>
) : null}
</div>
</div>
<div className="login-form__separator">
<p className="login-form__separator__text">
{strings.login_option}
</p>
</div>
<div className="login-form__socmed-box">
<ButtonFacebook
className="login-form__socmed-button"
onSubmit={this.handleRegisterGoogle}
>
<p> {strings.facebook}</p>
</ButtonFacebook>
<ButtonGoogle
className="login-form__socmed-button"
onSubmit={this.handleRegisterGoogle}
>
<p> {strings.google}</p>
</ButtonGoogle>
</div>
<p style={{ marginTop: "70px" }}>
{strings.formatString(
strings.login_quote,
<a href="/register" className="login-form__register">
{strings.login_register}{" "}
</a>
)}
</p>
</FormItem>
</Form>
);
}
}
const mapStateToProps = state => ({
isAuthenticated: state.authentication.isAuthenticated,
token: state.authentication.token
});
const LoginForm = Form.create({})(Login);
export default connect(
mapStateToProps,
{ loginWithGoogle, loginWithForm }
)(LoginForm);
I'm working on React native. I'm using FlatList. I want to show the loading bar as I go down. I wrote the code for that.
I've reviewed the documentation, but it's not working. I guess I'm making a mistake at the await. I don't know how to use it. What is the difference of async? I'm leaving the code sample below.
Thanks in advance for your help.
handleRefresh = () => {
this.setState({
offset: 0,
maxSize: 10,
isSearch: false,
isLoading: true,
isRefreshing: true
}, () => {
this.loadData();
});
};
handleLoadMore = () => {
this.setState({
maxSize: this.state.maxSize + 5,
isSpinner: true
}, () => {
this.loadData();
});
};
keyExtractor = (item, index) => index.toString();
renderFooter = () => {
if(this.state.isSpinner === false) { return null; }
return (
<View style={{ paddingVertical: 20 }}>
<ActivityIndicator animating size="small" />
</View>
);
};
loadData = async () => {
try {
const { offset, maxSize } = this.state;
const username = await AsyncStorage.getItem('username');
const token = await AsyncStorage.getItem('token');
var credentials = Base64.btoa(username + ':' + token);
var URL = `http://demo.espocrm.com/advanced/api/v1/Lead?sortBy=createdAt&asc&offset=${offset}&maxSize=${maxSize}`;
axios.get(URL, {headers : { 'Espo-Authorization' : credentials }})
.then(this.dataSuccess.bind(this))
.catch(this.dataFail.bind(this));
} catch (error) {
Alert.alert(
'Hata',
'Bir hata meydana geldi. Lütfen yöneticiye başvurunuz.',
[
{ text: 'Tamam', onPress: () => null }
]
);
}
};
dataSuccess(response) {
this.setState({ isRefreshing: false, isSpinner: false, isLoading: false, leadList: response.data.list });
}
dataFail(error) {
this.setState({ isLoading: false });
Alert.alert(
'Hata',
'Beklenmedik bir hata oluştu',
[
{ text: 'Tamam', onPress: () => null }
]
);
}
render() {
const { isLoading, isRefreshing, searchText, leadList } = this.state;
return(
<View style={styles.container}>
<SearchBar
placeholder="Bir lead arayın..."
onChangeText={this.searchLead.bind(this)}
onClear={this.handleRefresh}
onCancel={this.loadData}
value={searchText}
/>
{
isLoading ? <ActivityIndicator style={styles.loading} size="large" color="orange" /> :
<FlatList
data={leadList}
ListFooterComponent={this.renderFooter}
renderItem={({item}) =>
<ListItem
leftAvatar={{ source: { uri: 'https://pbs.twimg.com/profile_images/567081964402790401/p7WTZ0Ef_400x400.png' } }}
title={item.name}
subtitle={item.status}
bottomDivider={true}
/>
}
keyExtractor={this.keyExtractor}
refreshing={isRefreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0.5}
/>
}
</View>
)
}
}
In your FlatList component, you need to include an attribute called extraData and set that to this.state so the component will update.
<FlatList
data={leadList}
extraData={this.state}
...
/>
I am relatively beginner in React, and in this particular situation, I am probably missing something very fundamental.
Here, I have a simple CRUD app, and after user adds new data, the updated list of items should be rendered. And the new data is added with a second Dialog component AddNewDevelopmentWork.js
So, after new data is added by the AddNewDevelopmentWork.js (which is the child compoenent that will only open a dialog for the user to input and fill few TestFields), in the main component (DevelopmentList.js), I am using componentDidUpdate to do the comparison with current state and prevState (for the state variable allDevelopmentWorks which is an array of objects) , and if they are not equal then make a request to the backend Express API and fetchin data and updating state within componentDidUpdate. And then render with new data.
Problem is, this main DevelopmentList.js component is not rendering the new data entered by the user till the page is refreshed. But after refreshing the page manually its showing the newly entered data.
Here is my DevelopmentList component.
class DevelopmentList extends Component {
constructor(props) {
super(props);
this.state = {
allDevelopmentWorks: []
};
}
componentDidUpdate(prevProps, prevState) {
if (
this.state.allDevelopmentWorks.length !==
prevState.allDevelopmentWorks.length
) {
return axios
.get("/api/developmenties")
.then(res => {
this.setState({
allDevelopmentWorks: res.data
});
})
.catch(function(error) {
console.log(error);
});
}
}
componentDidMount() {
axios.get("/api/developmenties").then(res => {
this.setState({
allDevelopmentWorks: res.data
});
});
}
render() {
const { classes } = this.props;
return (
<div>
<Table className={classes.table}>
<TableHead>
<TableRow className={classes.row}>
<CustomTableCell align="left">Location</CustomTableCell>
<CustomTableCell align="left">
Description Of Work
</CustomTableCell>
<CustomTableCell align="left">
Date of Commencement
</CustomTableCell>
<CustomTableCell align="left">Date of Completion</CustomTableCell>
<CustomTableCell align="left">Status of Work</CustomTableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.allDevelopmentWorks.map((document, i) => (
<TableRow className={classes.row} key={i}>
<CustomTableCell component="th" scope="row">
{document.location}
</CustomTableCell>
<CustomTableCell align="left">
{document.work_description}
</CustomTableCell>
<CustomTableCell align="left">
{moment(document.date_of_commencement).format("YYYY-MM-DD")}
</CustomTableCell>
<CustomTableCell align="left">
{moment(document.date_of_completion).format("YYYY-MM-DD")}
</CustomTableCell>
<CustomTableCell align="left">
{document.status_of_work}
</CustomTableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
}
export default withStyles(styles)(DevelopmentList);
However, withing the componentDidUpdate method below, if I change it to as below the if condition (taking the length propery out of equation) then the new data is immediately rendered on the page, but then it also becomes an infinite loop inside componentDidUpdate and hitting the Express API again and again each second.
componentDidUpdate(prevProps, prevState) {
if (
this.state.allDevelopmentWorks !==
prevState.allDevelopmentWorks
) {
return axios
.get("/api/developmenties")
.then(res => {
this.setState({
allDevelopmentWorks: res.data
});
})
.catch(function(error) {
console.log(error);
});
}
}
The code in the second component, (which is the child component to the main DevelopmentList.jscomponent, that will only open a dialog for the user to input and fill few TestFields add new data to this CRUD) is below AddNewDevelopmentWork.js
class AddNewDevelopmentWork extends Component {
state = {
open: false,
location: "",
work_description: "",
date_of_commencement: new Date(),
date_of_completion: new Date(),
status_of_work: "",
vertical: "top",
horizontal: "center"
};
handleCommencementDateChange = date => {
this.setState({
date_of_commencement: date
});
};
handleCompletionDateChange = date => {
this.setState({
date_of_completion: date
});
};
handleToggle = () => {
this.setState({
open: !this.state.open
});
};
handleClickOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.props.history.push("/dashboard/developmentworks");
};
onChange = e => {
const state = this.state;
state[e.target.name] = e.target.value;
this.setState(state);
};
handleFormSubmit = e => {
e.preventDefault();
const {
location,
work_description,
date_of_commencement,
date_of_completion,
status_of_work
} = this.state;
axios
.post("/api/developmenties/", {
location,
work_description,
date_of_commencement,
date_of_completion,
status_of_work
})
.then(() => {
// this.props.history.push("/dashboard/developmentworks");
// window.location.href = window.location.href;
this.setState({
open: false,
vertical: "top",
horizontal: "center"
});
})
.catch(error => {
alert("Ooops something wrong happened, please try again");
});
};
handleCancel = () => {
this.setState({ open: false });
};
render() {
const { classes } = this.props;
const {
location,
work_description,
date_of_commencement,
date_of_completion,
status_of_work,
vertical,
horizontal
} = this.state;
return (
<MuiThemeProvider theme={theme}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<div>
<MuiThemeProvider theme={theme}>
<Dialog open={this.state.open} onClose={this.handleToggle}>
<DialogContent required>
<form onSubmit={this.handleFormSubmit}>
<TextField
value={location}
onChange={e =>
this.setState({
location: e.target.value
})
}
error={location === ""}
helperText={
location === "" ? "Please enter Location" : " "
}
label="Location"
type="email"
fullWidth
/>
<TextField
value={work_description}
onChange={e =>
this.setState({
work_description: e.target.value
})
}
error={work_description === ""}
helperText={
work_description === ""
? "Please enter Work Description"
: " "
}
label="Description of Work"
type="email"
fullWidth
/>
<div>
<DatePicker
format="dd/MM/yyyy"
label="Date of Commencement"
value={date_of_commencement}
onChange={this.handleCommencementDateChange}
disableOpenOnEnter
animateYearScrolling={false}
/>
</div>
<div>
<DatePicker
format="dd/MM/yyyy"
label="Date of Completion"
value={date_of_completion}
onChange={this.handleCompletionDateChange}
/>
</div>
<TextField
value={status_of_work}
onChange={e =>
this.setState({
status_of_work: e.target.value
})
}
error={location === ""}
helperText={
status_of_work === ""
? "Please enter Status of Work!"
: " "
}
label="Status of Work"
type="email"
fullWidth
/>
</form>
</DialogContent>
<DialogActions>
<Button
onClick={this.handleCancel}
classes={{
root: classes.root
}}
variant="contained"
>
Cancel
</Button>
<Button
onClick={this.handleFormSubmit}
color="primary"
variant="contained"
>
Save
</Button>
</DialogActions>
</Dialog>
</MuiThemeProvider>
</div>
</MuiPickersUtilsProvider>
</MuiThemeProvider>
);
}
}
The problem is that you are not treating your state as if it were immutable (as recommended by the React documentation). When you call this.setState({ allDevelopmentWorks: res.data }), you are replacing the value of allDevelopmentWorks with a new array with a new object reference. Because the array references do not match, checking for equality directly will fail (i.e. this.state.allDevelopmentWorks !== prevState.allDevelopmentWorks is comparing object references).
Checkout this answer for updating a state array without mutating it.
And take a look at loadash isEqual for comparing array equality.
The resulting infinite loop occurring has little to do with React itself but rather how javascript handles comparison between two objects. Since data type returned from the API is an array
[] !== [] => // true
The condition will always be true and hence setState is called repeatedly for every state change and this will trigger a re-render. A better understanding of how the different component lifecycle methods are invoked is one of the core concepts that I had to get a hang of when in my early days of learning React.
componentWillMount ->
render ->
componentDidMount(state changes here will trigger a re-render from maybe an api fetch) ->
componentWillUpdate ->
render ->
componentDidUpdate
You could share a link to the repo if it there is one and I can have a look
If you use setState inside componentDidUpdate it updates the component, resulting in a call to componentDidUpdate which subsequently calls setState again resulting in the infinite loop. You should conditionally call setState and ensure that the condition violating the call occurs eventually e.g:
componentDidUpdate: function() {
if (condition) {
this.setState({..})
} else {
//do something else
}
}
In case you are only updating the component by sending props to it(it is not being updated by setState, except for the case inside componentDidUpdate), you can call setState inside componentWillReceiveProps instead of componentDidUpdate.
Answering my own question, after I resolved the issue. It was a problem of not updating the parent component's (DevelopmentList.js) state at all when the user was adding a new item with the child component.(AddNewDevelopmentWork.js which is a form Dialog).
So, it was case of passing data from child to parent to update parent's state as below
A> Define a callback in my parent (addItem function) which takes
the data I need in as a parameter.
B> Pass that callback as a prop to the child
C> Call the callback using this.props.[callback] inside the child,
and pass in the data as the argument.
Here's my final working code in the parent DevelopmentList.js
class DevelopmentList extends Component {
constructor(props) {
super(props);
this.state = {
allDevelopmentWorks: []
};
}
addItem = item => {
this.setState({
allDevelopmentWorks: [item, ...this.state.allDevelopmentWorks]
});
};
componentDidMount() {
axios.get("/api/developmenties").then(res => {
this.setState({
allDevelopmentWorks: res.data
});
});
}
componentDidUpdate(prevProps, prevState) {
if (
this.state.allDevelopmentWorks.length !==
prevState.allDevelopmentWorks.length
) {
return axios
.get("/api/developmenties")
.then(res => {
this.setState({
allDevelopmentWorks: res.data
});
})
.catch(function(error) {
console.log(error);
});
}
}
deleteDevelopmentWorks = id => {
axios.delete("/api/developmenties/" + id).then(() => {
this.setState({
allDevelopmentWorks: this.state.allDevelopmentWorks.filter(
item => item._id !== id
)
});
});
};
render() {
const { classes } = this.props;
return (
<div>
<AddNewDevelopmentWork addNewItemToParentState={this.addItem} />
<Table className={classes.table}>
<TableBody>
{this.state.allDevelopmentWorks.map((document, i) => (
<TableRow className={classes.row} key={i}>
<CustomTableCell component="th" scope="row">
{document.location}
</CustomTableCell>
<CustomTableCell align="left">
{document.work_description}
</CustomTableCell>
<CustomTableCell align="left">
{moment(document.date_of_commencement).format("YYYY-MM-DD")}
</CustomTableCell>
<CustomTableCell align="left">
{moment(document.date_of_completion).format("YYYY-MM-DD")}
</CustomTableCell>
<CustomTableCell align="left">
{document.status_of_work}
</CustomTableCell>
<CustomTableCell align="left">
<div id="snackbar">
The Document has been successfully deleted
</div>
<Button
onClick={this.deleteDevelopmentWorks.bind(
this,
document._id
)}
variant="contained"
className={classes.button}
>
<DeleteIcon className={classes.rightIcon} />
</Button>
</CustomTableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
}
export default withStyles(styles)(DevelopmentList);
And here's my final working code in the child AddNewDevelopmentWork.js
class AddNewDevelopmentWork extends Component {
state = {
open: false,
opensnackbar: false,
vertical: "top",
horizontal: "center",
location: "",
work_description: "",
date_of_commencement: new Date(),
date_of_completion: new Date(),
status_of_work: ""
};
handleCommencementDateChange = date => {
this.setState({
date_of_commencement: date
});
};
handleCompletionDateChange = date => {
this.setState({
date_of_completion: date
});
};
handleToggle = () => {
this.setState({
open: !this.state.open
});
};
handleClickOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ opensnackbar: false });
this.props.history.push("/dashboard/developmentworks");
};
onChange = e => {
const state = this.state;
state[e.target.name] = e.target.value;
this.setState(state);
};
handleFormSubmit = e => {
e.preventDefault();
const { addNewItemToParentState } = this.props;
const {
location,
work_description,
date_of_commencement,
date_of_completion,
status_of_work
} = this.state;
axios
.post("/api/developmenties/", {
location,
work_description,
date_of_commencement,
date_of_completion,
status_of_work
})
.then(() => {
addNewItemToParentState({
location,
work_description,
date_of_commencement,
date_of_completion,
status_of_work
});
this.setState({
open: false,
opensnackbar: true,
vertical: "top",
horizontal: "center"
});
})
.catch(error => {
alert("Ooops something wrong happened, please try again");
});
};
handleCancel = () => {
this.setState({ open: false });
};
render() {
const { classes } = this.props;
const {
location,
work_description,
date_of_commencement,
date_of_completion,
status_of_work,
vertical,
horizontal,
opensnackbar
} = this.state;
return (
<MuiThemeProvider theme={theme}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<div>
<MuiThemeProvider theme={theme}>
<Fab
variant="fab"
onClick={this.handleClickOpen}
aria-pressed="true"
color="secondary"
size="large"
aria-label="Add"
fontSize="large"
>
<AddIcon className={styles.largeIcon} />
</Fab>
<Dialog
open={this.state.open}
onClose={this.handleToggle}
aria-labelledby="form-dialog-title"
fullWidth={true}
maxWidth={"md"}
>
<DialogTitle
id="form-dialog-title"
disableTypography="false"
className={this.props.classes.styledHeader}
>
New Development Work
</DialogTitle>
<DialogContent required>
<form onSubmit={this.handleFormSubmit}>
<TextField
value={location}
onChange={e =>
this.setState({
location: e.target.value
})
}
type="email"
/>
<TextField
value={work_description}
onChange={e =>
this.setState({
work_description: e.target.value
})
}
type="email"
/>
<div>
<DatePicker
value={date_of_commencement}
onChange={this.handleCommencementDateChange}
/>
</div>
<div>
<DatePicker
value={date_of_completion}
onChange={this.handleCompletionDateChange}
/>
</div>
<TextField
value={status_of_work}
onChange={e =>
this.setState({
status_of_work: e.target.value
})
}
type="email"
fullWidth
/>
</form>
</DialogContent>
<DialogActions>
<Button
onClick={this.handleCancel}
classes={{
root: classes.root
}}
variant="contained"
>
Cancel
</Button>
<Button
onClick={this.handleFormSubmit}
color="primary"
variant="contained"
>
Save
</Button>
</DialogActions>
</Dialog>
<Snackbar
anchorOrigin={{ vertical, horizontal }}
open={opensnackbar}
autoHideDuration={2000}
onClose={this.handleClose}
>
<MySnackbarContent
onClose={this.handleClose}
variant="success"
message="New Development Works has been uploaded successfully"
/>
</Snackbar>
</MuiThemeProvider>
</div>
</MuiPickersUtilsProvider>
</MuiThemeProvider>
);
}
}
AddNewDevelopmentWork.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(AddNewDevelopmentWork);