I'm pretty new to react and I ran into a weird issue today. I have a function called handleCheckBoxClick()
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked},
function () {
if (this.state.isChecked) {
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo
);
} else {
this.props.removeFromTransactionSubmissions(
this.props.submissionGuid
);
}
}
);
}
This particular function calls a function passed down through a parent called addTo/removeFromTransactionSubmission. The code for both is as follows:
addToTransactionSubmissions : function (guid, paymentInfo) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : paymentInfo.status_description.toLowerCase()},
function () {
console.log('it set the state though');
console.log(this.state.transactionSubmissions);
this.toggleButtons(this.state.submissionType);
}
);
}
var newTansactionSubmissions = update(this.state.transactionSubmissions,
{
$merge : {[guid] : paymentInfo}
});
this.setState({transactionSubmissions : newTansactionSubmissions},
function () {
console.log('state is now', this.state.transactionSubmissions);
}
);
},
removeFromTransactionSubmissions : function (guid) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : undefined},
function () {
this.toggleButtons(this.state.submissionType);
}
);
}
var newTransactionSubmission = update(this.state.transactionSubmissions,
{
[guid] : {$apply: function (x) {return undefined}}
});
this.setState({transactionSubmissions : newTransactionSubmission},
function () {
console.log('here in remove Transaction');
});
}
The problem I run into is that when addTo/removeFromTransactionSubmissions is called, the checkbox does not changes states, even though the state is changed before addTo/removeFromTransactionSubmissions is called. Through further debugging using Firebug, I discovered that 1) the functions are all being called properly, 2) if I do not set state in addTo/removeFromTransactionSubmissions everything runs without a hitch, and 3) the checkbox becomes unchecked after handleCheckboxClick completely finishes.
I suspect that for whatever reason, the state is being lost when Reactjs is trying to update the DOM. However, I do not know why this is the case and don't know how to further debug. And for clarification, the checkbox is in the child component whereas the transactionSubmissions state is in a parent, and on the click of a checkbox, transactionSubmissions is modified (Child Action modifies Parent state). If this is the wrong way to go about the problem, please tell me.
Basically what I want to do is every time I click/unclick a box, it removes the corresponding object to/from a map of ids to the object. Am I doing something incorrectly?
Thanks for the help!
I think you should use another aproach.
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked})
}
Add a method componentWillUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate) or componentDidUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate) and handle there the changes that must occur after state change
componentWillUpdate()
{
if (this.state.isChecked)
{
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo);
} else {
this.props.removeFromTransactionSubmissions(this.props.submissionGuid);
}
}
Also you should not call setState sequencially, it can throw some errors by trying to mutate a component while it was updating.
Every time your code calls setState, react goes trought all the dom, check what has changed and render the changes again, saving processing. Ref:https://facebook.github.io/react/docs/component-api.html#setstate
Related
My aim was to use a boolean stored in state to update a dropdown menu to either show or remain hidden. I wrote two functions, 'toggleHander' (triggered by a click event) which toggles the boolean state and then calls the next function - 'dropdownClickHandler'. 'dropdownClickHandler' adds a 'show' css class (or not) dependent on the state of the boolean held in state hence updating the drop down menu.
My problem (I think) is that the second function ('dropdownClickHandler') is called before the first ('toggleHandler') - even though the 'toggleHandler' is directly triggered by a click event and should only call the second function after setState has completed. This results in a user needing to click twice before the drop down shows (instead of once).
I'm not sure if this is something to do with hoisting or the asynchronous nature of setState? Or perhaps something else?
Any help appreciated
FYI - I've tried changing the initial boolean state to true and the menu works. I just don't understand the order of function calls
Codepen here: https://codepen.io/k-i-r-o-n/pen/vwERzP
Suspected offending code:
toggleHandler = () => {
this.setState(
{addClass: !this.state.addClass},
() => console.log('in toggle', this.state),
this.dropdownClickHandler()
);
}
dropdownClickHandler = () => {
console.log('in drop', this.state);
this.state.addClass === true ? boxClass = ["dropdownContent", "dropdownContent__show"] : boxClass = ["dropdownContent"]
}
proper syntax:
toggleHandler = () => {
this.setState(
{addClass: !this.state.addClass},
() => {
console.log('in toggle', this.state),
this.dropdownClickHandler()
}
);
}
but (as stated in comments) dropdownClickHandler() won't work correctly ...
...you don't need it at all (basic conditional renderings):
render() {
const boxClass = this.state.addClass ? "dropdownContent dropdownContent__show" : "dropdownContent"
return (
<div className={boxClass} >
or even simpler
render() {
return (
<div className={"dropdownContent "+ this.state.addClass && "dropdownContent__show"} >
I had a class component named <BasicForm> that I used to build forms with. It handles validation and all the form state. It provides all the necessary functions (onChange, onSubmit, etc) to the inputs (rendered as children of BasicForm) via React context.
It works just as intended. The problem is that now that I'm converting it to use React Hooks, I'm having doubts when trying to replicate the following behavior that I did when it was a class:
class BasicForm extends React.Component {
...other code...
touchAllInputsValidateAndSubmit() {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let inputs = {};
for (let inputName in this.state.inputs) {
inputs = Object.assign(inputs, {[inputName]:{...this.state.inputs[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in inputs) {
inputs[inputName].touched = true;
}
// UPDATE STATE AND CALL VALIDATION
this.setState({
inputs
}, () => this.validateAllFields()); // <---- SECOND CALLBACK ARGUMENT
}
... more code ...
}
When the user clicks the submit button, BasicForm should 'touch' all inputs and only then call validateAllFields(), because validation errors will only show if an input has been touched. So if the user hasn't touched any, BasicForm needs to make sure to 'touch' every input before calling the validateAllFields() function.
And when I was using classes, the way I did this, was by using the second callback argument on the setState() function as you can see from the code above. And that made sure that validateAllField() only got called after the state update (the one that touches all fields).
But when I try to use that second callback parameter with state hooks useState(), I get this error:
const [inputs, setInputs] = useState({});
... some other code ...
setInputs(auxInputs, () => console.log('Inputs updated!'));
Warning: State updates from the useState() and useReducer() Hooks
don't support the second callback argument. To execute a side effect
after rendering, declare it in the component body with useEffect().
So, according to the error message above, I'm trying to do this with the useEffect() hook. But this makes me a little bit confused, because as far as I know, useEffect() is not based on state updates, but in render execution. It executes after every render. And I know React can queue some state updates before re-rendering, so I feel like I don't have full control of exactly when my useEffect() hook will be executed as I did have when I was using classes and the setState() second callback argument.
What I got so far is (it seems to be working):
function BasicForm(props) {
const [inputs, setInputs] = useState({});
const [submitted, setSubmitted] = useState(false);
... other code ...
function touchAllInputsValidateAndSubmit() {
const shouldSubmit = true;
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let auxInputs = {};
for (let inputName in inputs) {
auxInputs = Object.assign(auxInputs, {[inputName]:{...inputs[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in auxInputs) {
auxInputs[inputName].touched = true;
}
// UPDATE STATE
setInputs(auxInputs);
setSubmitted(true);
}
// EFFECT HOOK TO CALL VALIDATE ALL WHEN SUBMITTED = 'TRUE'
useEffect(() => {
if (submitted) {
validateAllFields();
}
setSubmitted(false);
});
... some more code ...
}
I'm using the useEffect() hook to call the validateAllFields() function. And since useEffect() is executed on every render I needed a way to know when to call validateAllFields() since I don't want it on every render. Thus, I created the submitted state variable so I can know when I need that effect.
Is this a good solution? What other possible solutions you might think of? It just feels really weird.
Imagine that validateAllFields() is a function that CANNOT be called twice under no circunstances. How do I know that on the next render my submitted state will be already 'false' 100% sure?
Can I rely on React performing every queued state update before the next render? Is this guaranteed?
I encountered something like this recently (SO question here), and it seems like what you've come up with is a decent approach.
You can add an arg to useEffect() that should do what you want:
e.g.
useEffect(() => { ... }, [submitted])
to watch for changes in submitted.
Another approach could be to modify hooks to use a callback, something like:
import React, { useState, useCallback } from 'react';
const useStateful = initial => {
const [value, setValue] = useState(initial);
return {
value,
setValue
};
};
const useSetState = initialValue => {
const { value, setValue } = useStateful(initialValue);
return {
setState: useCallback(v => {
return setValue(oldValue => ({
...oldValue,
...(typeof v === 'function' ? v(oldValue) : v)
}));
}, []),
state: value
};
};
In this way you can emulate the behavior of the 'classic' setState().
I have tried to solve it using the useEffect() hook but it didn't quite solve my problem. It kind of worked, but I ended up finding it a little too complicated for a simple task like that and I also wasn't feeling sure enough about how many times my function was being executed, and if it was being executed after the state change of not.
The docs on useEffect() mention some use cases for the effect hook and none of them are the use that I was trying to do.
useEffect API reference
Using the effect hook
I got rid of the useEffect() hook completely and made use of the functional form of the setState((prevState) => {...}) function that assures that you'll get a current version of your state when you use it like that. So the code sequence became the following:
// ==========================================================================
// FUNCTION TO HANDLE ON SUBMIT
// ==========================================================================
function onSubmit(event){
event.preventDefault();
touchAllInputsValidateAndSubmit();
return;
}
// ==========================================================================
// FUNCTION TO TOUCH ALL INPUTS WHEN BEGIN SUBMITING
// ==========================================================================
function touchAllInputsValidateAndSubmit() {
let auxInputs = {};
const shouldSubmit = true;
setInputs((prevState) => {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
for (let inputName in prevState) {
auxInputs = Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in auxInputs) {
auxInputs[inputName].touched = true;
}
return({
...auxInputs
});
});
validateAllFields(shouldSubmit);
}
// ==========================================================================
// FUNCTION TO VALIDATE ALL INPUT FIELDS
// ==========================================================================
function validateAllFields(shouldSubmit = false) {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let auxInputs = {};
setInputs((prevState) => {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
for (let inputName in prevState) {
auxInputs =
Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
}
// ... all the validation code goes here
return auxInputs; // RETURNS THE UPDATED STATE
}); // END OF SETINPUTS
if (shouldSubmit) {
checkValidationAndSubmit();
}
}
See from the validationAllFields() declaration that I'm performing all my code for that function inside a call of setInputs( (prevState) => {...}) and that makes sure that I'll be working on an updated current version of my inputs state, i.e: I'm sure that all inputs have been touched by the touchAllInputsValidateAndSubmit() because I'm inside the setInputs() with the functional argument form.
// ==========================================================================
// FUNCTION TO CHECK VALIDATION BEFORE CALLING SUBMITACTION
// ==========================================================================
function checkValidationAndSubmit() {
let valid = true;
// THIS IS JUST TO MAKE SURE IT GETS THE MOST RECENT STATE VERSION
setInputs((prevState) => {
for (let inputName in prevState) {
if (inputs[inputName].valid === false) {
valid = false;
}
}
if (valid) {
props.submitAction(prevState);
}
return prevState;
});
}
See that I use that same pattern of the setState() with functional argument call inside the checkValidationAndSubmit() function. In there, I also need to make sure that I'm get the current, validated state before I can submit.
This is working without issues so far.
While attempting to add the functionality to my project of saving the term in the Search Bar through refreshing your browser, I got it to where things were being saved correctly. The only issue came up with me saving the term in the "handleTermChange" method of the component, so it would pass undefined if you do not change the term in any way before searching. I attempt to bypass this in the "search" method by checking if the state for the term is empty (as it's live updated with the handleTermChange method). The conditional checking if the term in the state is empty works fine, as you enter it when you search without changing anything in the SearchBar. The first console.log prints out the variable fine, but the second console log still prints out an empty string, I'm probably missing something small, but I can't tell, I'm too tired to see little things.
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
this.setState({term: savedTerm});
console.log(this.state.term);
}
this.props.onSearch(this.state.term);
}
Does this work ? Because of the state updates could be asynchronous, you can't be sure that term will be savedItem without the callback.
Doc here.
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
this.setState({term: savedTerm}, () => {
console.log(this.state.term);
this.props.onSearch(this.state.term);
});
} else {
this.props.onSearch(this.state.term);
}
}
This call is asynchronous, try adding a callback function before accessing the property like:
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
var thisState = this;
this.setState({term: savedTerm}, function () {
console.log(thisState.state.term);
});
}
this.props.onSearch(this.state.term);
}
I tried to say is I am not able to set the value using setState in sportsBasketballChange function but I am able to set it in sportsSoccerChange function
i am new to react.js
i am trying to set the value using setState.
in sportsSoccerChange function its correctly setting setState.
but sportsBasketballChange another place its not setting state properly.
can you guys tell me how to fix it.
providing my code below.
part of code
sportsSoccerChange(value) {
this.props.onChange();
let processedValue = value;
// sportsMessages sportsAirFalling
processedValue = sportsAirFallBALL(processedValue, this.props.sportsAirFall);
// sportsSuceessOnTime-ation
let sportsSuceessOnTime-ationResult = sportsSuceessOnTime-ateBALL(processedValue, this.props.sportsDrive);
if (sportsSuceessOnTime-ationResult === true) {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' });
///here i get value as sportsSuceessOnTime-
}
//this.setState({ isBALLValid: sportsSuceessOnTime-ationResult });
// formatting
processedValue = formatBALL(processedValue, this.props.sportsLongJump);
// set value in local component state
this.setState({ sportsMessagesValue: processedValue });
},
sportsBasketballChange() {
if (this.state.sportsOutcome === 'female') {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' });
///here i don't get value as sportsSuceessOnTime-
}
},
whole code here
https://gist.github.com/js08/e20c02bf21242201c1525577d55dedbc
I'm assuming that you are checking the value of this.state at those commented lines, either using logging or debugging.
setState is asynchronous. This means that there is no guarantee that the changes have occurred by the time you reach the next line of code. However, setState allows you to provide a callback function to be run after the state has finished updating. That is where you should be checking the updated value.
sportsBasketballChange() {
if (this.state.sportsOutcome === 'female') {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' },
function(){
console.log(this.state.sportsOutcome); // == 'sportsSuceessOnTime-'
}
);
console.log(this.state.sportsOutcome); // untrustworthy
}
},
I want to save a boolean value by clicking on a picture that changes its state from true to false every time I click onto the picture.
So simply I want to write something like
function SwitchByImage(propertyName) {
var oldValue=chrome.storage.sync.get({propertyName}, null);
var newValue=!oldValue;
chrome.storage.sync.set({propertyName:newValue},null);
if (newValue) {
$("#whateverimage").attr("src","upimage.jpg");
} else {
$("#whateverimage").attr("src","downimage.jpg");
}
}
I know that this does not work. Where I added "null" should be a function. My problem is: The storage works asynchronous, so it does not seem to be possible to write and read my data in a sequence.
What is the best way to get around this issue?
Since chrome.storage.sync.get is asynchronous, the fetched value will be available in the callback, so that is where the rest of the code should be placed:
function SwitchByImage(propertyName) {
chrome.storage.sync.get(propertyName, function(items) {
var oldValue = items[propertyName];
var newValue = !oldValue;
chrome.storage.sync.set({ propertyName: newValue });
if (newValue) {
$("#whateverimage").attr("src", "upimage.jpg");
} else {
$("#whateverimage").attr("src","downimage.jpg");
}
});
}