react set value to another component when change value in different component - reactjs

i have two react input components, where in one input component; value changes, i have to set the same value in another input field.
here is my two input components.
<div className="col-md-4">
<label htmlFor="saleamount">Payment </label>
<NumberFormat id="manualPaymentEntry" name="manualPaymentEntry" fixedDecimalScale={true} decimalScale={2} value={manualPayEntry.current} className="form-control" thousandSeparator={true} thousandsGroupStyle="lakh" onChange={(values) => {
const { formattedValue, value } = values;
handleManualPaymentEntry(value);
}} />
</div>
<div className="col-md-4">
<label htmlFor="selectedPayment">Actual Payment</label>
<NumberFormat readOnly={true} fixedDecimalScale={true} decimalScale={2} value={cumulativePay} className="form-control" thousandSeparator={true} thousandsGroupStyle="lakh" />
</div>
</div>
when there is change in manualPaymentEntry i have to set same value to cumulativePay
const [cumulativePay, setCumulativePay] = useState(0);
const manualPayEntry = useRef(0);
useEffect(() => {
setCumulativePay(manualPayEntry.current);
}, [manualPayEntry]);
const handleManualPaymentEntry = (value) => {
let val = parseFloat(value);
manualPayEntry.current = value;
}
i have used useRef for manualPayEntry to set current value to cumulativePay, but when there is change in manualPayEntry it is not setting value to cumulativePay, always i am getting '0' only..., but current value of manualPayEntry shouled get refelcted in cumulativePay

issue got resolved, after chaning onChange to onValueChange, as said in the documentation onchange will no longer get value , instead we need to use onValueChange
react-number-format npm
onValueChange is not same as onChange. It gets called on whenever
there is change in value which can be caused by any event like change
or blur event or by a prop change. It no longer receives event object
as second parameter.

Changing a value manually like you are doing it there:
manualPayEntry.current = value;
Does not trigger the rerendering functionalities of React. Why don't you just add the setCumulativePay directly in the handleManualPaymentEntry?
const handleManualPaymentEntry = (value) => {
let val = parseFloat(value);
manualPayEntry.current = value;
setCumulativePay(value);
}
Or you should treat manualPayEntry as a state as well and not a ref, though it's not really advised to change a state in a useEffect based on another state. I really think changing directly the needed state in your handler function is the best solution.

Related

Unchecking a checkbox in react from several checkbox groups (I'm using React hooks)

I have several checkboxes running in several checkbox groups. I can't figure out how to uncheck (thus changing the state) on a particular checkbox. FOr some reason I can't reach e.target.checked.
<Checkbox
size="small"
name={item}
value={item}
checked={checkboxvalues.includes(item)}
onChange={(e) => handleOnChange(e)}
/>
and my function
const handleOnChange = (e) => {
const check = e.target.checked;
setChecked(!check);
};
I made a working sample of the component in this sandbox.
You need to create handleOnChange function specific to each group. I have created one for Genre checkbox group in similar way you can create for other groups.
Here is handler function.
const handleOnChangeGenre = (e) => {
let newValArr = [];
if (e.target.checked) {
newValArr = [...state.pillarGenre.split(","), e.target.value];
} else {
newValArr = state.pillarGenre
.split(",")
.filter((theGenre) => theGenre.trim() !== e.target.value);
}
setState({ ...state, pillarGenre: newValArr.join(",") });
};
pass this function as handleOnChange prop to CustomCheckboxGroup as below.
<CustomCheckboxGroup
checkboxdata={genres}
checkboxvalues={state.pillarGenre}
value={state.pillarGenre}
sectionlabel="Genre"
onToggleChange={handleGenreSwitch}
togglechecked={genreswitch}
handleOnChange={handleOnChangeGenre}
/>
comment your handleOnChange function for testing.
check complete working solution here in sandbox -
complete code
Here's how I'd do it: https://codesandbox.io/s/elastic-pateu-flwqvp?file=/components/Selectors.js
I've abstracted the selection logic into a useSelection() custom hook, which means current selection is to be found in store[key].selected, where key can be any of selectors's keys.
items, selected, setSelected and sectionLabel from each useSelection() call are stored into store[key] and spread onto a <CustomCheckboxGroup /> component.
The relevant bit is the handleCheck function inside that component, which sets the new selection based on the previous selection's value: if the current item is contained in the previous selected value, it gets removed. Otherwise, it gets added.
A more verbose explanation (the why)
Looking closer at your code, it appears you're confused about how the checkbox components function in React.
The checked property of the input is controlled by a state boolean. Generic example:
const Checkbox = ({ label }) => {
const [checked, setChecked] = useState(false)
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<span>{label}</span>
</label>
)
}
On every render, the checked value of the <input /> is set according to current value of checked state. When the input's checked changes (on user interaction) the state doesn't update automatically. But the onChange event is triggered and we use it to update the state to the negative value of the state's previous value.
When dealing with a <CheckboxList /> component, we can't serve a single boolean to control all checkboxes, we need one boolean for each of the checkboxes being rendered. So we create a selected array and set the checked value of each <input /> to the value of selected.includes(item) (which returns a boolean).
For this to work, we need to update the value of selected array in every onChange event. We check if the item is contained in the previous version of selected. If it's there, we filter it out. If not, we add it:
const CheckboxList = ({ items }) => {
const [selected, setSelected] = useState([])
const onChecked = (item) =>
setSelected((prev) =>
prev.includes(item)
? prev.filter((val) => val !== item)
: [...prev, item]
)
return items.map((item) => (
<label key={item}>
<input
type="checkbox"
checked={selected.includes(item)}
onChange={() => onChecked(item)}
/>
<span>{item}</span>
</label>
))
}
Hope that clears things up a bit.
The best way to do it, it's to save selected checkboxes into a state array, so to check or uncheck it you just filter this state array based on checkbox value property that need to be unique.
Try to use array.some() on checkbox property checked. To remove it it's just filter the checkboxes setted up in the state array that are different from that single checkbox value.

