I have three components which App is the parent component(class-based) and two children component(functional-based) named User & Input. I've imported User to App and Input to User. I want to change the name by the value in Input(there is a button -> onClick).
Updated
The problem is the function in App.jsx(I'm pretty sure the way I try to do it is true).
// App.jsx
Imported User
state = {
users : [
{id: 1, fullName: 'Amirreza Amini'},
{id: 2, fullName: 'John Smith'},
...
]
};
handleChangeName = name => {
const editedUsers = [...this.state.users];
const users = editedUsers.map(user => user.fullName = name);
this.setState({ users });
};
render() {
const { users } = this.state;
const userComponent = users.map(user => (
<User
key={user.id}
fullName={user.fullName}
edit={() => this.handleChangeName(user.fullName)}
/>
));
.
// User.jsx
Imported Input
const User = ({ fullName }) => {
return (
<section>
<h1>{ fullName }</h1>
<Input fullName={fullName} />
</section>
);
};
export default User;
.
// Input.jsx
const Input = ({ fullName }) => {
return (
<section>
{/* The value of this input must be the fullName after I clicked the button */}
<input className="user-edit-input" placeholder={fullName} />
<button className="edit-button" onClick={edit(document.querySelector('.user-edit-input').value)}>Change</button>
</section>
);
};
export default Input;
Two big options as I see it:
Option one is changing App.jsx to functional component and send the function to change the fullname to User.jsx and the to Input.jsx as props letting the Input component change the value by itself
Option two that can leave your App.jsx component as is, keep it more secured and allow the child component to deal with only what it needs to actually know, is similarly sending a function from the parent App component so the Input component can trigger sending the old fullname and new fullname and the parent component function would handle the rest of the process.
example for parent function:
const updateFullname = (previousName, newName) => {
// Get previous object data and location in array
let originalUsersState = this.state.users;
let originalUser = originalUsersState.filter(
(user, index) => user.fullName === previousName
)
const originalUserIndex = originalUsersState.findIndex(
(user, index) => user.fullName === previousName
)
// Create new updated object and insert it to updated object array
let updatedUser = originalUser
updatedUser.fullName = newName;
let updatedUsersState = originalUsersState;
updatedUsersState[originalUserIndex] = updatedUser;
// Update the state with the new updated object array
this.setState(
{
users: updatedUsersState
}
);
}
Abbriviation for a solution for the question code with the new proposed function and process:
// App.jsx
Imported User
state = {
users : [
{id: 1, fullName: 'Amirreza Amini'},
{id: 2, fullName: 'John Smith'},
...
]
};
handleChangeName = name => {
const editedUsers = [...this.state.users];
const users = editedUsers.map(user => user.fullName = name);
this.setState({ users });
};
const updateFullname = (previousName, newName) => {
// Get previous object data and location in array
let originalUsersState = this.state.users;
let originalUser = originalUsersState.filter(
(user, index) => user.fullName === previousName
)
const originalUserIndex = originalUsersState.findIndex(
(user, index) => user.fullName === previousName
)
// Create new updated object and insert it to updated object array
let updatedUser = originalUser
updatedUser.fullName = newName;
let updatedUsersState = originalUsersState;
updatedUsersState[originalUserIndex] = updatedUser;
// Update the state with the new updated object array
this.setState(
{
users: updatedUsersState
}
);
}
render() {
const { users } = this.state;
const userComponent = users.map(user => (
<User
key={user.id}
fullName={user.fullName}
updateFullname={updateFullname}
edit={() => this.handleChangeName(user.fullName)}
/>
));
// User.jsx
Imported Input
const User = ({ fullName, updateFullname }) => {
return (
<section>
<h1>{ fullName }</h1>
<Input fullName={fullName} updateFullname={updateFullname} />
</section>
);
};
export default User;
// Input.jsx
const Input = ({ fullName }) => {
const [inputValue, setInputValue] = useState(undefined);
return (
<section>
<input className="user-edit-input" value={inputValue} onChange={event => setInputValue(event.target.value)} placeholder={fullName} />
<button className="edit-button" onClick={(fullname, inputValue) => updateFullname}>Change</button>
</section>
);
};
export default Input;
You can send input as props to callback from User component. From Users, with the callback function, call one more function to set state in User component
Related
so i have the problem where i have the form a phonebook and i add people in it but the task is whenever i try add the user that is already in there it must not let me do it and throw an error this person is already in a phonebook i dont want only solution i want explanation if anyone can explain it to me how to solve it and how it works code:
import React, { useState } from 'react'
import Person from './Person'
const App = () => {
const [persons, setPersons] = useState([
])
const [newName, setNewName] = useState('')
const handleNoteChange = (event) => {
setNewName(event.target.value)
}
const resetForm = () => {
setNewName('')
}
const handleSubmit = (e) => {
e.preventDefault()
const persona = {
name: newName,
id:Math.floor(Math.random() * 10000)
}
setPersons(persons.concat(persona))
setNewName('')
persons.map(perperson =>{
if(perperson.name === newName){
alert(`${newName} is already in the phonebook `)
}
})
console.log(persona);
}
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={handleSubmit}>
<div>
name: <input
value={newName}
onChange={handleNoteChange}
/>
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
<ul>
{persons.map(person => {
return(
<Person key={person.id} name={person.name} />
)
})}
</ul>
<p onClick={resetForm}>reset input</p>
<p>phonebook name is - {newName}</p>
</div>
)
}
export default App
Person component:
import React from 'react';
const Person = (props) => {
return(
<div>
<li key ={props.id}>{props.name}</li>
</div>
)
};
export default Person;
On submit function, you need to first check whether the value is already there or not. only after checking push it to the original array.
in your current method you are pushing the name before validation check.
check the comments in code for more explanation
import React, { useState } from "react";
import Person from "./Person";
const App = () => {
const [persons, setPersons] = useState([]);
const [newName, setNewName] = useState("");
const handleNoteChange = (event) => {
setNewName(event.target.value);
};
const resetForm = () => {
setNewName("");
};
const handleSubmit = (e) => {
e.preventDefault();
//checking is the value already there in persons, if index return -1
//it means value is not there on the array and we can push newName to
//array, if it return a value other than -1 it means the value already there in a index.
if (persons.findIndex((p) => p.name == newName) != -1) {
alert(`${newName} is already in the phonebook `);
return; //This will stop further execution if function so that it will not push data
}
const persona = {
name: newName,
id: Math.floor(Math.random() * 10000)
};
setPersons([...persons, persona]); //contacting array using spread opertaor
setNewName("");
};
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={handleSubmit}>
<div>
name: <input value={newName} onChange={handleNoteChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
<ul>
{persons.map((person) => {
return <Person key={person.id} name={person.name} />;
})}
</ul>
<p onClick={resetForm}>reset input</p>
<p>phonebook name is - {newName}</p>
</div>
);
};
export default App;
Here is working demo https://codesandbox.io/s/stoic-hofstadter-f402o?file=/src/App.js
You should implement your validation logic before inserting the person in the array, right now your code is performing the validation after you've inserted the person, hence it will never avoid a repeated entry.
const handleSubmit = (e) => {
e.preventDefault()
const persona = {
name: newName,
id:Math.floor(Math.random() * 10000)
}
//Here you are inserting the person into the persons array and setting the newName to '',
//that won't allow you to use newName later in order to perform any kind
//of validation. You should set newName to '' once you've already validated if the
//provided user is part of the array.
setPersons(persons.concat(persona))
setNewName('')
//Here you are validating if the person is already part of the array
//You should do this before the insertion process.
persons.map(perperson =>{
if(perperson.name === newName){
alert(`${newName} is already in the phonebook `)
}
})
Also, there's no need to use map in order to perform the validation process, since map iterates over the array and return the elements that meet the specified condition (that won't help you in this particular case). You should use the some method instead, and keep the same validation logic; return true if an element meets the expected condition and false otherwise, that will allow you to check if indeed, the element you're trying to insert is already part of the array. You'll end up with the following result:
const handleSubmit = (e) => {
e.preventDefault()
const persona = {
name: newName,
id:Math.floor(Math.random() * 10000)
}
let alredyInRegister = persons.some(perperson =>{
if(perperson.name === newName){
return true;
}
return false
})
if (alreadyInRegister) {
alert(`${newName} is already in the phonebook `)
} else {
setPersons(persons.concat(persona))
setNewName('')
}
}
I am trying to add, new data that user enters, to the array declared outside the component named DUMMY_MEALS and then render it as a list.
The problem here is the 'data' which is an object adds twice or more to the DUMMY_MEALS and renders twice in the page. Why is this happening?
The component with issue
"use strict";
import React from "react";
let DUMMY_MEALS = [
{id: "m1", name: "Sushi"},
];
const MealList = ({data}) => {
//const data = {id: "m5", name: "pushi"}
let mealslist = [];
DUMMY_MEALS = [data, ...DUMMY_MEALS];
mealslist = DUMMY_MEALS.map((meal) => <li>{meal.name}</li>);
return <ul>{mealslist}</ul>;
};
export default MealList;
Parent component
const Header = () => {
const [data, setData] = useState({});
const sendInputData = (inputData) => {
setData(inputData);
}
return (
<>
<MealsList data={data}/>
<MealForm getInputData={sendInputData}/>
</>
);
};
export default Header;
Sibling Component
const MealForm = (props) => {
const [name, setName] =useState("");
const formSubmitHandler = (e) => {
e.preventDefault();
let inputData = {
key : Math.random(),
name : name,
}
props.getInputData(inputData);
inputData = {};
}
return (
<form onSubmit={formSubmitHandler}>
<label htmlFor="name">name</label>
<input type="text" id="name" value={name} onChange={(e)=>setName(e.target.value)}></input>
<button>Submit</button>
</form>
);
};
export default MealForm;
You should use useState hook instead of let mealslist = []; Inside your MealList component.
And don't use DUMMY_MEALS as the component state. use useEffect hook to add the new meal to the state just once.
Check out this tested code CodeSandbox
MealList component changed as follow :
const MealList = ({ data }) => {
const [mealslist, setMealList] = useState([]);
useEffect(() => {
if (data)
setMealList([data, ...DUMMY_MEALS]);
}, []);
return <ul>{
mealslist.map((meal)=>{ <ListRender meal={meal} />})
}
</ul>;
};
And here is your App component:
const data = {
id: "k123",
name: "Falafel",
description: "An Iranian food.",
price: 16.5
};
export default function App() {
return (
<MealList data={data} />
);
}
Below, i am rendering <App/> component with children as <Input/> component array. I added few inputs using "add new" button. I am able to add input text components. But, when i am typing value in text, it is not displaying. i am not able to modify object in state array since index is showing as "-1" in setData function. Due to this, value is not showing when we type in text box. Please let me know why state is [] when i am accessing in setData function.
function Input(props)
{
return (
<div>
<label htmlFor='variable'>Name</label>
<input id='variable'
type='text'
value={props.value}
onChange={(e) => props.setData(props.id, e.target.value)} />
</div>
)
}
function App()
{
let [state, setState] = React.useState([])
let [inputs, setInputs] = React.useState([])
let setData = ((id, value) =>
{
console.log(state); // prints []
let index = state.findIndex(ele => ele.key === id);
console.log(index); // prints -1
if (!(index === -1))
{
setState(state =>
{
state[idx]["value"] = value;
})
}
})
let handleAdd = () =>
{
let idx = `${new Date().getTime()}`
let tempState = {
"key": idx,
"value": "",
}
setState(state => [...state, tempState])
let input = <Input key={tempState.key}
value={tempState.value}
id={tempState.key}
setData={setData} />
setInputs(inputs => [...inputs, input])
}
return (
<div>
<button onClick={handleAdd}>add new</button>
<div>
{inputs}
</div>
</div>
)
}
When you create an Input component inside handleAdd, it creates a closure and as a result setData gets the state that existed when the component was created, missing the newly added state.
In general, creating components and saving them to state is not a good approach. Instead it's better to only save the data onto state and render the components based on it.
Here's one way to do this, note how much simpler the component and its logic are.
function App() {
let [state, setState] = React.useState([]);
let setData = (id, value) => {
const newState = state.map((st) => {
if (st.key === id) {
st.value = value;
}
return st;
});
setState(newState);
};
const addInput = () => {
const idx = `${new Date().getTime()}`;
setState([...state, { key: idx, value: '' }]);
};
return (
<div>
<button onClick={addInput}>add new</button>
<div>
{state.map((st) => (
<Input value={st.value} key={st.key} setData={setData} id={st.key} />
))}
</div>
</div>
);
}
I'm still beginner with JavaScript and ReactJS.
I have a list that shows some numbers that the user typed. What I would like to know is how do I add the current value that exists in my array + the new value that the user typed.
For example, if there is a number 5 in my array, and the user types the number 3, I want to add these values and show them to the user. It is easier to explain through an image:
And here's my code.
Add New Value
import React from "react";
import { AppContext } from "../../providers";
const Add = () => {
const { dispatch } = React.useContext(AppContext);
const [transaction, setTransaction] = React.useState(0);
const onChangeValue = (e) => {
setTransaction(+e.target.value);
};
const onTransactions = () => {
if (transaction === 0) return;
dispatch({
type: "ADD_TRANSACTION",
payload: transaction
});
setTransaction(0);
};
return (
<div>
<h3>New transaction</h3>
<div>
<div>
<input type="number" value={transaction} onChange={onChangeValue} />
</div>
</div>
<div style={{ margin: "20px 0" }}>
<button type="button" onClick={onTransactions}>
Submit
</button>
</div>
</div>
);
};
export default Add;
List Results:
import React from "react";
import { AppContext } from "../../providers";
const List = () => {
const { state } = React.useContext(AppContext);
return (
<div>
{state.transactions.map((transaction) => (
<div>
<span>
-You add ${transaction}, your balance is: ${transaction}
</span>
</div>
))}
</div>
);
};
export default List;
Thank you very much for any help!!!
If you keep a total balance and a running balance:
const initialState = {
balance:0,
transactions: []
};
const reducer = (state, action) => {
switch (action.type) {
case "ADD_TRANSACTION":
return {
...state,
balance: state.balance + action.payload,
transactions: [{value:action.payload,balance:state.balance+action.payload}, ...state.transactions]
};
default:
return state;
}
};
You can just render these in your list view
const List = () => {
const { state } = React.useContext(AppContext);
return (
<div>
{state.transactions.map((transaction) => (
<div>
<span>
-You add ${transaction.value}, your balance is: ${transaction.balance}
</span>
</div>
))}
</div>
);
};
Just declear another variable for total Amount of all input Sum
const [transaction, setTransaction] = React.useState(0);
const [totalAmount, setTotalAmount] = React.useState(0);
const onChangeValue = (e) => {
setTransaction(e.target.value);
setTotalAmount(ParseInt(totalAmount) + ParseInt(e.target.value))
};
your render JXS will be
<span>
-You add ${transaction}, your balance is: ${totalAmount}
</span>
This is relatively simple in your local transaction state, as you can access the current value of transaction in your setTransaction call by passing it a function rather than a value.
const onChangeValue = (e) => {
setTransaction(state => state + e.target.value); // `state` here is the value before your update
};
Whatever you return from this callback will become the new value of transaction.
By the looks of it, the fact that you're storing these values in an array in context is irrelevant, unless of course you intend to use those existing values as your starting state for later transactions (i.e., multiple submit events without resetting to zero in between).
If that's the case, and if I'm understanding you correctly, then you'll want to map over each item in your context state and add the locally stored transaction to each of those values. Something like:
const onTransactions = () => {
if (transaction === 0) return;
dispatch({
type: "ADD_TRANSACTION",
payload: state.transactions.map(val => val + transaction)
});
setTransaction(0);
};
This'll add the new value to each of your values in your context (assuming your context has a handler that's doing what I think it's doing).
Just declear another variable for total Amount of all input Sum
const [transaction, setTransaction] = React.useState([]);
const [totalAmount, setTotalAmount] = React.useState(0);
const onChangeValue = (e) => {
let newtransaction = transaction
newtransaction.push({value: e.target.value, amount: totalAmount +
ParseInt(e.target.value)})
setTransaction(newtransaction);
setTotalAmount(totalAmount + ParseInt(e.target.value))
};
your render JXS will be
transaction.map(trans =>
<span>
-You add ${trans.value}, your balance is: ${trans.amount}
</span>
)
I have an input field which is written in a child component
and its inside return function
const EditSectionComponent = ({
editCaption,
editName,
}) => {
const {name,caption} = details;
return (
<input
type="text"
className="imageNameDetails"
value={name}
onChange={e => editName(e.target)}
/>
)
}
and in parent Component, it's like
const onEditClick = id => {
const selectedAsset = All.find(i => i.id === id);
setDetails(selectedAsset);
}
const [details, setDetails] = useState([]);
const editName = target => {
setDetails({ ...details, [name]: target.value })
};
Initial page load I can see both caption and name in the text field, but I am not able to change its value
It's not updating the UI. Is the right way to reflect the newly edited text in the input field
Make sure to destructure the details from props in EditSectionComponent.
In your parent, the initial state i.e. details is defined as an array. It need to be an object.
Also while doing setDetails, you need to specify the key. (not a dynamic name in your case)
Updated code is here:
const EditSectionComponent = ({
editCaption,
editName,
details,
}) => {
const {name,caption} = details;
return (
<input
type="text"
className="imageNameDetails"
value={name}
onChange={e => editName(e.target)}
/>
)
}
const [details, setDetails] = useState({name: '', caption: ''});
const editName = target => {
setDetails(prev => ({...prev, name: target.value}))
};