what am I doing wrong that causes the previous value to always remain in the input field, even after it gets re-rendered?
The parent holds the values to the current input value and keeps track of the index, since we are going through an object that holds multiple setting values, that way we go through each setting value 1 by 1.
PARENT:
const [setting_values,setSettingValues] = useState([
{title:"Name",description:"What is your name?",default_value:"My Name",type:3,maxChars:30,set_value:""},
{title:"Location",description:"Where do you live?",default_value:"Germany",type:3,maxChars:50,set_value:""},
{title:"Age",description:"What is your age?",default_value:18,type:1,min:1,max:200,set_value:""}
]);
const [settingIndex,setIndex] = useState(0);
const [current_input_value,setInputValue] = useState("");
let buttonAmount = 1;
if(settingIndex>0)
{
buttonAmount=2;
}
function setDefaultValue(){
// Set New Default Value
setInputValue("");
if(setting_values[settingIndex].set_value!="")
{
setInputValue(setting_values[settingIndex].set_value);
console.log(setting_values[settingIndex].set_value);
}
else
{
setInputValue("");
}
}
function saveNext(){
// Compare Values
if(setting_values[settingIndex].set_value!=current_input_value)
{
// Save Value
const new_arr = setting_values;
new_arr[settingIndex].set_value = current_input_value;
setSettingValues(new_arr);
}
// Go to next setting
setIndex(settingIndex 1);
}
function goBack(){
// Go to previous setting
setIndex(settingIndex-1);
}
function setSettingValue(new_val){
setInputValue(new_val);
}
useEffect(()=>{
setDefaultValue();
},[settingIndex]);
return (
<div>
<MainContainer>
<DashboardTitle>Create New Setting</DashboardTitle>
<div className="mb-6">
<div className="text-DT3 mb-3">Progress: {settingIndex 1}/{setting_values.length}</div>
<div className="w-full h-2 bg-DB3 border border-opacity-10 border-DT3 rounded-md">
<div style={{width:`${((settingIndex 1)/setting_values.length)*100}%`}} className="h-full bg-DTM2 duration-300"></div>
</div>
</div>
<div className="bg-B2 border border-DT3 border-opacity-10 w-full rounded-md mb-6">
<div className="flex flex-wrap justify-start max-h-[700px] overflow-y-auto">
<SettingRow {...setting_values[settingIndex]} setValue={setSettingValue} saveValue={saveNext}/>
</div>
</div>
<div className="text-T1 w-full flex justify-between flex-wrap">
{settingIndex===setting_values.length-1 &&
<PerfectMargin amount="1"><DashboardButton handleClick={saveNext}><DashboardIcon custom="1"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M18.6 11.4h-7.2v7.2c0 .8-.6 1.4-1.4 1.4-.8 0-1.4-.6-1.4-1.4v-7.2H1.4C.6 11.4 0 10.8 0 10c0-.8.6-1.4 1.4-1.4h7.2V1.4C8.6.6 9.2 0 10 0c.8 0 1.4.6 1.4 1.4v7.2h7.2c.8 0 1.4.6 1.4 1.4 0 .8-.6 1.4-1.4 1.4Z"/></svg></DashboardIcon><span className="ml-3">Create Setting</span></DashboardButton></PerfectMargin>
}
{settingIndex!=setting_values.length-1 &&
<PerfectMargin amount="1"><DashboardButton handleClick={saveNext}><DashboardIcon custom="1" width="21" height="20"><svg viewBox="0 0 21 20" xmlns="http://www.w3.org/2000/svg"><path d="M19.3846 20C18.9269 20 18.5435 19.84 18.2345 19.52C17.9243 19.2011 17.7692 18.8056 17.7692 18.3333V1.66667C17.7692 1.19444 17.9243 0.798334 18.2345 0.478334C18.5435 0.159445 18.9269 0 19.3846 0C19.8423 0 20.2257 0.159445 20.5348 0.478334C20.8449 0.798334 21 1.19444 21 1.66667V18.3333C21 18.8056 20.8449 19.2011 20.5348 19.52C20.2257 19.84 19.8423 20 19.3846 20ZM2.50385 18.2917C1.96538 18.6806 1.41346 18.7083 0.848077 18.375C0.282692 18.0417 0 17.5417 0 16.875V3.125C0 2.45833 0.282692 1.965 0.848077 1.645C1.41346 1.32611 1.96538 1.34722 2.50385 1.70833L12.5192 8.625C13.0038 8.95833 13.2462 9.41667 13.2462 10C13.2462 10.5833 13.0038 11.0417 12.5192 11.375L2.50385 18.2917Z"/></svg></DashboardIcon><span className="ml-3">Save & Continue</span></DashboardButton></PerfectMargin>
}
{settingIndex>0 &&
<PerfectMargin amount={buttonAmount}><DashboardButton handleClick={goBack}><DashboardIcon custom="1" width="21" height="20"><svg viewBox="0 0 21 20" xmlns="http://www.w3.org/2000/svg"><path d="M19.6523 8.68887H4.59885L11.1755 2.25701C11.7011 1.74299 11.7011 0.899463 11.1755 0.385442C11.0508 0.263258 10.9027 0.166322 10.7397 0.100183C10.5766 0.0340437 10.4019 0 10.2254 0C10.0489 0 9.8741 0.0340437 9.71107 0.100183C9.54804 0.166322 9.39994 0.263258 9.27527 0.385442L0.394118 9.07109C0.269185 9.19302 0.170067 9.33785 0.102439 9.4973C0.0348106 9.65674 0 9.82766 0 10.0003C0 10.1729 0.0348106 10.3438 0.102439 10.5033C0.170067 10.6627 0.269185 10.8075 0.394118 10.9295L9.27527 19.6151C9.40004 19.7371 9.54816 19.8339 9.71118 19.9C9.8742 19.966 10.0489 20 10.2254 20C10.4018 20 10.5765 19.966 10.7396 19.9C10.9026 19.8339 11.0507 19.7371 11.1755 19.6151C11.3003 19.4931 11.3992 19.3482 11.4667 19.1888C11.5343 19.0294 11.569 18.8585 11.569 18.6859C11.569 18.5134 11.5343 18.3425 11.4667 18.183C11.3992 18.0236 11.3003 17.8788 11.1755 17.7567L4.59885 11.3249H19.6523C20.3935 11.3249 21 10.7318 21 10.0069C21 9.28197 20.3935 8.68887 19.6523 8.68887Z"/></svg></DashboardIcon><span className="ml-3">Go Back</span></DashboardButton></PerfectMargin>
}
<PerfectMargin amount={buttonAmount}><Link to="/dashboard/settings"><DashboardButton><DashboardIcon custom="1"><svg width="19" height="20" viewBox="0 0 19 20" xmlns="http://www.w3.org/2000/svg"><path d="M18.2145 3.72463L11.9391 10L18.2145 16.2754C18.9048 16.9657 18.9048 18.0952 18.2145 18.7855C17.5242 19.4758 16.3946 19.4758 15.7043 18.7855L9.42896 12.5101L3.15358 18.7855C2.46329 19.4758 1.33372 19.4758 0.643433 18.7855C-0.0468581 18.0952 -0.0468579 16.9657 0.643433 16.2754L6.91881 10L0.643433 3.72463C-0.0468579 3.03434 -0.0468581 1.90477 0.643433 1.21448C1.33372 0.524186 2.46329 0.524185 3.15358 1.21448L9.42896 7.48985L15.7043 1.21448C16.3946 0.524185 17.5242 0.524186 18.2145 1.21448C18.9048 1.90477 18.9048 3.03434 18.2145 3.72463Z"/></svg></DashboardIcon><span className="ml-3">Cancel</span></DashboardButton></Link></PerfectMargin>
</div>
</MainContainer>
</div>
);
}
The Child renders the input and uses the function from the parent to update the input value. Here I have the problem, that the input field displays the same value, even after going to the next index. CHILD:
const SettingRow = ({title,description,default_value,set_value,type,min,max,max_chars,setValue,saveValue}) => {
function changeSetValue(e)
{
e.target.value = e.target.value;
setValue(e.target.value);
}
function handleInputEnter(e)
{
if(e.key ==='Enter')
{
saveValue();
}
}
function createInputComponent(){
console.log("RERENDER!");
if(set_value!="")
{
// Setting HAS value, use real value
if(type===3)
{
return <input className="h-14 p-6 w-full rounded-md text-DT4 font-medium text-md bg-DT3" type="text" autoFocus defaultValue={set_value} placeholder={default_value} onKeyDown={handleInputEnter} onChange={changeSetValue} maxLength={max_chars} />
}
}
else
{
// Setting DOESN'T have value, use placeholder
if(type===3)
{
return <input className="h-14 p-6 w-full rounded-md text-DT4 font-medium text-md bg-DT3" type="text" autoFocus defaultValue="" placeholder={default_value} onKeyDown={handleInputEnter} onChange={changeSetValue} maxLength={max_chars} />
}
}
}
return (
<div className="w-full flex flex-col justify-between items-start p-6 text-DT3 text-sm">
<span className="text-DT2 text-lg font-medium">{title}</span>
<span className="text-DT3 text-md mt-3">{description}</span>
<div className="mt-6 w-full">
{createInputComponent()}
</div>
</div>
);
}
I display a placeholder if the setting object has no value assigned, but want to display the assigned value, if I press "Go Back", since the object already has a value saved. What causes my input field value to remain the same, even though the object value changes?
EDIT: I checked the component states via the React Dev Tools and the input displays an outdated value that isn't present in any state.
CodePudding user response:
Looks like you need to set the value prop of the inputs
function createInputComponent(){
console.log("RERENDER!");
if(set_value!="")
{
// Setting HAS value, use real value
if(type===3)
{
return <input
value={set_value} //here
className="h-14 p-6 w-full rounded-md text-DT4 font-medium text-md bg-DT3" type="text" autoFocus defaultValue={set_value} placeholder={default_value} value={set_value} onKeyDown={handleInputEnter} onChange={changeSetValue} maxLength={max_chars} />
}
}
else
{
// Setting DOESN'T have value, use placeholder
if(type===3)
{
return <input
value={set_value} //here
className="h-14 p-6 w-full rounded-md text-DT4 font-medium text-md bg-DT3" type="text" autoFocus defaultValue="" placeholder={default_value} onKeyDown={handleInputEnter} onChange={changeSetValue} maxLength={max_chars} />
}
}
}
Hope it helps
CodePudding user response:
There are actually two problems here:
1. using defaultValue vs value
You are passing the value that is updated in that input to the defaultValue
prop of the input, instead of the value
prop. The defaultValue
prop only sets the value once, on the initial render. Use value
to tie the value of the input in the DOM to the value of the variable. (Thx to Azzy for pointing it out)
2. Mutating an object inside an array
In the saveNext()
function, you are mutating an object inside the array that is stored in the useState variable setting_values
.
I'll explain why this doesn't trigger a rerender below, first the solution, change your code as follows:
// Save Value
const new_arr = setting_values;
new_arr[settingIndex].set_value = current_input_value;
setSettingValues([...new_arr]); // Use spread operator to create a shallow copy
What does
[...new_arr]
do?The expression
[...new_arr]
is a combination of an array literal[]
and the spread operator...
.[]
creates a new array and the spread operator spreads the contents ofnew_arr
into it.
The important part is the new array here. Below is a snippet that demonstrates the difference.
If you run the snippet, you can see that for the button that tries to update without a new array, the array is actually updated (you can tell by the console.log), but React still does not rerender the input. The reason must lie somewhere between the setState
call and the passing of the value to the input.
There's many other threads that deal with the same underlying problem here on SO, but I still don't have a satisfying answer, why exactly React behaves that way. Might update later.
const { useState } = React;
const MyComponent = () => {
const [state1, setState1] = useState([{ value: "0" }]);
const [state2, setState2] = useState([{ value: "0" }]);
const updateStateWithNewArray = (value) => {
const new_state = state1;
new_state[0].value = value;
setState1([...new_state]);
};
const updateState = (value) => {
const new_state = state2;
new_state[0].value = value;
setState2(new_state);
console.log(new_state[0].value);
};
return (
<React.Fragment>
<input
value={state1[0].value}
onChange={(e) => updateStateWithNewArray(e.target.value)}
/>
<button onClick={() => updateStateWithNewArray("yeaaah")}>updateStateWithNewArray</button>
<br/>
<input
value={state2[0].value}
onChange={(e) => updateState(e.target.value)}
/>
<button onClick={() => updateState("nooooo")}>updateState</button>
</React.Fragment>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<MyComponent/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>