Why is setState(this.state) not triggering a rerender? - reactjs

I am trying to get my component to rerender after deleting an entry from the table.
I have tried binding my functions and using this.forceUpdate() as well but nothing seems to work. Any help will be much appreciated!
This is my component
class Directory extends Component {
constructor() {
super();
this.state = {
tenantData: [],
searchText: "",
searchedColumn: "",
tenantInfo: {},
visible: false,
userId: null,
rerender: "",
};
// this.delTenant = this.delTenant.bind(this);
this.deleteAudit = deleteAudit.bind(this);
// this.deleteTenant = this.deleteTenant.bind(this);
// this.onDeleteClick = this.onDeleteClick.bind(this);
}
This is my delete function within my component
onDeleteClick = () => {
this.setState({
...this.state,
visible: false,
});
var tenantList = this.state.tenantData;
for (var i = 0; i < tenantList.length; i++) {
if (tenantList[i].userId == this.state.userId) {
console.log(this.state.userId);
delTenant({ _id: tenantList[i]._id });
deleteTenant({ _id: this.state.userId });
this.deleteAudit({ tenantID: tenantList[i]._id }).then(() => {
this.setState(this.state); // this should rerender the component but it does not
console.log("force update");
});
break;
}
}
And this is my AntD component
<Modal
title="Modal"
visible={this.state.visible}
onOk={this.onDeleteClick}
onCancel={this.hideModal}
okText="Confirm"
cancelText="Cancel"
>
<p>Are you sure you want to delete this Tenant?</p>
</Modal>
EDIT:
Soultion found - My component did rerender, but my tenantData state was unchanged as I forgot to get the data from the database after deleting the entry.
this.deleteAudit({ tenantID: tenantList[i]._id }).then(() => {
// this.setState(this.state); // this should rerender the component but it does not
this.getTenantFunction(); // this gets the new data from database and sets the state of tenantData to the updated one
});

this.setState(this.state); // this should rerender the component but it does not
React doesn't re-render your component if your state or props doesn't change.
I don't know why forceUpdate doesn't work, look at this example
import React from "react";
export default class App extends React.Component {
constructor() {
super();
this.state = {
tenantData: [],
searchText: "",
searchedColumn: "",
tenantInfo: {},
visible: false,
userId: null,
rerender: ""
};
}
onDeleteClick = () => {
console.log("click");
};
onDeleteClickForced = () => {
console.log("clickForced");
this.forceUpdate();
};
render = () => {
console.log("render");
return (
<>
<button onClick={() => this.onDeleteClick()}>Not forced</button>
<button onClick={() => this.onDeleteClickForced()}>Forced</button>
</>
);
};
}

Related

State won't stay updated

After I use setState in getClients(), the state clientOptions is showed correctly. But when I try to pass it on to a child component it is an empty array. I've tried logging it again after componentDidMount and there it seems that the state clientOptions is resetted to its original state ([]). Sorry if this seems like a noob question, I'am pretty new to react.
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
clientFilter: 'ALL',
positionFilter: 'ALL',
clientOptions: []
};
}
componentDidMount = () => {
this.getClients()
this.getTitles()
console.log('this one shows only []: ' + this.state.clientOptions)
}
getClients = () => {
axios.get('http://localhost:5000/clients')
.then((response) => {
let clientObj = [{value: 'ALL', label: 'ALL'}];
const clientOptions = []
response.data.forEach(function (client, index) {
clientObj.push({value: client.name, label: client.name})
});
clientOptions.push({'options' : clientObj});
this.setState(prevState =>{
return{
...prevState,
clientOptions : clientOptions
}
})
console.log('this one works: ' + this.state.clientOptions)
});
}
As requested the state passed on to the child:
render() {
return (
<div className="main">
<Header
clientOptions={this.state.clientOptions}
/>
</div>
);
}

Discarding changes when using Formik with React

