How do I set initial values from state to dynamically added inputs? - javascript

I have dynamic inputs I can add and save to the state, but I want to be able to set initial values, to begin with. I would like to update those values and resave those edits at any time.
Here is the full code below. You can also check out the SANDBOX HERE
import { useState } from "react";
// I want to use these as my initial values. This is the object in my database:
const InitialValuesDB = [{name: "John", age: "108"}, {name: "Jane", age: "204"}]
function Form() {
const [formFields, setFormFields] = useState([{ name: "", age: "" }]);
// I can iterate the values like this:
function LoggingMap() {
InitialValuesDB.map((item, i) => {
console.log('Index:', i, 'name:', item.name);
console.log(item.name)
// But I can't access theme outside of this function:
});
}
LoggingMap()
const handleFormChange = (event, index) => {
let data = [...formFields];
data[index][event.target.name] = event.target.value;
setFormFields(data);
};
const submit = (e) => {
e.preventDefault();
console.log(formFields);
};
const addFields = () => {
let object = {
name: "",
age: "",
};
setFormFields([...formFields, object]);
};
const removeFields = (index) => {
let data = [...formFields];
data.splice(index, 1);
setFormFields(data);
};
return (
<div className="App">
<form onSubmit={submit}>
{formFields.map((form, index) => {
return (
<div key={index}>
{/* But how do I set my initial values (item.name, item.age) as initial values, so that when I reload, the saved values return */}
<input
name="name"
placeholder="Name"
onChange={(event) => handleFormChange(event, index)}
value={form.name}
/>
<input
name="age"
placeholder="Age"
onChange={(event) => handleFormChange(event, index)}
value={form.age}
/>
<button onClick={() => removeFields(index)}>Remove</button>
</div>
);
})}
</form>
<button onClick={addFields}>Add More..</button>
<br />
<button onClick={submit}>Submit</button>
</div>
);
}
export default Form;
Expected Results
If I have 5 inputs with values submitted, I want those values saved in a state and on reload, have those as initial values. I want to edit the inputs, resave that, etc.

For initially putting the items
you should replace your useState with the initial value.
Replace this with:
const [formFields, setFormFields] = useState([{ name: "", age: "" }]);
This
const [formFields, setFormFields] = useState(InitialValuesDB);

Use localStorage, write the state values to localStorage when state updates and read from localStorage on initial render to set the state back to what it was previously before the reload.
EDIT: Try out the following code and see if it fits your usecase.
import { useEffect, useState } from "react";
// I want to use these as my initial values. This is the object in my database:
const InitialValuesDB = [
{ name: "John", age: "108" },
{ name: "Jane", age: "204" },
];
function Form() {
const [formFields, setFormFields] = useState(JSON.parse(localStorage.getItem("key"))|| InitialValuesDB || [{ name: "", age: "" }]);
useEffect(() => {
localStorage.setItem("key", JSON.stringify(formFields))
},[formFields])
// I can iterate the values like this:
function LoggingMap() {
InitialValuesDB.map((item, i) => {
console.log("Index:", i, "name:", item.name);
console.log(item.name);
// But I can't access theme outside of this function:
});
}
LoggingMap();
const handleFormChange = (event, index) => {
let data = [...formFields];
data[index][event.target.name] = event.target.value;
setFormFields(data);
};
const submit = (e) => {
e.preventDefault();
console.log(formFields);
};
const addFields = () => {
let object = {
name: "",
age: "",
};
setFormFields([...formFields, object]);
};
const removeFields = (index) => {
let data = [...formFields];
data.splice(index, 1);
setFormFields(data);
};
return (
<div className="App">
<form onSubmit={submit}>
{formFields.map((form, index) => {
return (
<div key={index}>
{/* But how do I set my initial values (item.name, item.age) as initial values, so that when I reload, the saved values return */}
<input
name="name"
placeholder="Name"
onChange={(event) => handleFormChange(event, index)}
value={form.name}
/>
<input
name="age"
placeholder="Age"
onChange={(event) => handleFormChange(event, index)}
value={form.age}
/>
<button onClick={() => removeFields(index)}>Remove</button>
</div>
);
})}
</form>
<button onClick={addFields}>Add More..</button>
<br />
<button onClick={submit}>Submit</button>
</div>
);
}
export default Form;

