I'm trying to edit an input value in a child component and send to the parent
:
https://codesandbox.io/s/sleepy-rain-skoss?file=/src/Editlabel.js:0-389
Parent:
import "./styles.css";
import EditLabel from "./Editlabel";
import { useEffect, useState } from "react";
export default function App() {
const [newName, setNewName] = useState();
useEffect(() => {
console.log("newName", newName);
}, [newName]);
return (
<div className="App">
<EditLabel
value={"hello"}
click={(changedName) => {
setNewName(changedName);
}}
/>
</div>
);
}
Child:
import React, { useState } from "react";
const EditLabel = ({ value, click }) => {
const [name, setName] = useState(value);
return (
<>
<input type={"text"} placeholder={name}></input>
<button
onClick={(e) => {
setName(e.target.value);
click(name);
}}
>
Edit
</button>
</>
);
};
export default EditLabel;
However, the console logs "hello" and then it just logs empty strings.
How can I make it work?
try this on your child's input box
<input type={"text"} placeholder={name} onChange={(e) => setName(e.target.value)}>
Change EditLabel to use a ref to capture the input value:
const EditLabel = ({ value, click }) => {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} type={"text"} placeholder={value}></input>
<button
onClick={() => {
click(inputRef.current.value);
}}
>
Edit
</button>
</>
);
};
Update App to use the values it gets via the click callback:
export default function App() {
const [newName, setNewName] = useState("hello");
useEffect(() => {
console.log("newName", newName);
}, [newName]);
return (
<div className="App">
<EditLabel
value={newName}
click={(changedName) => {
setNewName(changedName);
}}
/>
</div>
);
}
Related
I have a form in a page, when the user inputs the name of a new student and clicks submit, I want the content of that component (the form) to be completely replaced by the submitted name. How can I achieve this (Replace the form with the list onsubmit)?
I have read that I can use conditional rendering to toggle components, but it's not really clear to me how i can apply it here.
StudentListResult.Jsx
import React, { useState } from "react";
import StudentForm from "./StudentForm";
import StudentList from "./StudentList";
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
return (
<div>
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
/>
</div>
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
</div>
);
};
export default StudentListResult;
StudentListForm
import React from "react";
import { v4 as uuidv4 } from "uuid";
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
};
return (
<form onSubmit={addStudent}>
<div>
<input
value={newStudent}
type="text"
placeholder="Student Name"
onChange={(e) => setNewStudent(e.target.value)}
/>
</div>
<div>
<button>Submit</button>
</div>
</form>
);
};
export default StudentListForm;
StudentList.jsx
import React from "react";
const StudentList = ({ students = [], setStudentsList }) => {
return (
<div>
{students.map((student) => (
<ul key={student.id}>
<li>
<p>{student.name}</p>
</li>
</ul>
))}
</div>
);
};
export default StudentList;
So you want to show the form if not submitted and show the list if submitted? You can add a piece of state called submitted and do simple conditional rendering.
const StudentListResult = () => {
const [submitted, setSubmitted] = useState(false)
return (
{submitted ? <StudentList /> : <StudentListForm />}
);
};
And then in your addStudent function, set submitted.
const addStudent = (event) => {
// ...
setSubmitted(true)
}
If you want change form and list visibility state, you need pass custom function to form component:
StudentListResult.jsx:
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
const [getFormSubmitted, setFormSubmitted] = useState(false);
const setCompletedForm = () => {
setFormSubmitted(!getFormSubmitted);
};
return (
<div>
{getFormSubmitted ? (
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
) : (
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
onComplete={setCompletedForm}
/>
</div>
)}
</div>
);
};
Then call this function if form is submitted and all conditions is true
StudentListForm.tsx:
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
onComplete
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
onComplete();
};
import "./App.css";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addUser} from "./features/Users";
function App() {
const dispatch = useDispatch();
const userList = useSelector((state) => state.users.value);
const [name, setName] = useState("");
const [username, setUsername] = useState("");
return (
<div className="App">
{" "}
<div className="addUser">
<input
type="text"
placeholder="Name..."
onChange={(event) => {
setName(event.target.value);
}}
/>
<input
type="text"
placeholder="Username..."
onChange={(event) => {
setUsername(event.target.value);
}}
/>
<button
onClick={() => {
dispatch(
addUser({
id: userList[userList.length - 1].id + 1,
name,
username,
})
);
}}
>
{" "}
Add User
</button>
</div>
);}
I am new to react and redux. After clicking the "Add User" button, new User data from inputs in the code will be added to the backend list. I want the values in input sections to be cleared after clicking the "Add User" button, but I don't know how to do.
you need to clear your state after click on submit button. for ex: set function like =>
const clearData = {
setName("")
setUsername("")
}
and pass the func to your onClick event.
onClick={clearData}
The following code will work perfectly fine.
Just assign value={name} and value={username} to both input types respectively and when you click Add User just clear the data in both the states.
import "./App.css";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addUser} from "./features/Users";
function App() {
const dispatch = useDispatch();
const userList = useSelector((state) => state.users.value);
const [name, setName] = useState("");
const [username, setUsername] = useState("");
return (
<div className="App">
{" "}
<div className="addUser">
<input
type="text"
placeholder="Name..."
value={name}
onChange={(event) => {
setName(event.target.value);
}}
/>
<input
type="text"
placeholder="Username..."
value={username}
onChange={(event) => {
setUsername(event.target.value);
}}
/>
<button
onClick={() => {
setName("");
setUsername("");
dispatch(
addUser({
id: userList[userList.length - 1].id + 1,
name,
username,
})
);
}}
>
{" "}
Add User
</button>
</div>
);}
You can maintain a simple variable with list of form fields and can update the form state with the variable when you needed to clear form data. The below approach comes handy when you need to add additional fields as well.
import "./App.css";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addUser} from "./features/Users";
const formFields = { name: '', username: '' };
function App() {
const dispatch = useDispatch();
const userList = useSelector((state) => state.users.value);
const [params, setParams] = useState(formFields)
const handleChange = (e) => {
const { name, value } = e.target;
setParams({ ...params }, ...{[name]: value});
}
const clearForm = () => setParams(formFields);
return (
<div className="App">
<div className="addUser">
<input
type="text"
placeholder="Name..."
value={params.name}
onChange={(e) => handleChange(e)}
/>
<input
type="text"
placeholder="Username..."
value={params.username}
onChange={(e) => handleChange(e)}
/>
<button
onClick={() => {
dispatch(
addUser({
id: userList[userList.length - 1].id + 1,
...params
})
);
clearForm();
}}
>
{" "}
Add User
</button>
</div>
</div>
)
}
I want to add to Chip an startIcon={<Icon />}
when click on a Chip.
The state of the icon is managed by chipsState.
In this code,
the state of all chips would change.
How can I change only the chipsState of the element that is clicked?
In this code, the state of all chips will change.
How can I change only the chipsState of the element that is clicked?
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
const [chipsState, setChipsState] = useState(false);
const onChipClick = (element:any) => {
setChipsState(chipsState => !chipsState);
}
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<Chip onClick={() => onChipClick(element)} startIcon={chipsState && <Icon />}>{element.description}</Chip>
</div>
))}
</div>
);
}
export default Modal;
To handle local state (and better testing), you should create a new custom Chip component with dedicated chipState.
interface CustomChipProps {
description: string
}
const CustomChip = (props: CustomChipProps) => {
const [chipState, setChipState] = useState(false);
return <Chip onClick={() => setChipState(prev => !prev)} startIcon={chipState && <Icon />}>{props.description}</Chip>;
}
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<CustomChip description={element.description} />
</div>
))}
</div>
);
}
export default Modal;
You can achieve your desired output by changing chipState state from boolean to object.
So first let's change to object state instead of boolean
const [chipsState, setChipsState] = useState({});
Now we will change onChipClick function to change value of selected chip state
const onChipClick = (element:any) => {
setChipsState({...chipsState, chipsState[element]: !chipsState[element]});
}
And finally we will read correct value of each chipsState element.
<Chip onClick={() => onChipClick(element)} startIcon={chipsState[element] && <Icon />}>{element.description}</Chip>
You can try like the following
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
const ChipSet = ({ symtomsData }) => {
const data = symtomsData.map((symtom) => ({ ...symtom, isSelcted: false }));
const [chipSets, setChipSets] = useState(data);
const onSelectChipSet = useCallback(
(e, index) => {
const updatedChipSets = chipSets.map((chip, i) =>
i === index ? { ...chip, isSelcted: e.target.checked } : chip
);
setChipSets(updatedChipSets);
},
[chipSets]
);
console.log("chipSets", chipSets);
return (
<div>
<h1>Symtoms Data</h1>
{chipSets.map((x, i) => (
<div key={i}>
<label>
<input
onChange={(e) => onSelectChipSet(e, i)}
type="checkbox"
value={x.isSelcted}
/>
{x.description}
</label>
</div>
))}
</div>
);
};
class App extends React.Component {
render() {
const symtomsData = [
{
description: "mild"
},
{
description: "cold"
}
];
return (
<Grid>
<Row>
<ChipSet symtomsData={symtomsData} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
What i am trying to achieve is that i want to allow users to upload as many or limited files. To add he can always add the "Input file field".
Everything works correctly but when i try to delete it. It does not delete to "Input field" of SPECIDIC INDEX or Specific ID provided but it deletes the last field always.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [fields, setFields] = useState([{ files: null }]);
function handleChange(i, event) {
const values = [...fields];
values[i].files = event.target.[0];
console.log(values);
setFields(values);
}
function handleAdd() {
const values = [...fields];
values.push({ value: null });
setFields(values);
}
function handleRemove(i) {
const values = [...fields];
values.splice(i, 1);
setFields(values);
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button type="button" onClick={() => handleAdd()}>
+
</button>
{fields.map((field, idx) => {
return (
<div key={`${field}-${idx}`}>
<input
type="text"
defaultValue={field.files || ""}
onChange={(e) => handleChange(idx, e)}
/>
<button type="button" onClick={() => handleRemove(idx)}>
X
</button>
</div>
);
})}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The problem is the map KEY you should use a specific id instead of the index or the file object you can find below the example with the uuid package.
EXAMPLE :
import { useState } from "react";
import "./styles.css";
import { v4 as uuidv4 } from "uuid";
const App = () => {
const [fields, setFields] = useState([{ id: uuidv4(), files: "" }]);
function handleChange(i, event) {
setFields(
fields.map((input) =>
input.id === i ? { ...input, files: event.target.value } : input
)
);
}
function handleAdd() {
const values = [...fields];
values.push({ id: uuidv4(), files: "" });
setFields(values);
}
function handleRemove(idx) {
setFields(fields.filter((_, i) => i !== idx));
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button type="button" onClick={() => handleAdd()}>
+
</button>
{fields.map((field, idx) => {
return (
<div key={field.id}>
<input
type="text"
value={field.files}
onChange={(e) => handleChange(field.id, e)}
/>
<button type="button" onClick={() => handleRemove(idx)}>
X
</button>
</div>
);
})}
</div>
);
};
export default App;
I would like to update the parent state from child component, which renders each object of the array of objects. The main goal of the child component is to update the original value from the array of objects.
I've the following code
Parent:
import { useState } from 'react';
import ExpenseItem from './expenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} {...expense} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
child:
import { useState, useRef } from 'react';
function ExpenseItem({ description, date, credit, debit }) {
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Once way, is to pass the parent state property (expenses) and the function that updates it (setExpenses) to the child Component via the props:
Parent:
import React from 'react';
import ReactDOM from 'react-dom';
import { useState } from 'react';
import ExpenseItem from './ExpenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
Checking: { expenses[0].description } | { expenses[1].description }
<hr/>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
Child:
import React from 'react';
import { useState, useRef } from 'react';
function ExpenseItem( props ) {
let { description, date, credit, debit } = props.expenses[props.index];
const setExpenses = props.setExpenses;
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
const expenses = [ ...props.expenses ]; // Get a copy of the expenses array
// Replace the current expense item
expenses.splice( props.index, 1, {
description: value, date, credit, debit
});
// Update the parent state
setExpenses( expenses );
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Working demo
This can get really complicated as you move along, so the best option is to look for some sort of State Management solution, like using the Context API.
Also, take a look at this interesting post that talks about using the map index value as a key value: Index as a key is an anti-pattern