Can I programmatically update the values of React bootstrap <Form.Control> after it's been updated?

Is it possible to update the value of a React Bootstrap <Form.Control> after it's been edited by a user?
I've created a small Bootstrap form in React with four controlled fields. Details are below. Submission works, but I'd like a way to update the values of various fields programmatically, both to reset the form and to fill in sets of common defaults.
My code, in which various buttons call setFormContents, works until a field is updated. After this, updating the value passed into the defaultValue of the <Form.Control>. This makes sense, but I'm not sure what is the preferred way to proceed.
The variables formContents and setFormContents are assorted with the parent element of <MyForm>.
Details
The state of the form and the associated updating function is provided via props
define MyForm(props)
return <Form
onSubmit={(e) => {
e.preventDefault();
onSubmit(props.formContents);
}}
>
<Form.Group className="mb-3" controlId="field1">
<Form.Label>Field 1</Form.Label>
<Form.Control
defaultValue={props.formContents.val1}
onChange={({ target: { value } }) =>
props.setFormContents({ ...props.formContents, val1: value })
}
/>
</Form.Group>
///... Other <Form.Group>s
</Form>
In my case, the update was for all fields. My solution, which is modeled on this StackOverflow response was to reset the form before updating it.
Create a state that will store reference to the DOM <form> element.
const [formDOM, setFormDOM] = useState(null);
Use the ref= property of the <Form> element to capture the DOM element. The state will have type ?HTMLFormElement
<Form
ref={(form) => props.setFormDOM(form)}
...
>
Provide this ReactDOM to elements that update the form. Reset the form before any updates.
onClick={(e) => {
if (props.formDOM != null) {
props.formDOM.reset();
}
props.setFormContents(...);
}}
You can also use useRef() to access your From.Control programmatically :
const inputRef = useRef(null);
[...]
<Form.Control
ref={inputRef}
defaultValue={props.formContents.val1}
onChange={({ target: { value } }) =>
props.setFormContents({ ...props.formContents, val1: value })
}
/>
And then access to the value :
inputRef.current.value = someValue;
In your case, in your onClick function :
onClick={(e) => {
...
inputRef.current.value = "";
}
This approach is less concise because you have to do it on every fields but allows to independently control the content of each From.Control.

Why useRef value dynamically updates when it is an integer but stores the previous value when it is a string?

I just learned useRef and am confused about how it actually works. For example,
#1
function Ref() {
const rerenderCount = useRef(0);
useEffect(() => {
rerenderCount.current = rerenderCount.current + 1;
});
return <div>{rerenderCount.current}</div>;
}
Here, the useRef gives the same output as useState in the following code, #2
function State() {
const [rerenderCount, setRerenderCount] = useState(0);
useEffect(() => {
setRerenderCount(prevCount => prevCount + 1);
});
return <div>{rerenderCount}</div>;
}
But in this #3 code, the previousName.current value always displays the previous value. But it is set to name.
const [name, setName] = useState("");
const previousName = useRef(null);
useEffect(() => {
previousName.current = name;
}, [name]);
return (
<div className="App">
<input value={name} onChange={(e) => setName(e.target.value)} />
<div>
My name is {name} and it used to be {previousName.current}
</div>
</div>
);
Please, someone explain why the name is one step back where the integer updates on time. Also, what is the use of [name] in useEffect. Without or without it, I get the same result and rendercount.
In you example previousName is one step behind because when you change the name state the component is re-render, useEffect is called and previousName is updated but this last change doesn't cause a new rendering (useRef is not like useState, the component is not re-render), so you see name updated correctly but previousName with the same value as before, even if its value has changed.
This is because the change of previousName occurs during the subsequent rendering caused by the changing on the state.
To see its change an additional rendering would require.
To avoid this behavior you could use an event handler and not rely on the useEffect hook.
const handleChange = (text: string) => {
setName(text);
previousName.current = text;
};
return (
<div className="App">
<input value={name} onChange={(e) => handleChange(e.target.value)} />
<div>
My name is {name} and it used to be {previousName.current}
</div>
</div>
);

setState not working as expected in react hook

When I am updating the with setState hook, value is not getting updated.
As per my knowledge this setState call is async. How to solve this issue?
export default function ResultItem({result:{ name, result_type, entry, unit, min_val, max_val, in_spec}, handleEntryChange}){
const [inSpec, setInSpec] = useState(in_spec)
const [result, setResult] = useState(entry);
console.log(handleEntryChange)
return(
<Fragment>
<td>{name}</td>
<td>{result_type}</td>
<td>{}</td>
<td>
<input
name={name}
type={result_type === 'numeric' ? "number" : "text"}
value={result || ''}
onChange={(e) => {
let val = parseInt(e.target.value)
setResult(val)
if(val >= min_val && val <= max_val){
setInSpec(true);
}else{
setInSpec(false);
}
console.log(result, inSpec, val)
handleEntryChange({[name]: val, [name]: inSpec});
}}
/>
</td>
<td>{unit}</td>
<td>{inSpec? 'YES': 'NO'}{console.log(in_spec)}</td>
</Fragment>
)
}
You are basically trying to make a custom Input. In general, you need to be careful of how this input is controlled ? On your interface, you have entry (which might be the initial value) but internally you have result.
Both are actually the value in Input context, but they normally are either controlled from external (on the interface) or tracked internally, but not both.
Just imaging if entry is changed, what could be result ?
The right way is the following
const Input = ({ value, onChange }) => {
// no internal state here
return (
<input value={value} onChange={onChange} />
)
}
This is a pure pass through model with both value and onChange driven by external.
Now you might ask how do I change the look and feel of my custom Input.
You can now write wrapper for onChange to inject your own implementation.
const onValueChange = e => {
// e.target.value
// do your own handling and then
// if need call onChange(e) to handle external
}
<input onChange={onValueChange} />
I'll skip the UI change for input here, since your example is already there.
The last comment here, the controlled input does not have storage. So in order to use your input, you need to have a state defined from external.
const [result, setResult] = useState(0)
return (
<YourInput value={result} />
)
The take home message here is WHO should manage the storage (memory), this might be the #1 fixture to put into place before designing any component.

UI not re-rendering on state update using React Hooks and form submission

I'm trying to update a UI using React Hooks and a form. I have a state set to monitor the value on the form and when I click submit, I want to add this value to an array (held in state) and display it on the UI. My problem is that when I submit the value, although it is added to the array (and state is updated), the UI only updates when I change the value in the input.
My Component is as follows:
const PushToArrayUpdateState = () => {
const [array, setArray] = useState([]);
const [formVal, setFormVal] = useState(``);
const handleSubmit = event => {
event.preventDefault();
let updateArray = array;
updateArray.push(formVal);
console.log(updateArray);
setArray(updateArray);
};
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text" name="arrayVal" onChange={e => setFormVal(e.target.value)} />
<input type="submit" value="Submit" />
</form>
<div>
{array.map((val, index) => <p key={index}>{val}</p>)}
</div>
</div>
);
};
You can also see this [not] working at:
https://codesandbox.io/s/p3n327zn3q
Has anyone got any suggestions as to why the setArray in the handleSubmit function is not automatically causing the component to re-render?
Instead of
let updateArray = array;
Try this:
const updateArray = [...array];
https://codesandbox.io/embed/qxk4k3zmzq
Because arrays in JS are reference values, so when you try to copy it using the = it will only copy the reference to the original array.
A similar bug can happen with the same manifestation:
const [bought, setBought] = useState([])
...
bought.push(newItem)
setBought(bought)
To fix this you need to use
const newBought = [...bought, newItem] <- new reference
setBought(newBought)

Resources