Is it possible to set the ternary operator and call the start() function. Under the influence of data from API, pending will change totrue.
I am trying to use the ternary operator inside the button and initiate the click event. Pending = true call the click event, thestart ()function.
{this.state.pending ? this.start() : null}
class Button extends Component {
constructor() {
super();
this.state = {
isRunning: false,
pending: false
};
}
componentDidMount() {
axios.get
axios({
url: "https://app/api/v1/running",
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
console.log(response);
this.setState({
pending: response.data.pending //true
});
})
.catch(error => {
console.log(error);
})
}
start = () => {
console.log('AAAA');
}
render () {
return (
<button {this.state.pending ? this.start() : null} onClick={this.start}>
Start
</button>
);
}
}
You could take one of two approaches here.
1. Run the function straight from the api response success callback
.then(response => {
console.log(response);
this.setState({
pending: response.data.pending //true
});
this.start(); //Here
})
If you want to update the state before calling that function in any case then simply call the function in the callback of setStates second parameter. setState also accepts a function as its parameter and executes that function after performing the update. This is important to remember because setState operates asynchronously. So, If you want to make sure before your call the value has to update for some reason you can do this way. More about it here: https://reactjs.org/docs/react-component.html#setstate
.then(response => {
console.log(response);
this.setState({
pending: response.data.pending //true
}, this.start);
})
2. Use a component lifecycle method to listen for state changes and run the function
componentDidUpdate = (prevProps, prevState) => {
if(prevState.pending === false && this.state.pending === true){
this.start()
}
}
Every time you have a state or prop change componentDidUpdate executes. Note that it does not update when the component first mounts. And from the look of your code since pending is local to this component only you don't need to capture the case when it mounts firsts. More reference on lifecycle methods: https://reactjs.org/docs/react-component.html
Hope that clears your confusion here. Cheers!
Related
I understood the error, my componentDidUpdate functions is creating an infinite loop, i don't know hot to fix it. I found the two errors that the traceback show me, but i don't know what to do. Here is the submit handler function, it's in the main (logup) component:
submitHandler = event => {
event.preventDefault();
const {month, day, year} = this.state.data;
this.setState({
loading: true,
error: false,
errors: {},
data: {
...this.state.data,
foundation: `${month} ${day} de ${year}`
}
});
fetch('http://127.0.0.1:8000/logup/',
{
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(this.state.data)
}).then(response => {
this.setState({ loading: false });
if (response.ok) {
console.log('redirect to social nets');
} else {
this.setState({ error: true });
}
return response.json();
}).then(json => {
if (this.state.error) {
this.setState({errors: json}) // The traceback give me error right here
} else {
console.log(json);
}
});
};
I also have many Inputs component in the render of that logup component, the traceback show me errors here too.
state = {
error: false
};
componentDidUpdate() {
let bad = Object.keys(this.context.errors).includes(this.props.name);
if (bad) {
this.setState({ error: true }); // traceback give me error too.
};
};
In your componentDidUpdate you update state in the following manner:
this.setState({ error: true })
You do so under the condition that this is true:
Object.keys(this.context.errors).includes(this.props.name)
Setting the state causes the component to rerender and also update, so componentDidUpdate will run again. However, it is very likely that when that happens your condition above will still be true. You will thus update state again, and React will not short circuit the state update because you are creating a new object every time. One simple way to save yourself here would be to change your condition to:
if (bad && !this.state.error) {
this.setState({ error: true }); // traceback give me error too.
};
You should use some type of conditions inside componentDidUpdate for not to trigger state update. Like thus below:
componentDidUpdate(prevProps, prevState) {
let bad = Object.keys(this.context.errors).includes(this.props.name);
if (prevState.error !== this.state.error && bad) {
this.setState({ error: true }); // traceback give me error too.
};
};
Always compare the prevState and currentState value inside componentDidUpdate as in most cases this condition is suffice.
Note : Before starts using componentDidUpdate please refer to the docs as it also provides prevProps and prevState values which
are always helpful in such scenarios.
Change
if (bad) {
this.setState({ error: true }); // traceback give me error too.
};
to
if (bad && !this.state.error) {
this.setState({ error: true }); // traceback give me error too.
};
Using setState inside of componentDidUpdate will cause it to re-render and recall componentDidUpdate. Adding an additional check will help stop this loop.
I could get data from nodejs backend to react frontend using axios. But I can't assign that object to state object in React.
getData=()=>{
let responseToHandle;
axios.get("http://localhost:9000/api/getData")
.then(function (response)
{
console.log(response.data);//This is working
responseToHandle = response.data;
console.log(responseToHandle);//This is working
this.setState({
data: responseToHandle}, () => {
console.log(this.state.data)
})
})
// HERE IS MY STATE OBJECT
state={
data:[]
}
axios call and this.setState are both asynchronous.
You must add your desired code inside a callback:
this.setState({
data: responseToHandle
}, () => {
console.log(this.state.data)
// Now you can use this.state.data
});
Edit:
You also need to change
axios.get("http://localhost:9000/api/getData").then(function (response) { ... })
to
axios.get("http://localhost:9000/api/getData").then(response => { ... })
Without arrow function the scope inside .then() is the function scope, different from component scope, which means using this gives you a different value.
This code won't work properly:
console.log(this.state.data)
You called a console.log function synchronously right after a promise declaration. Promise works asynchronously.
So in order to check a new state, you should call console.log in render() method, or the best option is to use componentDidUpdate() method.
E.g.
componentDidUpdate(prevProps, prevState) {
console.log(`Previous data: ${prevState.data}`);
console.log(`New data: ${this.state.data}`);
}
When my component mounts it sends out several requests to an API for data. There is a method in my component that should run only when certain requests have been met, and only once.
I’m already using getDerivedStateFromProps to run other methods that aren’t so needy, but I’m not sure it’s right since the idea there is to compare new prop values against previous state. I have 4 different props I need check to ensure their previous state was null and the new prop has value.
One idea I had was to have an entry in state that looks something like this:
this.state = {
conditions: {
conditionA: false,
conditionB: false,
conditionC: false,
conditionD: false
}
}
Every time a prop has value, update its related item in state:
this.state = {
conditions: {
conditionA: false,
conditionB: false,
conditionC: true,
conditionD: false
}
}
Then once all are true, fire up the method. But is there a better way to do this? Is there a recommended way to ensure several props have value before running a method?
Javascript has a Promise.all that you can use to make sure all your Api requests have been completed and then take an action.
The .then() callback to Promise.all is passed an array that contains the response from all API calls and is triggered when all API calls have succeeded. Even if one fails the .catch callback is triggered
componentDidMount() {
const req = urls.map((url) => { // make API requests
return fetch(url);
});
Promise.all(req).then(res => {
// make the function call here
})
}
I would use componentDidUpdate:
componentDidUpdate(prevProps) {
// check your props values meet your required condition here
if(!prevProps.condition1 && this.props.condition1 === true && ...) {
callsomefunction()
}
}
Assuming these API requests use a technology (like fetch) that returns promises (if not, wrap whatever you are using in something that returns promises), the fairly standard way to do this would be to do this in componentDidMount (the docs call out network requests as one use case for it):
Start the requests in parallel
Use Promise.all to wait for them all to complete (or for one of them to fail)
Call your method that needs the results from a fulfillment handler on the promise from Promise.all
For instance:
componentDidMount() {
Promise.all([
getThingA(),
getThingB(),
getThingC(),
getThingD(),
])
.then(([a, b, c, d]) => {
this.yourMethod(a, b, c, d);
})
.catch(error => {
// Set error state
});
}
(I assume your method eventually calls setState.)
Live Example:
const getThing = label => new Promise(
resolve => setTimeout(resolve, Math.random() * 800, `Value for ${label}`)
);
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {
a: null,
b: null,
c: null,
c: null
};
}
componentDidMount() {
Promise.all([
getThing("a"),
getThing("b"),
getThing("c"),
getThing("d"),
])
.then(([a, b, c, d]) => {
this.yourMethod(a, b, c, d);
})
.catch(error => {
// Set error state
});
}
yourMethod(a, b, c, d) {
this.setState({a, b, c, d});
}
render() {
const {a, b, c, d} = this.state;
if (!a || !b || !c || !d) {
return <div>Loading...</div>;
}
return (
<div>a: {a}, b: {b}, c: {c}, d: {d}</div>
);
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You should use componentDidUpdate hook:
componentDidUpdate(prevProps) {
if(Object.values(prevProps).every(val => val)) {
// do the stuff
}
}
If you want to ignore some state, then you can bypass the state like:
const { ignoredState, ...rest } = prevProps
// now, use Object.values to satisfy with rest
Or, only use single state for them when all api requests are succeeded by using promise as others stated:
// when all promises are done
this.setState({fetched: true})
Now, you can just check fetched state.
Basic Scenario
I have a React textbox controlled component whose onChange event eventually triggers an AJAX call to a server-side API. The results of this call may potentially change the value of the textbox. So, there is a call to setState in the AJAX call's callback.
Basic Problem
I am having trouble finding a way to smoothly, consistently update this value when changes are made to the input before the AJAX call completes. There are two approaches I have tried so far. The main difference is in how eventually the AJAX call happens.
Approach 1
My first attempt calls setState with the immediately entered data, which eventually triggers a re-render and componentDidUpdate. The latter then makes the AJAX call, on the condition that the state data in question is different.
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
setState({ inputText: response.validatedInputTextValue }); // will also trigger componentDidUpdate
});
}
}
This approach has the advantage of quickly updating the state to reflect the user's immediate input. However, if two inputs are made quickly, a sequence such as the following occurs:
Event handler 1 fires with value '1'
Handler 1 calls setState with value '1'
Component re-rendered from change in state
componentDidUpdate triggered from re-render
Value '1' is different from last value, so
AJAX call 1 made with value '1'
While AJAX call 1 in progress, event 2 handler fires with value '12'
Handler 2 calls setState with value '12'
componentDidUpdate triggered from re-render
Value '12' is different from '1', so
AJAX call 2 made with value '12'
While AJAX call 2 in progress, AJAX call 1 returns with value '1'
AJAX callback 1 calls setState with value '1'
componentDidUpdate triggered from re-render
Value '1' is different from '12', so
AJAX call 3 made with value '1'
While AAJX call 3 in progress, AJAX call 2 returns with value '12'...
TL;DR an infinite loop occurs despite the last-state check in componentDidUpdate, since two overlapping AJAX calls give alternating values to setState.
Approach 2
To address this, my second approach simplifies the system and makes the AJAX call directly from the event handler:
handleChange(event) {
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
setState({ inputText: response.validatedInputTextValue });
});
}
If I do this, however, the immediate update of the controlled component value is stalled until the AJAX call completes and calls setState. It is simple and stable, only setting state and rendering once; but stalling input while waiting on an AJAX call is bad UX. The first approach at least has some semblance of an (overly) immediate update.
Approach 3?
While I am waiting for an answer, I am going to implement the following Approach 3, which is basically an enhanced version of Approach 1:
Add a request ID to the AJAX call data which is incremented every time the call is made
Echo the request ID back in the response
In the callback, if the current request ID is greater than that of the response, the response has expired data
If the response data is not expired, call setState
Question
I am still relatively new to React. I imagine someone else has encountered this use case, but I am having trouble finding a solution. I would like a way to set the state and update the component's value immediately, a la Approach 1, and still have Approach 2's data stability. Approach 3 seems promising, but a little too complicated. Is there an elegant pattern that accomplishes this?
The suggested solution (#1) has a big caveat:
You have no guarantee that the first request will return before the second.
In order to avoid it, you can follow one of these approaches:
Lock the select input:
Your select component:
const Select = props => {
const {disabled, options} = props;
return (<select disabled={disabled}>
{ options.map(item => <option value={item}> {item} </option> }
</select>)
}
Your logical component:
class LogicalComponent extends React.Component {
constructor(props) {
this.state = {
selectDisabled: false;
options: ['item1', 'item2', 'item3'],
inputText: ''
}
}
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
// disabling the select until the request finishes
this.setState({ selectDisabled: true });
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
//re-enabling it when done
setState({ inputText: response.validatedInputTextValue, selectDisabled: false }); // will also trigger componentDidUpdate
// don't forget to enable it when the request is failed
}).fail(res => this.setState({selectDisabled: false}));
}
}
render() {
const { selectDisabled, options, inputText } = this.state;
return <>
<Select disabled={selectDisabled} options={options} />
<input type="text" value={inputText}/>
<>
}
}
Cancel the request that's in progress
If you already have an AJAX request in progress, you can cancel it and fire a new one. This will guarantee that only the recent request is returned.
class LogicalComponent extends React.Component {
constructor(props) {
this.requestInProgress = null;
this.state = {
options: ['item1', 'item2', 'item3'],
inputText: ''
}
}
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
// checking to see if there's a request in progress
if(this.requestInProgress && this.requestInProgress.state() !== "rejected") {
// aborting the request in progress
this.requestInProgress.abort();
}
// setting the current requestInProgress
this.requestInProgress = $.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
setState({ inputText: response.validatedInputTextValue }); // will also trigger componentDidUpdate
// don't forget to enable it when the request is failed
})
}
}
render() {
const { selectDisabled, options, inputText } = this.state;
return <>
<Select disabled={selectDisabled} options={options} />
<input type="text" value={inputText}/>
<>
}
}
I ended up reverting back to Approach 1, but debouncing the input to eliminate the overlap.
In particular, I used Lodash to debounce a method refactored from the code in componentDidUpdate that actually made the AJAX call:
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.validateInput = this.validateInput.bind(this);
this.validateInputDebounced = _.debounce(this.validateInput, 100);
}
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
validateInputDebounced(inputTextValue);
}
}
validateInput(newInputTextValue) {
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: newInputTextValue })
// set other AJAX options
}).done((response) => {
setState({ inputText: response.validatedInputTextValue }); // will also trigger componentDidUpdate
});
}
This is in part based on the work done here: https://medium.com/#platonish/debouncing-functions-in-react-components-d42f5b00c9f5
Edit
Upon further examination, this method falls short as well. If the AJAX call is sufficiently longer than the debounce, the requests potentially resolve out of order again. I think I will keep the debounce logic to save on network traffic; but the accepted solution, cancelling a previous in-progress request, sufficiently addresses the issue.
React Doc says
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state
Does this mean that i can't trust this.state at any place?
For example:
MyComponent extends Component {
// ...
handleClick () {
// ...
fetch(targetUrl, {
method: 'POST',
body: JSON.stringify({
param1: this.state.param1
})
})
}
// ...
}
Does it mean that i may send wrong param1 to targetUrl(Since this.state may not been updated yet)?
set state is asyncchronus . if you want to do something with state like
this.setState({param1:true},()=>{
fetch(targetUrl, {
method: 'POST',
body: JSON.stringify({
param1: this.state.param1
})
})
})
it takes a call back where you can get state after state is updated and perform action with the updated one.. hope it helps :)
It depends on what you do when handleClick.
If your handleClick method is like this:
handleClick () {
this.setState({ param1: 'something'});
fetch(targetUrl, {
method: 'POST',
body: JSON.stringify({
param1: this.state.param1
})
})
}
Then when you call fetch, the this.state.param1 is not updated yet. because setState is asynchronous.
And if you don't setState in handleClick, then you will be fine with your code.
Read more about setState Beware: React setState is asynchronous!
It means this.setState({}) updates state asynchronously and not instantly. Reaat will decide when to update the state via this.setState({}).
So if you do this.setState({data: response}), it is not guaranteed that it will state instantly, it might take some time to update it.
You can check this using below code:
this.setState({data: response}, () => console.log('state updated'));
Above is updating state and when updated it will execute call back method.