Reactjs setState issue - reactjs

In react, I'm storing the data in state. The issue is that I'm able to store and read the state within the function. But if I try to read it in render() method, I'm not able to read the state. Here is the below code :
import Opt from './GenerateData.jsx';
constructor(props) {
this.state = {
options : ""
}
this.handleOptions = this.handleOptions.bind(this);
}
handleOptions(params) {
this.setState({
options: params
});
// here it will print the state value
console.log(this.state.options)
}
// but it won't print it here..
render() {
return (
<Opt handleOptions={this.handleOptions.bind(this)} />
...
...
<FilterOpts name="optionSelection" data={this.state.options} />
)
}
In GenerateData.jsx
class GenerateData extends React.Component {
componentDidMount() {
var d = [1,2,3,4];
this.props.handleOptions(d);
}
render() {
return (
<div></div>
)
}
}

React setState() is asynchronous. It does not immediately mutate this.state but creates a pending state transition.
You can try this code to log the updated state.
handleOptions(params) {
this.setState({
options: params
}, () => {console.log(this.state.options);}
);
}

Related

Unable to pass data from a Parent'state to a Child's state in React

I am trying to pass an Array of values from a Parent module to a Child module, and set them in the Child's state in order to display a chart.
Here is my Parent module:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
this.getRates();
}
getRates = () => {
fetch(
"https://api.exchangerate.host/timeseries?start_date=2022-07-01&end_date=2022-07-05&base=USD&symbols=EUR"
)
.then((res) => res.json())
.then((timeseries) => {
const rates = Object.values(timeseries.rates);
this.setState({
data: rates,
});
});
};
render() {
const data = this.state;
return (
<>
<Child data={data} />
</>
);
}
}
And here is the Child module:
class Child extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
};
}
componentDidMount() {
this.setState({
items: this.props.data,
});
}
render() {
const { items } = this.state;
console.log("Child data from state: ", items);
console.log("Child data from props: ", this.props.data);
return (
<>
<ReactApexChart options={items} />
</>
);
}
}
Here is what I am getting from the console.log():
Child data from state: []
Child data from props: (30) [95.9182, 95.7676, 94.8036, ..., 95.2308, 95.2906]
Why am I unable to set the Child's state with this data?
Your Child component does not get its state updated because the lifecycle function you are using does not get called when the component gets an updated set of props from the Parent.
Please check the Updating heading on https://www.w3schools.com/react/react_lifecycle.asp
You will not find the componentDidMount lifecycle in there because it does not get called on a prop update.
What you need to use is something like getDerivedStateFromProps
static getDerivedStateFromProps(props) {
return {items: props.data};
}
This makes sure that every time the Parent sends an updated value in the props, the Child uses it to update the state and then re-render accordingly.

How can I chain asynchronous Firebase updates in my React app?

React & Firebase newbie here. I have a React component that needs to look up some stuff in Firebase before rendering. My database design requires first getting the correct doohick ids and subsequently looking up the doohick details, but I'm not sure how to do that with the asynchronous nature of Firebase database access. This doesn't work:
class Widget extends React.Component {
componentDidMount() {
firebase.database().ref(`/users/${username}/doohick-ids`).on('value', snapshot => {
this.setState({doohick_ids: doohick_ids});
});
this.state.doohick_ids.forEach(id => {
// ids don't actually exist at this point outside the callback
firebase.database().ref(`/doohick-details/${id}`).on('value', snapshot => {
// update state
});
});
render() {
if (this.state.doohick-ids) {
return null;
} else {
// render the Doohick subcomponents
}
}
}
I can think of a few solutions here, but none that I like. What's the recommended way to chain together Firebase calls, or perhaps redesign this to eliminate the problem?
I think you should split one component Widget to two WidgetList and WidgetItem.
WidgetItem
subscribe and unsubscribe to firebase.database().ref(/doohick-details/${id})
class WidgetItem extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
}
constructor(props) {
super(props);
this.state = {};
this.dbRef = null;
this.onValueChange = this.onValueChange.bind(this);
}
componentDidMount() {
const { id } = this.props;
this.dbRef = firebase.database().ref(`/doohick-details/${id}`);
this.dbRef.on('value', this.onValueChange);
}
componentWillUnmount() {
this.dbRef.off('value', this.onValueChange);
}
onValueChange(dataSnapshot) {
// update state
this.setState(dataSnapshot);
}
render() {
return (
<pre>{JSON.stringify(this.state, null, 2)}</pre>
);
}
}
WidgetList
subscribe and unsubscribe to firebase.database().ref(/users/${username}/doohick-ids)
class WidgetItem extends React.Component {
constructor(props) {
super(props);
this.state = { doohick_ids: [] };
this.dbRef = null;
this.onValueChange = this.onValueChange.bind(this);
}
componentDidMount() {
// Note: I've just copied your example. `username` is undefined.
this.dbRef = firebase.database().ref(`/users/${username}/doohick-ids`);
this.dbRef.on('value', this.onValueChange);
}
componentWillUnmount() {
this.dbRef.off('value', this.onValueChange);
}
onValueChange(dataSnapshot) {
this.setState({ doohick_ids: dataSnapshot });
}
render() {
const { doohick_ids } = this.state;
if (doohick_ids.length === 0) {
return 'Loading...';
}
return (
<React.Fragment>
{doohick_ids.map(id => <WidgetItem key={id} id={id} />)}
</React.Fragment>
);
}
}
And code that requires the data from the database needs to be inside the callback that is invoked when that data is available. Code outside of the callback is not going to have the right data.
So:
firebase.database().ref(`/users/${username}/doohick-ids`).on('value', snapshot => {
this.setState({doohick_ids: doohick_ids});
doohick_ids.forEach(id => {
// ids don't actually exist at this point outside the callback
firebase.database().ref(`/doohick-details/${id}`).on('value', snapshot => {
// update state
});
});
});
There's many optimizations possible here, but they all boil down to the code being inside the callback and updating the state when a value comes from the database.

