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>
);
}
Related
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>
</>
);
};
}
I'm little new to React Native. I have a scenario where I need to create the TextInput dynamically and bind values it from an array. Once the array updates, the value of the TextInput is not updating. Below is my code.
constructor(props) {
super(props);
this.state = {
textInputValues: [],
textInput: [],
samplearray://gets an array from the JSON
}
componentDidMount() {
this.setTextInputValue();
this.prepareTextBox();
}
setTextInputValue() {
let textInputValues = this.state.textInputValues;
this.state.samplearray.map(() => {
textInputValues.push("") //default value
this.setState({ textInputValues })
})
}
prepareTextBox() {
let textInput = this.state.textInput;
this.state.samplearray.map((value, index) => {
textInput.push(<TextInput style={styles.textBox} value={this.state.textInputValues[index]} key={index} />);
})
this.setState({ textInput })}
Code to render the TextBox in the render method.
{ this.state.textInput.map((value, index) => {
return value
})}
I have button on which this.state.textInputValues array value gets changed. But change of that is not being reflected in the TextInput. Stuck with this since 2 days. Any help is appreciated, thanks in advance.
This is how your code block should look (do read the comments for explanation):
componentDidMount() {
this.setTextInputValue();
// call the below function from `setTextInputValue` as you have dependency on that
// this.prepareTextBox();
}
setTextInputValue() {
let textInputValues = [...this.state.textInputValues];
this.state.samplearray.map((value) => {
textInputValues = [ ...textInputValues , value] //default value
// this is how you should call `prepareTextBox`
// in setState callback as it will confirm that state is updated
this.setState({ textInputValues },() => {
this.prepareTextBox();
})
})
}
prepareTextBox() {
let textInput = [...this.state.textInput];
this.state.samplearray.map((value, index) => {
textInput.push(<input value={this.state.textInputValues[index]} key={index} />);
})
this.setState({ textInput })
}
You can run the below snippet and check, hope that will clear your doubts :
const { useState , useEffect } = React;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
textInputValues: [],
textInput: [],
samplearray:["Vivek","Darshita"]//gets an array from the JSON
}
}
componentDidMount() {
this.setTextInputValue();
}
setTextInputValue() {
let textInputValues = [...this.state.textInputValues];
this.state.samplearray.map((value) => {
textInputValues = [ ...textInputValues , value] //default value
this.setState({ textInputValues },() => {
this.prepareTextBox();
})
})
}
prepareTextBox() {
let textInput = [...this.state.textInput];
this.state.samplearray.map((value, index) => {
textInput.push(<input value={this.state.textInputValues[index]} key={index} />);
})
this.setState({ textInput })
}
changeValues = () => {
this.setState({
textInput : [],
textInputValues : ["New - Vivek" , "New - Darshita"]
},() => {
this.prepareTextBox();
});
}
render() {
return (
<div>
{ this.state.textInput }
<button onClick={this.changeValues}>Change Value</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
function typeContactGetter is binded to this and everything is working, the only issue is in the functions return on the <li> element, I am trying to set a className coming from state and it returns undefined for this.state.
Why is this happening?
Thanks,
Bud
component
class ContactType extends Component {
constructor(props) {
super(props);
this.state = {
date: new Date(),
hiddenList: false,
familyContacts: this.typeContactGetter("Family"),
friendContacts: this.typeContactGetter("Friends")
};
this.typeContactGetter = this.typeContactGetter.bind(this);
this.handleClick = this.handleClick.bind(this);
this.hideList = this.hideList.bind(this);
}
handleClick = (event) => {
event.preventDefault();
console.log('clicked, state: ' + this.state.hiddenList);
};
hideList = () => {
console.log("this is hidelist: " + this.state.hiddenList);
if (this.state.hiddenList === true){
this.setState({
hiddenList: false
});
}
this.setState({
hiddenList: !this.state.hiddenList
});
};
typeContactGetter = (name) => {
console.log(this.state);
for (let contact of CONTACTS) {
if (contact.name === name) {
return (
<li className={this.state.hiddenList ? 'hidden' : ''} onClick={this.handleClick} key={contact.id.toString()}>
{contact.contacts.map(value => {
if (value.type === "Contact") {
return (
<a key={value.id.toString()} href="#">{value.name}</a>
);
}
})
}
</li>
);
}
}
};
render() {
return (
<ContactView familyContacts={this.state.familyContacts} friendContacts={this.state.friendContacts} hideList={this.hideList}/>
);
}
}
export default ContactType;
That's because you call typeContactGetter in the constructor before the state is actually created.
constructor(props) {
super(props);
this.state = {
date: new Date(),
hiddenList: false,
familyContacts: this.typeContactGetter("Family"), // hey, but we are actually creating the state right now
friendContacts: this.typeContactGetter("Friends")
};
}
Why do you want to keep a component list in the state? Maybe it is better to pass them directly:
constructor(props) {
super(props);
this.state = {
date: new Date(),
hiddenList: false,
};
}
....
<ContactView familyContacts={this.typeContactGetter("Family")} friendContacts={this.typeContactGetter("Friends")} hideList={this.hideList}/>
btw you don't need to bind function as they are bound already by arrow functions.
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.
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.