I think I don't fully understand you question but here my solution.
Just add useEffect after your removeFields function
useEffect(() => {
setFormFields(InitialValuesDB)
}, [])

Use usefieldarry api of react hook form to maintain dynamic input fields in react form that's great and very simple.
Here is working code sandbox link
https://codesandbox.io/s/nice-swartz-7exhy2?file=/src/form.jsx
Note: I have no knowledge of typescript but implemented it in JavaScript I hope you can convert it into typescript

Related

How to update hashmap and where can I put a reset for link state on React?

So Im having problems with a form in which takes in text inputs to be set on an object. This object will then be updated in a hashmap of <key, object>.
So far, I can type in the input areas, but if I add another item in the hashmap which generates another div with the input elements it will contain the same value of the previous inputs.
So I need a way to reset those labels within the form and a way to update the hashmap.
I got an updateMap function, but I don't know where to place it for it to await the changes.
Edit: I need to reset the state of link, but when I do a function for it. It says something about preventing infinite loops.
export default function Admin() {
const [link, setLink] = useState({ name: "", link: "" });
const [links, setLinks] = useState(new Map());
const clickAddLink = () => addToMap(links.size + 1, link);
const deleteOnMap = (key) => {
setLinks((prev) => {
const newState = new Map(prev);
newState.delete(key);
return newState;
});
};
const getName = (key) =>
{
let linkFromKey = links.get(key)
return linkFromKey.name
}
const getLink = (key) =>
{
let linkFromKey = links.get(key)
return linkFromKey.link
}
const addToMap = (key, value) => {
setLinks((prev) => new Map([...prev, [key, value]]));
};
const updateMap = (key, value) => {
setLinks((prev) => new Map([...prev, [key, value]]));
};
const clear = () => {
setLinks((prev) => new Map(prev.clear()));
};
.... skipping code ....
<div>
{console.log(link.name)}
{console.log(link.link)}
{[...links.keys()].map((key) => (
<div key={key}>
{links.size > 0 && (
<div>
<form>
<span>Name</span>
<input
type="text"
placeholder={getName(key)}
required
value={link.name}
onChange={(e) =>
setLink({ name: e.target.value })
}
/>
<span>Link</span>
<input
type="text"
placeholder={getLink(key)}
required
value={link.link}
onChange={(e) =>
setLink({ link: e.target.value })
}
/>
</form>
</div>
)}
</div>
))}
</div>
{console.log(link.name)}
{console.log(link.link)}
</div>
```

My form is not updating when its. preloaded with default value - react

I have these input fields I want to already show default values in, but i also want to be able to update those values
here is the code- https://stackblitz.com/edit/react-vfcqkh?file=src/App.js
import React, { useState } from 'react';
import './style.css';
export default function App() {
const baseData = [
{nutrient: 'Energy',per100: '449kcal', per35: '157kcal', "id": 6 },
{nutrient: 'Fat',per100: '24.4g', per35: '8.6g', "id": 1 },
{ nutrient: 'Saturated fat',per100: '4.5g', per35: '1.6g', "id": 2 },
{ nutrient: 'Monounsaturated fat',per100: '13.6g', per35: '4.8g', "id": 3 }
];
// set original state as your data
const [data, setData] = useState(baseData);
const updateValue = e => {
const copiedData = [...data]; //to avoid mutating state
let index = copiedData.findIndex(obj => obj.id == e.target.id);
copiedData[index].nutrient = e.target.value;
setData(copiedData);
};
return (
<div>
<div>
<div>
{ Object.keys(baseData[0]).map(({id,nutrient}) => (
<input type="text" key={id} id={id} value={nutrient} name="productName" onChange={(e) => updateValue(e)} />
))}
</div>
{baseData.map((item) => (
<div key={item.id}>
{Object.values(item).map(({id,nutrient}) => (
<input type="text" key={id} id={id} value={nutrient} id="name" name="productName" onChange={(e) => updateValue(e)} />
))}
</div>
))}
</div>
</div>
);
}
I think you are trying to display the initial values of the form then while the user is typing or typed in values the value in the <input type="text" name="name"/> will be update or do you want to update the state with the value typed in the input field?
Displaying Default values
change the value prop/attribute of the input to defaultValue
<input type="text" key={id} id={id} defaultValue={nutrient} name="productName" onChange={(e) => updateValue(e)} />
This way user can type in value and the initial values will also be displayed.
Update values
in you update function, you can access the value that the user typed by doing this:
updateValue(e){
let value= e.target.value
// then do state update here
}
Re-defined Solution
This should work properly as I have tested the code and run it successfully
import React, {useState} from 'react'
import './style.css';
export default function App() {
const baseData = [
{ nutrient: "Energy", per100: "449kcal", per35: "157kcal", id: 6 },
{ nutrient: "Fat", per100: "24.4g", per35: "8.6g", id: 1 },
{ nutrient: "Saturated fat", per100: "4.5g", per35: "1.6g", id: 2 },
{ nutrient: "Monounsaturated fat", per100: "13.6g", per35: "4.8g", id: 3 },
];
// set original state as your data
const [data, setData] = useState(baseData);
const updateValue = (e) => {
const copiedData = [...data]; //to avoid mutating state
let index = copiedData.findIndex((obj) => obj.id === e.target.id);
copiedData[index].nutrient = e.target.value;
setData(copiedData);
};
// initialize vars to hold the baseData object keys and vals
let baseDataKeys = [],
baseDataVals = [];
// first get all the keys of the objects and store them in this var
baseData.forEach((keys) => {
baseDataKeys = Object.keys(keys);
});
// debug to see the keys
console.log(baseDataKeys);
// Secondly get all the values of the objects and store then in this var
baseData.forEach((vals) => {
baseDataVals = Object.values(vals);
});
// debug to see the valus
console.log(baseDataVals);
return (
<div>
<div>
<div>
{baseDataKeys.map((keys) => (
<input
type="text"
key={keys}
id={keys}
defaultValue={keys}
name="productName"
onChange={(e) => updateValue(e)}
/>
))}
</div>
{/* This is for the values */}
{baseDataVals.map((vals) => (
<div key={vals}>
<input
type="text"
id={vals}
defaultValue={vals}
id="name"
name="productName"
onChange={(e) => updateValue(e)}
/>
</div>
))}
</div>
</div>
);
}
Try this updated solution this is going to work accurately

Removing an item from an array of items only removes the last item

I am having trouble with deleting some element from an array of React components.
I have some simple App, which has only one button "Add form", when user clicks on this button, new Form
with firstname and lastname inputes will be added. If user wants to delete this form, user can just click delete button and remove this form from an array. However, the problem is that whenever user click on delete on some form, last element becomes removed all the time. But if you look at the console, you can see that deleted form is removed from state, but in the UI last element removed.
Sample code:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [forms, setForms] = useState([]);
const addNewForm = () => {
setForms([
...forms,
{
valuesOfForm: { firstname: "", lastname: "" }
}
]);
};
const handleChangeForms = (name, value, index) => {
const values = [...forms];
values[index].valuesOfForm[name] = value;
setForms([...values]);
};
const handleDeleteForms = (index) => {
const values = [...forms];
values.splice(index, 1);
setForms([...values]);
};
return (
<div className="App">
<div>
<button onClick={addNewForm}>Add new form</button>
{console.log(forms)}
{forms.map((form, index) => (
<SomeForm
value={form.valuesOfForm}
key={index}
index={index}
handleChangeForms={handleChangeForms}
handleDeleteForms={handleDeleteForms}
/>
))}
</div>
</div>
);
}
const SomeForm = (props) => {
const [value, setValue] = useState(props.value);
const onFormChange = (event) => {
props.handleChangeForms(event.target.name, event.target.value, props.index);
setValue({ ...value, [event.target.name]: event.target.value });
};
const onClick = (event) => {
event.preventDefault();
props.handleDeleteForms(props.index);
};
return (
<form onChange={onFormChange}>
<input
type="text"
name="firstname"
placeholder="first name"
onChange={onFormChange}
value={value.firstname}
/>
<input
type="text"
name="lastname"
placeholder="last name"
value={value.lastname}
/>
<button onClick={onClick}>Delete form</button>
</form>
);
};
Sandbox
Because you're using index as key. The key will help react know which one should be updated. So you need to provide a unique value for each component.
FYI: https://reactjs.org/docs/lists-and-keys.html
The quick fix for your example:
let id = 0 // Introduce id variable
export default function App() {
Increase Id each time you add new item:
const addNewForm = () => {
id++;
setForms([
...forms,
{
id,
valuesOfForm: { firstname: "", lastname: "" }
}
]);
};
Change the key to id:
<SomeForm
value={form.valuesOfForm}
key={form.id}
index={index}
Everything will work after this

React hooks: Can't update state inside function component

I'm new to React and am trying to figure out how to make a phonebook. I've gotten quite far but I'm having an issue I can't solve. I'm using React Hooks.
Everything works fine, except for when I call the setNewNumber('') and setNewName('') functions at the end of addPerson(), just before the filteredEntries const.
I want to reset the input fields in the form to empty strings ('') once the other code inside addPerson() is done running, but it seems like the two functions are never called since the value for newName and newNumber don't change to '' (instead they keep the values the user added). However, my other useState functions (setPersons() and setFilteredPersons()) work inside addPerson()...
I've tried reading the documentation and asking around but haven't found a solution. I'd be very grateful for any clues/help!
import React, { useState } from 'react'
import Person from './components/Person'
const App = () => {
const [ persons, setPersons ] = useState([
{ name: 'Cat', number: '111' },
{ name: 'Dog', number: '222' },
{ name: 'Horse', number: '333' },
{ name: 'Owl', number: '444' }
])
const [filteredPersons, setFilteredPersons] = useState([...persons])
const [ newName, setNewName ] = useState('')
const [ newNumber, setNewNumber ] = useState('')
const addPerson = (event) => {
event.preventDefault()
const createPerson = () => {
const personObject = {
name: newName,
number: newNumber,
}
setPersons([...persons, personObject])
setFilteredPersons([...persons, personObject]) //varför går det inte att bara köra [...persons?]
}
const upperCaseNewName = newName.toUpperCase()
let doubleName
persons.map(person => {
const upperCasePerson = person.name.toUpperCase()
if(upperCaseNewName === upperCasePerson) {
doubleName = upperCasePerson
}
return doubleName
})
if (doubleName === undefined) {
createPerson()
} else if(doubleName === upperCaseNewName) {
alert(`${newName} is already in the phonebook`)
}
setNewName('')
setNewNumber('')
}
const filterEntries = event => {
let filtered = persons.filter(person => {
return person.name.toUpperCase().indexOf(event.target.value.toUpperCase()) !== -1
})
setFilteredPersons(filtered)
}
const renderPersons = () => filteredPersons.map(person =>
<Person key={person.name} name={person.name} number={person.number}/>
)
return (
<div>
<h2>Phonebook</h2>
<p>Filter entries:</p> <input onChange={(event) => filterEntries(event)}/>
<form>
<div>
name: <input onChange={(event) => setNewName(event.target.value)}/>
<br/>
phone: <input onChange={(event) => setNewNumber(event.target.value)}/>
</div>
<div>
<button type="submit" onClick={addPerson}>add</button>
</div>
</form>
<h2>Numbers</h2>
{renderPersons()}
</div>
)
}
export default App
The person component at the top just contains this code:
import React from 'react'
const Person = (props) => {
return(
<>
<p>{props.name} {props.number}</p>
</>
)
}
export default Person
Your components are not actually tied to your state values. You need them to be "controlled." Check out the docs for more examples :)
https://reactjs.org/docs/forms.html#controlled-components
<input value={newName} onChange={event => setNewName(event.target.value)} />
The reset is working correctly. What you forgot to do is add the value to each input. Without the value attribute the input is considered an uncontrolled component. By the sounds of it, you're looking to control the value via code.
Change
<div>
name: <input onChange={event => setNewName(event.target.value)} />
<br />
phone: <input onChange={event => setNewNumber(event.target.value)} />
</div>
to
<div>
name: <input value={newName} onChange={event => setNewName(event.target.value)} />
<br />
phone: <input value={newNumber} onChange={event => setNewNumber(event.target.value)} />
</div>
Codesandbox Demo
You have missed adding value attribute to input and thus your component is not the "Controlled" component.
You can read more here.
Changes needed
<input
value={newName}
onChange={event => setNewName(event.target.value)}
/>
<br />
phone:
<input
value={newNumber}
onChange={event => setNewNumber(event.target.value)}
/>
Codesandbox Link: https://codesandbox.io/s/crimson-frog-ecp98
Hope this Helps!

How to prevent a whole list from re-rendering when editing an element on the list?

I created this very simple app to hopefully explain this problem.
I tried using memoization and callback, but I believe it's re-rendering because the playerArr is always changing once I type into the text input.
my actual lists are only 15 elements in size, but the re-render is causing it to become REALLY SLOW when typing into the input.
Any suggestions? I have a deadline and i'm getting stressed out =( will going back to non-hooks help? or implementing redux? not sure the performance factor.
function App() {
const [player1, setPlayer1] = React.useState({
firstName: "First",
lastName: "Last ",
id: uuidv4()
});
const [player2, setPlayer2] = React.useState({
firstName: "First",
lastName: "Last",
id: uuidv4()
});
const [player3, setPlayer3] = React.useState({
firstName: "First",
lastName: "Last",
id: uuidv4()
});
return (
<div>
<State
player1={player1}
player2={player2}
player3={player3}
setPlayer1={setPlayer1}
setPlayer2={setPlayer2}
setPlayer3={setPlayer3}
/>
</div>
);
}
//----------------------------------------------------------
export const State = React.memo(({player1, player2, player3, setPlayer1, setPlayer2, setPlayer3}) => {
const playerArr = [player1, player2, player3];
const setPlayerArr = [setPlayer1, setPlayer2, setPlayer3];
return (
<div>
<Playlist
playerArr={playerArr}
setPlayerArr={setPlayerArr}
/>
</div>
);
});
//----------------------------------------------------------
export const Playlist = React.memo(({playerArr, setPlayerArr}) => {
return (
<div>
{
playerArr.map((player, index) => (
<Player
key={player.id}
player={player}
setPlayer={setPlayerArr[index]}
/>
))
}
</div>
);
});
//----------------------------------------------------------
export const Player = React.memo(({player, setPlayer}) => {
const handleOnChange = React.useCallback((event) => {
const playerCopy = {...player};
playerCopy[event.target.name] = event.target.value;
setPlayer(playerCopy);
}, [player, setPlayer]);
return (
<div>
<input type={"text"} name={"firstName"} value={player.firstName} onChange={handleOnChange}/>
<input type={"text"} name={"lastName"} value={player.lastName} onChange={handleOnChange}/>
</div>
);
});
EDIT: i edited app per discussion. same thing happening
No matter what you do, your <App> and <Playlist> components (even if they are memoized) will HAVE to re-render every time there is a user input because that is where you are storing your state, and is to be expected.
The best you can do is memoize each <Player> component so that when the list re-renders, every individual list item doesn't necessarily re-render itself. To do this you can pass an "areEqual" function as the second argument to React.memo. See the example in the React documentation: https://reactjs.org/docs/react-api.html#reactmemo
In your case, it would probably look something like this:
export const Player = React.memo(({player, setPlayer}) => {
const handleOnChange = React.useCallback((event) => {
const playerCopy = {...player};
playerCopy[event.target.name] = event.target.value;
setPlayer(playerCopy);
}, [player, setPlayer]);
return (
<div>
<input type={"text"} name={"firstName"} value={player.firstName} onChange={handleOnChange}/>
<input type={"text"} name={"lastName"} value={player.lastName} onChange={handleOnChange}/>
</div>
);
}, (prevProps, nextProps) => {
// Check to see if the data is the same
if (prevProps.firstName === nextProps.firstName
&& prevProps.lastName === nextProps.lastName
&& prevProps.id === nextProps.id) {
return true; // Return true if they ARE the same
} else {
return false; // Return false if they are NOT the same
}
});
Sometimes, if the data you are comparing is a simple collection of strings and/or numbers, you can use JSON.stringify as a shorthand way to convert it to a string and compare the strings:
export const Player = React.memo(({player, setPlayer}) => {
const handleOnChange = React.useCallback((event) => {
const playerCopy = {...player};
playerCopy[event.target.name] = event.target.value;
setPlayer(playerCopy);
}, [player, setPlayer]);
return (
<div>
<input type={"text"} name={"firstName"} value={player.firstName} onChange={handleOnChange}/>
<input type={"text"} name={"lastName"} value={player.lastName} onChange={handleOnChange}/>
</div>
);
}, (prevProps, nextProps) => {
// Check to see if the data is the same
if (JSON.stringify(prevProps) === JSON.stringify(nextProps)) {
return true; // Return true if they ARE the same
} else {
return false; // Return false if they are NOT the same
}
});

Categories

Resources