I am using Formik form with React. Whenever the user submits (handleSubmit), I put an option whether or not to discard the change or keep the change.
In my render,
<Formik
initialValues={this.state.experiment}
onSubmit={this.handleSubmit}
component={formikProps => (
<ExperimentForm {...formikProps} submitText="Save Changes" />
)}
/>
handleSubmit()
handleSubmit(formdata: any, actions: any) {
const data = processFormData(formdata);
let changes = this.detectChanges(this.state.experiment, data);
this.setState({ tempFormData: data });
// changed field exists
if (changes.length !== 0) {
this.setState({
isDialogOpen: true,
changedFields: changes,
});
} else {
actions.setSubmitting(false);
this.setState({
message: 'Nothing Changed',
});
}
}
keepChanges() and discardChanges()
keepChanges () {
const data = this.state.tempFormData
makeMutation(UpdateExperimentQuery, {
update: {
id: this.props.match.params.id,
data,
},
})
.then(responseData => {
console.log(responseData)
this.setState({ isDialogOpen: false });
this.props.history.push('/i/experiments');
})
.catch(err => {
this.setState({
message: 'Error Updating Experiment',
});
console.log(err);
});
}
discardChanges () {
this.setState({ isDialogOpen: false });
this.componentWillMount();
}
The keepChanges() successfully updates the data with the given field, but discardChanges just closes the dialog but does not reset the data to original value even though I try to call componentWillMount() which fetches and renders the original unchanged data in the DB.
How can I reset the fields when I choose to discard the changes?
Edit
discardChanges () {
this.formik.current.resetForm();
this.setState({ isDialogOpen: false });
this.componentWillMount();
}
//I get an error when I do React.createRef();
class EditExperiment extends Component<EditExperimentProps, EditState> {
constructor(props: EditExperimentProps) {
super(props);
this.formik = React.createRef();
this.state = {
experiment: null,
message: null,
changedFields: [],
isDialogOpen: false,
tempFormData: []
};
this.handleSubmit = this.handleSubmit.bind(this);
this.clearMessage = this.clearMessage.bind(this);
this.detectChanges = this.detectChanges.bind(this);
this.keepChanges = this.keepChanges.bind(this);
this.discardChanges = this.discardChanges.bind(this);
}
EDIT2
type EditExperimentProps = {
history: RouterHistory,
match: Match,
experiments: ExperimentsState,
refetch: () => void,
};
type EditState = {
experiment: ?Experiment,
message: ?string,
};
class EditExperiment extends Component<EditExperimentProps, EditState> {
constructor(props: EditExperimentProps) {
super(props);
this.formik = React.createRef();
this.state = {
experiment: null,
message: null,
changedFields: [],
isDialogOpen: false,
tempFormData: []
};
this.handleSubmit = this.handleSubmit.bind(this);
this.clearMessage = this.clearMessage.bind(this);
this.detectChanges = this.detectChanges.bind(this);
this.keepChanges = this.keepChanges.bind(this);
this.discardChanges = this.discardChanges.bind(this);
}
To reset the Formik you need to call resetForm - see an example here.
handleSubmit(formdata: any, actions: any) {
...
// changed field exists
if (changes.length !== 0) {
...
} else {
actions.setSubmitting(false);
actions.resetForm();
}
}
EDIT:
There is another way to get "actions" and call them wherever in component by using react refs:
constructor(props) {
super(props);
this.formik = React.createRef();
}
//somewhere in render
<Formik
ref={this.formik}
initialValues={this.state.experiment}
onSubmit={this.handleSubmit}
component={formikProps => (
<ExperimentForm {...formikProps} submitText="Save Changes" />
)}
/>
// now somewhere else in the same component ...
componentDidUpdate(prevProps) {
if(somethingHappend) {
if(this.formik.current) {
this.formik.current.resetForm();
}
}
}
You need to include the initial state when you want to use resetForm. Example:
this.formik.current.resetForm(this.initialState.experiment);
This means you need to save the initialState too:
constructor(props) {
super(props);
this.initialState = this.state;
}

added item to state but can't render

I managed to add Item to the list but can't render it.
My App.js:
class App extends Component {
constructor(props) {
super(props);
this.state = {
recipes: [{
...sample data...
}]
}
}
createRecipe(recipe) {
this.state.recipes.push({
recipe
});
this.setState({ recipes: this.state.recipes });
}
render() {
...
export default App;
and my RecipeAdd:
export default class RecipeAdd extends Component {
constructor(props) {
super(props);
this.state = {
url: '',
title: '',
description: '',
error: ''
};
}
...event handlers...
onSubmit = (e) => {
e.preventDefault();
if (!this.state.url || !this.state.title || !this.state.description) {
this.setState(() => ({ error: 'Please provide url, title and description.'}));
} else {
this.setState(() => ({ error: ''}));
this.props.createRecipe({
url: this.state.url,
title: this.state.title,
description: this.state.description
});
}
}
render () {
return (
<div>
...form...
</div>
)
}
}
In React dev tools I see the recipe is added with 'recipe'. How should I change my createRecipe action to add new recipe properly? :
It looks like you're wrapping the recipe in an extra object. You can see in your screenshot that index 3 has an extra recipe property.
It should be more like this -- just .push the recipe object directly:
createRecipe(recipe) {
this.state.recipes.push(recipe);
this.setState({ recipes: this.state.recipes });
}
Even better would be to not mutate the state object directly, by using concat instead:
createRecipe(recipe) {
this.setState({
recipes: this.state.recipes.concat([recipe])
});
}

State of React child doesn't update after updating parent component

I try to update the center prop of the child BaseMap component via the parent component. Even though the parent components state gets updated (and I could read the new updated properties in the console.log), it is not passed down to the child properties.
The last thing I tried was the componentWillReceiveProps method. It still doesn't work.
This is my code:
const google = window.google;
let geocoder = new google.maps.Geocoder();
class App extends Component {
constructor(props) {
super(props);
this.state = {
avatar: '',
username: 'someUse03',
realName: '',
location: '',
followers: '',
following: '',
repos: '',
address: '',
}
}
render() {
return (
<div>
<SearchBox fetchUser={this.fetchUser.bind(this)}/>
<Card data={this.state} />
<BaseMap />
</div>
);
}
fetchApi(url) {
fetch(url)
.then((res) => res.json())
.then((data) => {
this.setState({
avatar: data.avatar_url,
username: data.login,
realName: data.name,
location: data.location,
followers: data.followers,
following: data.following,
repos: data.public_repos,
address: geocoder.geocode({'address': data.location}, function(results, status) {
if (status == 'OK') {
var coords = [];
var results = results.map((i) => {
i.geometry.location = i.geometry.location
.toString()
.replace(/[()]/g, '')
.split(', ');
coords.push(i.geometry.location[0], i.geometry.location[1]);
results = coords.map((i) => {
return parseInt(i, 10)
});
return results;
});
} else {
alert('Geocoding was not successfull because ' + status)
}
})
})
});
}
fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
}
componentDidMount() {
let url = `https://api.github.com/users/${this.state.username}`;
this.fetchApi(url);
}
}
export default App;
This is the child component:
BaseMap extends React.Component {
constructor(props) {
super(props);
this.state = {
center: [41, 21],
}
}
componentWillReceiveProps(nextProps) {
this.setState({ center: nextProps.address});
}
render() {
return (
<Col md={10} mdPull={1}>
<div className="map">
<GoogleMap
bootstrapURLKeys={'AIzaSyBhzwgQ3EfoIRYT70fbjrWASQVwO63MKu4'}
center={this.state.center}
zoom={11}>
</GoogleMap>
</div>
</Col>
);
}
}
You are fetching inside the render method. this is a big NO NO.
Instead do that in the componentDidMount life cycle method
Another thing that may or may not be related to your problem, Arrays are reference types, that means if you mutate them they still points to the same ref in the memory. this could be problematic for the Reconciliation and diff algorithm of react to determine if the state indeed changed.
when you want to change or return a new array you could simply use the ES6 spread operator:
const nextArray = [...nextProps.address]
this.setState({ center: nextArray });
EDIT
Ok i forgot to mention the most important part here :)
You are not passing any props to <BaseMap /> so you won't get any helpful data in componentWillReceiveProps.

React force componentDidMount

I have the following:
import React from 'react';
import axios from 'axios';
class FirstName extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
};
}
getName () {
var name = this.refs.firstName.value;
this.setState(function() {
this.props.action(name);
});
}
handleSubmit (e) {
e.preventDefault();
this.setState({ submitted: true }, function() {
this.props.actionID(2);
this.props.activeNav('color');
});
}
render () {
return (
<div>
<h2>tell us your first name</h2>
<form>
<input
type="text"
ref="firstName"
onChange={this.getName.bind(this)}
/>
<div className="buttons-wrapper">
<button href="#">back</button>
<button onClick={this.handleSubmit.bind(this)}>continue</button>
</div>
</form>
</div>
);
}
};
class PickColor extends React.Component {
backToPrevious (e) {
e.preventDefault();
this.props.actionID(1);
this.props.activeNav('name');
}
goToNext (e) {
e.preventDefault();
this.props.actionID(3);
this.props.activeNav('design');
this.props.displayIconsHolder(true);
}
getColorValue(event) {
this.props.color(event.target.getAttribute("data-color"));
}
render () {
var colors = ['red', 'purple', 'yellow', 'green', 'blue'],
colorsLink = [];
colors.forEach(el => {
colorsLink.push(<li
data-color={el}
key={el}
onClick={this.getColorValue.bind(this)}
ref={el}>
{el}
</li>
);
});
return (
<section>
<ul>
{colorsLink}
</ul>
<button onClick={this.backToPrevious.bind(this)}>back</button>
<button onClick={this.goToNext.bind(this)}>continue</button>
</section>
);
}
}
class ConfirmSingleIcon extends React.Component {
goBack () {
this.props.goBack();
}
confirmCaptionandIcon (event) {
var optionID = event.target.getAttribute("data-option-id"),
name = event.target.getAttribute("data-option-name");
this.props.setOptionID(optionID);
this.props.setIcon(1, name, optionID, false);
}
goNext () {
this.props.goNext();
}
render () {
console.log(this.props.currentState);
var options = [],
that = this;
this.props.iconOptionsList.forEach(function(el){
options.push(<li onClick={that.confirmCaptionandIcon.bind(that)} key={el.option} data-option-name={el.option} data-option-id={el.id}>{el.option}</li>);
});
return (
<div>
<h2>Choose your caption</h2>
<h3>
{this.props.selectedIcon}
</h3>
<ul>
{options}
</ul>
<button onClick={this.goBack.bind(this)} >back</button>
<button onClick={this.goNext.bind(this)} >confirm</button>
</div>
);
}
}
class ConfirmCaption extends React.Component {
handleClick () {
var currentState = this.props.currentState;
this.props.setIcon(currentState.icon_ID, currentState.selectedIcon, currentState.option_ID, true);
this.props.setIconVisiblity(true);
this.props.setIconListVisiblity(false);
}
render () {
console.log(this.props.currentState);
return (
<div>
<p onClick={this.handleClick.bind(this)}>confirm icon and caption</p>
</div>
);
}
}
class ChooseIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
setOptionID (id) {
this.setState({ option_ID: id })
}
setIconVisiblity (onOff) {
this.setState({ confirmIcon: onOff })
}
setIconListVisiblity (onOff) {
this.setState({ iconList: onOff })
}
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
handleClick (event) {
var iconId = event.target.getAttribute("data-icon-id"),
that = this;
this.state.icons.forEach(function(el){
if(el.id == iconId){
that.setState(
{
confirmIcon: true,
iconList: false,
selectedIcon: el.name,
icon_ID: iconId,
selectedIconOptions: el.option
}
);
}
});
}
goBack () {
this.setState(
{
confirmIcon: false,
iconList: true
}
);
}
goNext () {
this.setState(
{
confirmIcon: false,
iconList: false,
confirmCaption: true
}
);
}
render () {
var icons = [];
this.state.icons.forEach(el => {
icons.push(<li data-icon-id={el.id} onClick={this.handleClick.bind(this)} key={el.name}>{el.name}</li>);
});
return (
<div>
{this.state.iconList ? <IconList icons={icons} /> : ''}
{this.state.confirmIcon ? <ConfirmSingleIcon goBack={this.goBack.bind(this)}
goNext={this.goNext.bind(this)}
setIcon={this.props.setIcon}
selectedIcon={this.state.selectedIcon}
iconOptionsList ={this.state.selectedIconOptions}
setOptionID={this.setOptionID}
currentState={this.state} /> : ''}
{this.state.confirmCaption ? <ConfirmCaption currentState={this.state}
setIcon={this.props.setIcon}
setIconVisiblity={this.setIconVisiblity}
setIconListVisiblity={this.setIconListVisiblity} /> : ''}
</div>
);
}
}
class IconList extends React.Component {
render () {
return (
<div>
<h2>Pick your icon</h2>
<ul>
{this.props.icons}
</ul>
</div>
);
}
}
class Forms extends React.Component {
render () {
var form;
switch(this.props.formID) {
case 1:
form = <FirstName action={this.props.action} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 2:
form = <PickColor displayIconsHolder={this.props.seticonsHolder} color={this.props.colorVal} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 3:
form = <ChooseIcon setIcon={this.props.setOptionA} />
break;
}
return (
<section>
{form}
</section>
);
}
}
export default Forms;
"ChooseIcon" is a component that will get used 3 times therefore everytime I get to it I need to bring its state back as if it was the first time.
Ideally I would need to make this ajax call everytime:
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
is there a way to manually call componentDidMount perhaps from a parent component?
React handles component lifecycle through key attribute. For example:
<ChooseIcon key={this.props.formID} setIcon={this.props.setOptionA} />
So every time your key (it can be anything you like, but unique) is changed component will unmount and mount again, with this you can easily control componentDidMount callback.
If you are using the ChooseIcon component 3 times inside the same parent component, I would suggest you to do the ajax in componentDidMount of the parent component like this (exaclty how you have in your example, in terms of code)
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
and then pass this data down to the ChooseIcon component
render() {
return (
//do your stuff
<ChooseIcon icons={this.state.icons}/>
)
}
after this you will only need to set the received props in your ChooseIconcomponent, for that you only need to change one line in it's constructor:
constructor(props) {
super(props);
this.state = {
icons: props.icons, // Changed here!
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
The parent component can use a ref to call the function directly.
However, trying to force this function feels like a smell. Perhaps lifting the state higher up the component tree would solve this problem. This way, the parent component will tell ChooseIcon what to show, and there will not be a need to call componentDidMount again. Also, I assume the Ajax call can also occur once.

Resources