How to force children rerendering after axios call in React?

I'm working on a form with interactive inputs. They have to actualise themselves with information into parent state.
I use Axios to get the data to show, getting them from an external API. I tried to set default values, but they never actualise with newer values.
class Form extends React.Component {
getData() {
axios.get('http://xxx/getform/').then(
res => this.setState(res.data)
);
}
componentDidMount() {
this.getData();
setInterval(() => {
this.getData();
}, 36000000)
}
render() {
return (
<div>
<form>
<DatePicker />
</form>
</div>
)
}
}
class DatePicker extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: new Date(),
runMin: new Date(),
runMax: new Date()
};
}
getDate() {
console.log('DAD');
try { // if axios didn't finish, to avoid undefined
this.setState({
runMin: super.state.RunMin,
runMax: super.state.RunMax})
} catch (e) {
this.setState({
runMin: new Date(),
runMax: new Date()})
}
}
componentDidMount() {
this.getDate();
this.setState({selected: this.state.runMax});
}
render() {
return (<div></div>);
}
}
Actually after axios call, the children doesn't rerender. I separated the call for axios and the component using it, because the Form component do a single call for multiple children (not displayed here), and they read the parent's state to render.
Firstly, you should not access the parents state using super and instead pass the required value as props
Secondly, componentDidMount lifecycle is executed on initial mount and hence the logic within it won't execute when the parent state updates.
The correct way to handle your case would be
class Form extends React.Component {
state = {
RunMin: new Date(),
RunMax: new Date()
}
getData() {
axios.get('http://xxx/getform/').then(
res => this.setState({RunMin: res.data.RunMin, RunMax: res.data.RunMax})
);
}
componentDidMount() {
this.getData();
setInterval(() => {
this.getData();
}, 36000000)
}
render() {
return (
<div>
<form>
<DatePicker runMin={this.state.RunMin} runMax={this.state.RunMax}/>
</form>
</div>
)
}
}
class DatePicker extends React.Component {
render() {
console.log(this.props.runMin, this.props.runMax);
return (<div></div>);
}
}
The way you are setting the state is incorrect
Change
this.setState(res.data);
To
this.setState({data: res.data});
You need to set the response to a state field you have in component and make sure you pass the data to the child component

React: fire render after promise completed

Please note, that I a fetching data from AWS DynamoDB.
...
class Test extends Component {
constructor(props) {
super(props);
this.state = {
contactList: []
}
}
componentDidMount() {
var getItemsPromise = db.scan({ TableName: "tester" }).promise();
getItemsPromise.then((data) => this.setState({ contactList: data.Items }));
}
render() {
return (
<div>{this.state.contactList[0].link.S}</div>
);
}
}
export default Test;
I am trying to render the returned value, but can't. If I set
render() {
console.log(this.state.contactList[0].link.S);
return (
<div>test</div>
);
}
it works. Why is that? Why is it not working when I set it straight inline?
this.state.contactList[0] is undefined before the promise is resolved, so this.state.contactList[0].link will give rise to an error.
You could e.g. return null from the render method until the array has been filled with your objects:
class Test extends Component {
// ...
render() {
if (this.state.contactList.length === 0) {
return null;
}
return <div>{this.state.contactList[0].link.S}</div>;
}
}

React TypeError: this.setState is not a function despite having bound handler

Before flagging this as a duplicate of React this.setState is not a function, I have seen and read that.
My problem is that I'm getting this error message even though I've bound the handler.
class EditAccount extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
state = {
showForm: false
}
componentWillMount() {
this.setState = {
showForm: false
}
}
render() {
return (
<div>
<button onClick={this.onClick}>Edit</button>
{this.state.showForm ? ( <Stuff/> ): null }
</div>
)
}
//toggle form visibility
onClick(e) {
const showing = this.state.showForm;
if (showing) {
this.setState({ showForm: false });
} else {
this.setState({ showForm: true });
}
}
}
Any ideas?
componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering.
The reason to not use setState inside compomentWillMount,
React will use the initial state value from constructor or initialized default state for the first render instead of re-render. It not wait for componentWillMount to complete setState call asynchronously.
So,there is no point in making setState call inside componentWillMount. It's nothing more than that state handler processing which do nothing when setState is called.
The issue is in your componentWillMount. You are changing setState to no longer be a function but rather an object with the value showForm. You shouldn't be setting state in will mount as react advises against it. Drop that whole function and the code will work as you expect.
meaning this.setState = { showForm: false } is changing setState to be the object { showForm: false } instead of a function. So yes your error message is correct in that setState is not a function
Try dropping the whole componentWillMount function
class EditAccount extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
state = {
showForm: false
}
render() {
return (
<div>
<button onClick={this.onClick}>Edit</button>
{this.state.showForm ? ( <Stuff/> ): null }
</div>
)
}
//toggle form visibility
onClick(e) {
const showing = this.state.showForm;
if (showing) {
this.setState({ showForm: false });
} else {
this.setState({ showForm: true });
}
}
}
some optimizations.. you should be able to clean up the code a bit
class EditAccount extends Component {
state = {
showForm: false
}
render() {
return (
<div>
<button onClick={this.handleClick }>Edit</button>
{this.state.showForm ? <Stuff/> : null }
</div>
)
}
handleClick = (e) => {
this.setState((old) => ({ showForm: !old.showForm }) )
}
}

Resources