Hi in my application we are using initialstate where the application sample data will be defined and using the context for state management, below is my sample initial state:
-
creditCard: {
isSaved: false,
lastFourDigits: "1235",
loading: false,
cardholder: "",
cardnumber: "",
cardmonth: "",
cardyear: "",
cardcvv: "",
},
etc:{}....
and in my component i am using usestate for setting the data as below :
const { state,actionsCollection } = useContext(StateContext);
const [cardholder, setcardholder] = useState("");
const [cardnumber, setcardnumber] = useState("");
const [cardmonth, setcardmonth] = useState("");
const [cardyear, setcardyear] = useState("");
const [cardcvv, setcardcvv] = useState("");
and in onchange i am setting state as below:
<TextField
name="cardmonth"
label="MM"
error={errors.cardmonth}
value={cardmonth}
onChange={onChange}
onBlur={validateInput}
helperText={errors.cardmonth && "Invalid month"}
className={classes.expiryDateInputs}
/>
onchange=()=>{
let cardMonth = /^0[1-9]|1[0-2]/.test(e.target.value);
if (cardMonth === true) {
setcardmonth(e.target.value.replace(/\D/g, "").slice(0, 2));
setErrors({ ...errors, cardmonth: false });
} else {
setErrors({ ...errors, cardmonth: true });
}
if (state.creditCard.cardyear !== "") {
validateExpiryDate();
}
}
passing states to non related components using below code:
const validateForm = () => {
return actionsCollection.booking.validateForm(
errors,
setErrors,
cardholder,
setcardholder,
cardnumber,
setcardnumber,
cardType,
setCardType,
cardmonth,
setcardmonth,
cardyear,
setcardyear,
cardcvv,
setcardcvv,
isCurrentCaseIncluded,
cardYearValue
);
};
and in actions i am using this code:
const validateForm = (
errors,
setErrors,
cardholder,
setcardholder,
cardnumber,
setcardnumber,
cardType,
setCardType,
cardmonth,
setcardmonth,
cardyear,
setcardyear,
cardcvv,
setcardcvv,
isCurrentCaseIncluded,
cardYearValue
) => {
some validation logic.....
}
is this the correct way what i am doing, can anyone please tell me what i am doing in onchange and in html code is correct or not..
If you need to validate your input inside onChange this code is perfectly fine.
But ,It's not a good idea to validate input on the onChange event . Put your validation logic inside onBlur or onSubmit events and in onChange event , simply set state to event value.
also, I would recommend better naming convention.
onChange={(e)=>handleOnChange(e)}
///////////
handleOnChange=(event)=>{
setcardmonth(event.target.value);
}
// move your validation logic to onBlur or onSubmit
Seems like you have defined your initialState up on the context/global level, but at the same time redefined it in one of your local component with a bunch more individual state:
const { state } = useContext(StateContext);
const [cardholder, setcardholder] = useState("");
const [cardnumber, setcardnumber] = useState("");
const [cardmonth, setcardmonth] = useState("");
const [cardyear, setcardyear] = useState("");
const [cardcvv, setcardcvv] = useState("");
That effectly makes your onChange function only update the state you redefined in your component, and it does not affect your context state, and won't update UI if the UI is dependent on context state.:
onchange=()=>{
let cardMonth = /^0[1-9]|1[0-2]/.test(e.target.value);
if (cardMonth === true) {
setcardmonth(e.target.value.replace(/\D/g, "").slice(0, 2));
setErrors({ ...errors, cardmonth: false });
} else {
setErrors({ ...errors, cardmonth: true });
}
if (state.creditCard.cardyear !== "") {
validateExpiryDate();
}
Meaning you are doing some extra unnecessary work by simplying redefinning things/states.
You can do one of the three ways:
Go straight for local state without global state, and if some child components needs them, you can just pass props and drill them down;
Or combine global and local state: for those states that need to be shared between different non-related components, you define it in global level, ie. your initialstate, for those that doesn't, put them in a local state;
Or go straight for global state.
On the straight global state approach, you can combine useReducer with context, and that makes updating state more managable(considering your initialstate is relatively complex), otherwise, you can pass setState function:
const { state, setState } = useContext(StateContext); // <- pass down setState from context as well
// no need for local states
// this is how to use state value from context:
<TextField
name="cardmonth"
label="MM"
error={errors.cardmonth}
value={state.cardmonth} // <- access state value using dot notation
onChange={onChange}
onBlur={validateInput}
helperText={errors.cardmonth && "Invalid month"}
className={classes.expiryDateInputs}
/>
// this is how to update value from context:
onchange=()=>{
let cardMonth = /^0[1-9]|1[0-2]/.test(e.target.value);
if (cardMonth === true) {
setState(); // <- your update logic
} else {
setState(); // <- your update logic
}
if (state.creditCard.cardyear !== "") {
validateExpiryDate();
}
Related
useState does not update the state immediately.
I'm using react-select and I need to load the component with the (multi) options selected according to the result of the request.
For this reason, I created the state defaultOptions, to store the value of the queues constant.
It turns out that when loading the component, the values are displayed only the second time.
I made a console.log in the queues and the return is different from empty.
I did the same with the defaultOptions state and the return is empty.
I created a codesandbox for better viewing.
const options = [
{
label: "Queue 1",
value: 1
},
{
label: "Queue 2",
value: 2
},
{
label: "Queue 3",
value: 3
},
{
label: "Queue 4",
value: 4
},
{
label: "Queue 5",
value: 5
}
];
const CustomSelect = (props) => <Select className="custom-select" {...props} />;
const baseUrl =
"https://my-json-server.typicode.com/wagnerfillio/api-json/posts";
const App = () => {
const userId = 1;
const initialValues = {
name: ""
};
const [user, setUser] = useState(initialValues);
const [defaultOptions, setDefaultOptions] = useState([]);
const [selectedQueue, setSelectedQueue] = useState([]);
useEffect(() => {
(async () => {
if (!userId) return;
try {
const { data } = await axios.get(`${baseUrl}/${userId}`);
setUser((prevState) => {
return { ...prevState, ...data };
});
const queues = data.queues.map((q) => ({
value: q.id,
label: q.name
}));
// Here there is a different result than emptiness
console.log(queues);
setDefaultOptions(queues);
} catch (err) {
console.log(err);
}
})();
return () => {
setUser(initialValues);
};
}, []);
// Here is an empty result
console.log(defaultOptions);
const handleChange = async (e) => {
const value = e.map((x) => x.value);
console.log(value);
setSelectedQueue(value);
};
return (
<div className="App">
Multiselect:
<CustomSelect
options={options}
defaultValue={defaultOptions}
onChange={handleChange}
isMulti
/>
</div>
);
};
export default App;
React don't update states immediately when you call setState, sometimes it can take a while. If you want to do something after setting new state you can use useEffect to determinate if state changed like this:
const [ queues, setQueues ] = useState([])
useEffect(()=>{
/* it will be called when queues did update */
},[queues] )
const someHandler = ( newValue ) => setState(newValue)
Adding to other answers:
in Class components you can add callback after you add new state such as:
this.setState(newStateObject, yourcallback)
but in function components, you can call 'callback' (not really callback, but sort of) after some value change such as
// it means this callback will be called when there is change on queue.
React.useEffect(yourCallback,[queue])
.
.
.
// you set it somewhere
setUserQueues(newQueues);
and youre good to go.
no other choice (unless you want to Promise) but React.useEffect
Closures And Async Nature of setState
What you are experiencing is a combination of closures (how values are captured within a function during a render), and the async nature of setState.
Please see this Codesandbox for working example
Consider this TestComponent
const TestComponent = (props) => {
const [count, setCount] = useState(0);
const countUp = () => {
console.log(`count before: ${count}`);
setCount((prevState) => prevState + 1);
console.log(`count after: ${count}`);
};
return (
<>
<button onClick={countUp}>Click Me</button>
<div>{count}</div>
</>
);
};
The test component is a simplified version of what you are using to illustrate closures and the async nature of setState, but the ideas can be extrapolated to your use case.
When a component is rendered, each function is created as a closure. Consider the function countUp on the first render. Since count is initialized to 0 in useState(0), replace all count instances with 0 to see what it would look like in the closure for the initial render.
const countUp = () => {
console.log(`count before: ${0}`);
setCount((0) => 0 + 1);
console.log(`count after: ${0}`);
};
Logging count before and after setting count, you can see that both logs will indicate 0 before setting count, and after "setting" count.
setCount is asynchronous which basically means: Calling setCount will let React know it needs to schedule a render, which it will then modify the state of count and update closures with the values of count on the next render.
Therefore, initial render will look as follows
const countUp = () => {
console.log(`count before: 0`);
setCount((0) => 0 + 1);
console.log(`count after: 0`);
};
when countUp is called, the function will log the value of count when that functions closure was created, and will let react know it needs to rerender, so the console will look like this
count before: 0
count after: 0
React will rerender and therefore update the value of count and recreate the closure for countUp to look as follows (substituted the value for count).This will then update any visual components with the latest value of count too to be displayed as 1
const countUp = () => {
console.log(`count before: 1`);
setCount((1) => 1 + 1);
console.log(`count after: 1`);
};
and will continue doing so on each click of the button to countUp.
Here is a snip from codeSandbox. Notice how the console has logged 0 from the intial render closure console log, yet the displayed value of count is shown as 1 after clicking once due to the asynchronous rendering of the UI.
If you wish to see the latest rendered version of the value, its best to use a useEffect to log the value, which will occur during the rendering phase of React once setState is called
useEffect(() => {
console.log(count); //this will always show the latest state in the console, since it reacts to a change in count after the asynchronous call of setState.
},[count])
You need to use a parameter inside the useEffect hook and re-render only if some changes are made. Below is an example with the count variable and the hook re-render only if the count values have changed.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
The problem is that await api.get() will return a promise so the constant data is not going to have it's data set when the line setUserQueues(queues); is run.
You should do:
api.get(`/users/${userId}`).then(data=>{
setUser((prevState) => {
return { ...prevState, ...data };
});
const queues = data.queues.map((q) => ({
value: q.id,
label: q.name,
}));
setUserQueues(queues);
console.log(queues);
console.log(userQueues);});
I have a controlled input component with some action on the value which is passed to local and redux state.
At the moment on the onchange event im calling a function which "clean up" the value and then it send it to the local state and also send it to the redux state, as you can see in the following code.
componentDidUpdate(prevProps: Readonly<Props>): void {
if (this.props.value !== prevProps.value) {
this.updateInputValue(this.props.value);
}
}
handleOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
const value = e.target.value || '';
if (value === '' || NUMBER_REGEX.test(value)) {
this.updateInputValue(value);
this.props.onChange(e);
}
};
updateInputValue = (inputValue: string): void => this.setState({ inputValue });
onChangeInputField = (e: any): void => {
const { alphanumeric, uppercase } = this.props;
if (trim) e.target.value = e.target.value.replace(/\s/g, '');
if (alphanumeric && ALPHANUMERIC_REGEXP.test(e.target.value)) return;
if (uppercase) e.target.value = e.target.value.toUpperCase();
this.updateInputValue(e.target.value);
this.props.onChange(e);
};
My concern here is that:
onChangeInputField triggers:
updateInputValue which updates state value.
props.onChange which updates redux store value
because something was updated, now we're calling componentDidUpdate which:
Checks if received props are matching because we updated value in store with props.onChange, we're receiving new prop values
because of that updateInputValue is once again triggered - which updates state again.
The problems i see here is that first of all we have multiple sources of truth
Secondly i'm rerendering element multiple times.
what would be the correct flow?
I am currently converting this open source template (React + Firebase + Material UI). If you look in many parts of the codebase, you'll noticed after a state variable is changed, there will then be a call back. Here is one example from the signUp method found within SignUpDialog.js file:
signUp = () => {
const {
firstName,
lastName,
username,
emailAddress,
emailAddressConfirmation,
password,
passwordConfirmation
} = this.state;
const errors = validate({
firstName: firstName,
lastName: lastName,
username: username,
emailAddress: emailAddress,
emailAddressConfirmation: emailAddressConfirmation,
password: password,
passwordConfirmation: passwordConfirmation
}, {
firstName: constraints.firstName,
lastName: constraints.lastName,
username: constraints.username,
emailAddress: constraints.emailAddress,
emailAddressConfirmation: constraints.emailAddressConfirmation,
password: constraints.password,
passwordConfirmation: constraints.passwordConfirmation
});
if (errors) {
this.setState({
errors: errors
});
} else {
this.setState({
performingAction: true,
errors: null
}, () => { //!HERE IS WHERE I AM CONFUSED
authentication.signUp({
firstName: firstName,
lastName: lastName,
username: username,
emailAddress: emailAddress,
password: password
}).then((value) => {
this.props.dialogProps.onClose();
}).catch((reason) => {
const code = reason.code;
const message = reason.message;
switch (code) {
case 'auth/email-already-in-use':
case 'auth/invalid-email':
case 'auth/operation-not-allowed':
case 'auth/weak-password':
this.props.openSnackbar(message);
return;
default:
this.props.openSnackbar(message);
return;
}
}).finally(() => {
this.setState({
performingAction: false
});
});
});
}
};
With hooks, I tried something like this within the else statement...
setPerformingAction(true)
setErrors(null), () => {...}
I'll be honest, I am not the greatest at callbacks. From what I think this is doing is then calling the following methods after the state has been set. That said, this is not correct according to eslint and I was hoping to see if anyone could help. Thanks, Brennan.
If I understand your question correctly, you would like to know how to achieve the same behavior that the class based setState callback provides, but using functional components..
Thinking in functional components is a different ballgame than thinking in class based components.. The easiest way to put it is that class based components are more imperative, while hooks/functional components are more declarative.
The useEffect hook requires a dependency array (the part at the end }, [clicks]) is the dependency array) - anytime a variable that is included in the dependency array is changed, the useEffect method is fired.
What this means is you can use useEffect in a similar fashion to a setState callback.. Hooks allow you to focus in on, and have fine-grained control over, very specific parts of your state.
This is a good thread to check out - and more specifically, a good explanation of the difference between class based (setState) and hooks based (useState) paradigms.
The following example demonstrates how to achieve something similar to "callback" behavior, but using hooks/functional components.
const { render } = ReactDOM;
const { Component, useState, useEffect } = React;
/**
* Class based with setState
*/
class MyClass extends Component {
state = {
clicks: 0,
message: ""
}
checkClicks = () => {
let m = this.state.clicks >= 5 ? "Button has been clicked at least 5 times!" : "";
this.setState({ message: m });
}
handleIncrease = event => {
this.setState({
clicks: this.state.clicks + 1
}, () => this.checkClicks());
}
handleDecrease = event => {
this.setState({
clicks: this.state.clicks - 1
}, () => this.checkClicks());
}
render() {
const { clicks, message } = this.state;
return(
<div>
<h3>MyClass</h3>
<p>Click 'Increase' 5 times</p>
<button onClick={this.handleIncrease}>Increase</button>
<button onClick={this.handleDecrease}>Decrease</button>
<p><b><i>MyClass clicks:</i></b> {clicks}</p>
<p>{message}</p>
</div>
);
}
}
/**
* Function based with useState and useEffect
*/
function MyFunction() {
const [clicks, setClicks] = useState(0);
const [message, setMessage] = useState("");
useEffect(() => {
let m = clicks >= 5 ? "Button has been clicked at least 5 times!" : "";
setMessage(m);
}, [clicks]);
const handleIncrease = event => setClicks(clicks + 1);
const handleDecrease = event => setClicks(clicks - 1);
return(
<div>
<h3>MyFunction</h3>
<p>Click 'Increase' 5 times</p>
<button onClick={handleIncrease}>Increase</button>
<button onClick={handleDecrease}>Decrease</button>
<p><b><i>MyFunction clicks:</i></b> {clicks}</p>
<p>{message}</p>
</div>
);
}
function App() {
return(
<div>
<MyClass />
<hr />
<MyFunction />
</div>
);
}
render(<App />, document.body);
p {
margin: 1px;
}
h3 {
margin-bottom: 2px;
}
h3 {
margin-top: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
You can use the setter of useState multiple times in a handler but you should pass a function to the setter instead of just setting it.
This also solves missing dependency if you create your handler with useEffect.
const [state, setState] = useState({ a: 1, b: 2 });
const someHandler = useCallback(
() => {
//using callback method also has the linter
// stop complaining missing state dependency
setState(currentState => ({ ...currentState, a: currentState.a + 1 }));
someAsync.then(result =>
setState(currentState => ({
...currentState,
b: currentState.a * currentState.b,
}))
);
},
//if I would do setState({ ...state, a: state.a + 1 });
// then the next line would need [state] because the function
// has a dependency on the state variable. That would cause
// someHandler to be re created every time state changed
// and can make useCallback quite pointless
[] //state dependency not needed
);
Note that this component can actually be unmounted before your async work finishes and would cause a warning if you call state setter when component is unmounted so you'd better wrap the setter.
Last time I told you that checking mounted is pointless because you were checking it in App component and that component never unmounts (unless you can show me some place in your code that does unmount it) but this component looks like something that may unmount at some point.
In this case if you want to run some callback function if there are any errors after performing an action, you can use useEffect in this case since react hooks do not accept a 2nd optional callback function as with setState.
you can add errors into the dependency array of useEffect and write your callback function inside useEffect. Which will potentionally mean to run some funtion if there are any errors.
performAnAction = () => {
if (actionWentWrong) {
setError(); // maintained in state using useState ( const [error, setError] = useState(false);
}
}
useEffect(() => {
// inside of this function if there are any errors do something
if(error) {
// do something which you were previously doing in callback function
}
}, [error]); // this useEffect is watching if there is any change to this error state
I'm experiencing some odd behavior with react's useState hook. I would like to know why this is happening. I can see a few ways to sidestep this behavior, but want to know whats going on.
I am initializing the state with the following const:
const initialValues = {
order_id: '',
postal_code: '',
products: [
{
number: '',
qty: ''
}
]
}
const App = (props) => {
const [values, setValues] = React.useState(initialValues);
...
products is an array of variable size. As the user fills in fields more appear.
The change handler is:
const handleProductChange = (key) => (field) => (e) => {
if (e.target.value >= 0 || e.target.value == '') {
let products = values.products;
products[key][field] = e.target.value;
setValues({ ...values, products });
}
}
What I am noticing is that if I console log initialValues, the products change when the fields are changed. None of the other fields change, only inside the array.
Here is a codepen of a working example.
How is this possible? If you look at the full codepen, you'll see that initialValues is only referenced when setting default state, and resetting it. So I don't understand why it would be trying to update that variable at all. In addition, its a const declared outside of the component, so shouldn't that not work anyway?
I attempted the following with the same result:
const initialProducts = [
{
number: '',
qty: ''
}
];
const initialValues = {
order_id: '',
postal_code: '',
products: initialProducts
}
In this case, both consts were modified.
Any insight would be appreciated.
Alongside exploding state into multiple of 1 level deep you may inline your initial:
= useState({ ... });
or wrap it into function
function getInitial() {
return {
....
};
}
// ...
= useState(getInitial());
Both approaches will give you brand new object on each call so you will be safe.
Anyway you are responsible to decide if you need 2+ level nested state. Say I see it legit to have someone's information to be object with address been object as well(2nd level deep). Splitting state into targetPersonAddress, sourePersonAddress and whoEverElsePersonAddress just to avoid nesting looks like affecting readability to me.
This would be a good candidate for a custom hook. Let's call it usePureState() and allow it to be used the same as useState() except the dispatcher can accept nested objects which will immutably update the state. To implement it, we'll use useReducer() instead of useState():
const pureReduce = (oldState, newState) => (
oldState instanceof Object
? Object.assign(
Array.isArray(oldState) ? [...oldState] : { ...oldState },
...Object.keys(newState).map(
key => ({ [key]: pureReduce(oldState[key], newState[key]) })
)
)
: newState
);
const usePureState = initialState => (
React.useReducer(pureReduce, initialState)
);
Then the usage would be:
const [values, setValues] = usePureState(initialValues);
...
const handleProductChange = key => field => event => {
if (event.target.value >= 0 || event.target.value === '') {
setValues({
products: { [key]: { [field]: event.target.value } }
});
}
};
Probably the simplest move forward is to create a new useState for products which I had started to suspect before asking the question, but a solution to keep the logic similar to how it was before would be:
let products = values.products.map(product => ({...product}));
to create a completely new array as well as new nested objects.
As #PatrickRoberts pointed out, the products variable was not correctly creating a new array, but was continuing to point to the array reference in state, which is why it was being modified.
More explanation on the underlying reason initialValues was changed: Is JavaScript a pass-by-reference or pass-by-value language?
I have a component that uses useState() to handle the state of its floating label, like this:
const FloatingLabelInput = props => {
const {
value = ''
} = props
const [floatingLabel, toggleFloatingLabel] = useState(value !== '')
I have a series of those components and you'd expect initialFloatingLabel and floatingLabel to always be the same, but they're not for some of them! I can see by logging the values:
const initialFloatingLabel = value !== ''
console.log(initialFloatingLabel) // false
const [floatingLabel, toggleFloatingLabel] = useState(initialFloatingLabel)
console.log(floatingLabel) // true???
And it's a consistent result. How is that possible?
How come value can be different from initialValue in the following example? Is it a sort of race condition?
const [value, setValue] = useState(initialValue)
More details here
UPDATE
This (as suggested) fixes the problem:
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...but it creates another one: if I focus on a field, type something and then delete it until the value is an empty string, it will "unfloat" the label, like this (the label should be floating):
I didn't intend to update the floatingLabel state according to the input value at all times; the value of initialFloatingLabel was only meant to dictate the initial value of the toggle, and I'd toggle it on handleBlur and handleChange events, like this:
const handleFocus = e => {
toggleFloatingLabel(true)
}
const handleBlur = e => {
if (value === '') {
toggleFloatingLabel(false)
}
}
Is this approach wrong?
UPDATE
I keep finding new solutions to this but there's always a persisting problem and I'd say it's an issue with Formik - it seems to initially render all my input component from its render props function before the values are entirely computed from Formik's initialValues.
For example:
I added another local state which I update on the handleFocus and handleBlur:
const [isFocused, setFocused] = useState(false)
so I can then do this to prevent unfloating the label when the input is empty but focused:
useEffect(() => {
const shouldFloat = value !== '' && !isFocused
setFloatLabel(shouldFloat)
}, [value])
I'd still do this to prevent pre-populated fields from having an animation on the label from non-floating to floating (I'm using react-spring for that):
const [floatLabel, setFloatLabel] = useState(value !== '')
But I'd still get an animation on the label (from "floating" to "non-floating") on those specific fields I pointed out in the beginning of this thread, which aren't pre-populated.
Following the suggestion from the comments, I ditched the floatingLabel local state entirely and just kept the isFocused local state. That's great, I don't really need that, and I can only have this for the label animation:
const animatedProps = useSpring({
transform: isFocused || value !== '' ? 'translate3d(0,-13px,0) scale(0.66)' : 'translate3d(0,0px,0) scale(1)',
config: {
tension: 350,
},
})
The code looks cleaner now but I still have the an animation on the label when there shouldn't be (for those same specific values I mentioned at the start), because value !== '' equals to true for some obscure reason at a first render and then to false again.
Am I doing something wrong with Formik when setting the initial values for the fields?
You have the use useEffect to update your state when initialFloatingLabel change.
const initialFloatingLabel = value !== ''
const [floatingLabel, setFloatingLabel] = useState(initialFloatingLabel)
// calling the callback when initialFloatingLabel change
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...
Your problem look like prop drilling issue. Perhaps you should store floatingLabel in a context.
// floatingLabelContext.js
import { createContext } from 'react'
export default createContext({})
// top three component
...
import { Provider as FloatingLabelProvider } from '../foo/bar/floatingLabelContext'
const Container = () => {
const [floatingLabel, setFloatingLabel] = useState(false)
return (
<FloatingLabelProvider value={{ setFloatingLabel, floatingLabel }}>
<SomeChild />
</FloatingLabel>
)
}
// FloatingLabelInput.js
import FloatingLabelContext from '../foo/bar/floatingLabelContext'
const FloatingLabelInput = () => {
const { setFloatingLabel, floatingLabel } = useContext(FloatingLabelContext)
...
}
This way you just have to use the context to change or read the floatingLabel value where you want in your components three.