I make simple task for getting job. Working with react and redux. When i get value from input and send them to reducer they are lost in the way. Wait, not so easy. 1st item getting by reducer gets prop name, age, type, index and return new state. Nice. But other items lost prop name and age in the way. What? How did them it? Reducer return empty obj for render. Dont look on obj in dispatch i will rework it.
REDUCER
case 'EDIT_ITEM':
console.log(action.name, action.age, action.id);
return state.map((item, index) =>
action.id === index
? {
name: action.name,
age: action.age
}
: item
);
App.js
function EditUsers() {
const listItems = users.map(function (value, index) {
return (
<form>
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text">{value.name}, {value.age}</span>
</div>
<input type="text" placeholder="New name" id="newName" className="form-control"/>
<input type="text" placeholder="New age" id="newAge" className="form-control" aria-describedby="button-addon2"/>
<div className="input-group-append">
<button onClick={() => dispatch({
type: 'EDIT_ITEM',
id: index,
name: document.getElementById('newName').value,
age: document.getElementById("newAge").value
})}
className="btn btn-outline-primary"
type="button"
id="button-addon2">
Изменить
</button>
</div>
</div>
</form>
)
});
return (
<div>{listItems}</div>
)
}
You won't be able to access the input values from the button's onClick event, but if you decide to leave the inputs uncontrolled and move the logic to the associated form's onSubmit callback, then you can access the form's field values from the onSubmit event.
Define a submitHandler function to consume both the index and submit event, e:
const submitHandler = index => e => {
e.preventDefault(); // <-- prevent the default form action, important!
const { newName, newAge } = e.target; // <-- destructure the inputs from the event target
dispatch({
type: "EDIT_ITEM",
id: index,
name: newName.value, // <-- extract the input value
age: newAge.value // <-- extract the input value
});
};
Here the path to the input value is e.target.<fieldId>.value. Notice I've also defined submitHandler to curry the index, which allows for more optimal usage when mapping elements.
Next, attach the submitHandler callback to the onSubmit prop of the form.
const listItems = users.map(function(value, index) {
return (
<form key={index} onSubmit={submitHandler(index)}>
...
Here the curried function submitHandler(index) takes the index and encloses it in an instance of the callback, returning a function that takes the onSubmit event object, e => {....
Finally, update the button to have type="submit" and no onClick handler.
<button
className="btn btn-outline-primary"
type="submit"
id="button-addon2"
>
Изменить
</button>
Full code
const submitHandler = index => e => {
e.preventDefault();
const { newName, newAge } = e.target;
dispatch({
type: "EDIT_ITEM",
id: index,
name: newName.value,
age: newAge.value
});
};
function EditUsers() {
const listItems = users.map(function(value, index) {
return (
<form key={index} onSubmit={submitHandler(index)}>
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text">
{value.name}, {value.age}
</span>
</div>
<input
type="text"
placeholder="New name"
id="newName"
className="form-control"
/>
<input
type="text"
placeholder="New age"
id="newAge"
className="form-control"
aria-describedby="button-addon2"
/>
<div className="input-group-append">
<button
className="btn btn-outline-primary"
type="submit"
id="button-addon2"
>
Изменить
</button>
</div>
</div>
</form>
);
});
return <div>{listItems}</div>;
}
Related
I have a simple todo list that consists of multiple inputs.
I made the editing functionality and now everything works as it should, but only once. When I change the input data for the first time, it saves everything to an array with the correct data.
And when I want to do it a second time, then in order to save this data, three inputs must be changed.
I want that even when changing one input, the data is saved in an array (data that has not been changed is overwritten).
Stackblitz code
App.js
function App(props) {
const [tasks, setTasks] = useState(props.tasks);
function editTask(id, newName, newTranslate, newNote) {
const editedTaskList = tasks.map((task) => {
if (id === task.id) {
return { ...task, name: newName , translate: newTranslate , note: newNote };
}
return task;
});
setTasks(editedTaskList);
}
const taskList = tasks
.map((task) => (
<Todo
id={task.id}
name={task.name}
translate={task.translate}
note={task.note}
completed={task.completed}
key={task.id}
editTask={editTask}
tasks={tasks}
/>
));
return (
<div className="todoapp stack-large">
<ul
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
{taskList}
</ul>
</div>
);
}
export default App;
I did a check and added the save button onClick which outputs the data to the console. It gives the data correctly the first time, and if the same item in the todo is changed the second time, it gives an empty space instead of the data that has not been changed.
Todo.js
export default function Todo({name, translate, note, editTask, id, tasks}) {
const [isEditing, setEditing] = useState(false);
const [newName, setNewName] = useState(name);
const [newTranslate, setNewTranslate] = useState(translate);
const [newNote, setNewNote] = useState(note);
function handleChange(e) {
setNewName(e.target.value)
}
function handleChangeTranslate(e) {
setNewTranslate(e.target.value);
}
function handleChangeNote(e) {
setNewNote(e.target.value)
}
function handleSubmit(e) {
e.preventDefault();
if (!newName.trim()|| !newTranslate.trim() || !newNote.trim()) {
return;
}
editTask(id, newName,newTranslate,newNote);
setNewName("");
setNewTranslate("");
setNewNote("");
setEditing(false);
}
const editingTemplate = (
<form className="stack-small" onSubmit={handleSubmit}>
<div className="form-group">
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newName || name}
onChange={handleChange}
placeholder="write word"
/>
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newTranslate || translate}
onChange={handleChangeTranslate}
placeholder="write translate"
/>
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newNote || note}
onChange={handleChangeNote}
placeholder="write note"
/>
</div>
<div className="btn-group">
<button
type="button"
className="btn todo-cancel"
onClick={() => setEditing(false)}
>
Cancel
</button>
<button type="submit" className="btn btn__primary todo-edit" onClick={()=>console.log(newName, newTranslate, newNote)}>
Save
</button>
</div>
</form>
);
const viewTemplate = (
<div className="stack-small">
<div className="c-cb">
<label className="todo-label" htmlFor={id}>
{name}
</label>
<label className="todo-label" htmlFor={id}>
{translate}
</label>
<label className="todo-label" htmlFor={id}>
{note}
</label>
</div>
<div className="btn-group">
<button
type="button"
className="btn"
onClick={() => setEditing(true)}
>
Edit <span className="visually-hidden">{name}</span>
</button>
</div>
</div>
);
return <li className="todo">{isEditing ? editingTemplate : viewTemplate}</li>;
}
Since you want to keep those preview state which was not edit and still print out those state with the one you edit, you can just remove all the "reset state '' you put, since all your initial state from useState already had a value and is not an empty string "" like this
function handleSubmit(e) {
e.preventDefault();
if (!newName.trim()|| !newTranslate.trim() || !newNote.trim()) {
return;
}
editTask(id, newName,newTranslate,newNote);
setEditing(false);
}
New to react and currently working on a project with a backend.
Everything functions correctly apart from targeting the value of user selection.
basically whenever a user enters a number the setId is saved properly to the const with no problems while using the onChange method.
this method would render my page every change on text.
I am trying to save the Id only when the user clicks the button. however,
event.target.value does not work with onClick.
I tried using event.currentTarget.value and this does not seem to work.
Code:
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
Handle Submit:
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
is there a way of doing this with onclick? since I wouldn't like my component to render on every typo and only once a user has clicked the button.
Componenet:
interface GetOneCompanyProps {
company: CompanyModel;
}
interface RouteParam {
id: any;
}
interface CompanyById extends RouteComponentProps<RouteParam> {
}
function GetOneCompany(): JSX.Element {
const [id, setId] = useState('4');
const [company, setCompany] = useState<any>('');
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
async function send() {
try {
const response = await axios.get<CompanyModel>(globals.adminUrls.getOneCompany + id)
store.dispatch(oneCompanyAction(response.data));
console.log(response);
const company = response.data;
setCompany(company)
} catch (err) {
notify.error(err);
}
}
useEffect(() => {
send();
}, [id]);
return (
<div className="getOneCompany">
<h1>hi </h1>
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
<div className="top">
</div>
<br/>
Company: {id}
<br/>
Client Type: {company.clientType}
<br/>
Company Name: {company.name}
<br/>
Email Adress: {company.email}
<br/>
</div>
);
}
export default GetOneCompany;
Hope I am clear on this.
Thanks.
You can turn your input from being a controlled input to an uncontrolled input, and make use of the useRef hook. Basically, remove most of your attributes from the input element, and grab the current value of the input form on click of the button. From there, you can do whatever you want with the input value.
const inputRef = useRef()
...other code
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input type="number" ref={inputRef} />
<button value={id} type="button" onClick={() => console.log(inputRef.current.value)}>Search</button>
</form>
...other code
I'm afraid to say that here onChange is mandatory as we also are interested in the value which we set by setId. onClick can't be used as we can't set the value in the input.
Hope I'm clear.
Thankyou!
I've been trying to create a form that can add a list of objects with multiple attributes to a list. I managed to get this right with one string attribute. But, I cannot figure out how to add an entire object with property values passed from the form. I'm using functional components to do this........How can I create a form that adds new objects of items to a list? I'm fairly new to ReactJS, btw.
resume.jsx
function App() {
const [jobExperience, setjobExperience] = useState([{
jobCompanyName: '',
jobDateRangeStart: '',
jobDateRangeEnd: '',
jobDescription: '',
reference_up_count: 0,
reference_down_count: 0,
}]);
const refUpvoteCount = index => {
const newReferences = [...jobExperience];
newReferences[index].reference_upvote_count++;
setjobExperience(newReferences)
}
const refDownvoteCount = index => {
const newReferences = [...jobExperience];
newReferences[index].reference_downvote_count++;
setjobExperience(newReferences)
}
return(
<Container className="container-fluid g-0">
<Row>
<Col>
<div>
{jobExperience.map((jobExp, index) => (
<JobExperience key={index} jobExperience={jobExp} refUpvote={refUpvoteCount} refDownvote={refDownvoteCount}
))}
</div>
</Col>
<Col>
<div className="pl-5 pr-5 pb-2">
<form onSubmit={//Add To Array of item Objects}>
<div className="form-group">
<label>Company Name</label>
<input type="text" className="form-control" placeholder="Add Company Name" name="jobCompanyName" onChange={handleJobExperienceChange} />
</div>
<div className="form-row">
<div className="col">
<div className="form-group">
<label>Start Date</label>
<Datetime dateFormat="YYYY" timeFormat={false} onChange={(date) => setstartDate(date.year())} value={jobExperience.jobDateRangeStart} />
</div>
</div>
<div className="col">
<div className="form-group">
<label>End Date</label>
<Datetime dateFormat="YYYY" name="jobDateRangeEnd" timeFormat={false} onChange={(date) => setendDate(date.year())} value={jobExperience.jobDateRangeEnd} />
</div>
</div>
</div>
<div className="pt-1">
<div className="form-group">
<label>Job Role/Responsibilities</label>
<textarea style={{width: '100%'}} name="jobDescription" onChange={handleJobExperienceChange} />
<button type="submit" onClick={handleJobExperienceAdd} className="btn btn-success btn-sm btn-block">Add Job Experience</button>
</div>
</div>
</div>
</form>
</Col>
</Row>
</Container>
)
}
function JobExperience({jobExperience, index, refUpvote, refDownvote}) {
return (
<div>
<Card style={{width: '18rem'}} className="remove-border-radius">
<Card.Body>
<Card.Title><span><i className="fa fa-building"></i> {jobExperience.jobCompanyName}</span></Card.Title>
</Card.Body>
<Card.Text>
<i className="fa fa-calendar"></i> {jobExperience.jobDateRangeStart}-{jobExperience.jobDateRangeEnd}
</Card.Text>
<Card.Text>
<span><i className="fa fa-info-circle"></i> {jobExperience.jobDescription}</span>
</Card.Text>
<Button variant="primary" onClick={() => refUpvote(index)} className="remove-border-radius"><i className="fa fa-plus"></i> Reference {jobExperience.reference_upvote_count}</Button>
<Button variant="danger" onClick={() => refDownvote(index)} className="remove-border-radius"><i className="fa fa-minus-circle"></i> Reference {jobExperience.reference_downvote_count}</Button>
</Card>
</div>
)
}
Change the way you set your state from this:
const refUpvoteCount = (index) => {
const newReferences = [...jobExperience];
newReferences[index].reference_upvote_count++;
setjobExperience(newReferences);
};
const refDownvoteCount = (index) => {
const newReferences = [...jobExperience];
newReferences[index].reference_downvote_count++;
setjobExperience(newReferences);
};
To this:
const refUpvoteCount = (index) => {
setjobExperience((previousState) => {
const newReferences = [...previousState];
newReferences[index].reference_upvote_count++;
return newReferences;
});
}
const refDownvoteCount = (index) => {
setjobExperience((previousState) => {
const newReferences = [...previousState];
newReferences[index].reference_downvote_count++;
return newReferences;
});
}
You may also take note the difference to understand this other way of setting-up state that needs to have the the value of the previous state
Do it like this.
const myFunction = () => {
setState((previousState)=> newState)
}
If you need to get the reference of the previous state pass a callback function on setState and that call back function can take 1 parameter which that represent the previous state. And on the callback function you can do some operations if you need to. The return value of callback function will be the new state
And not like this
const myFunction = () => {
const newState = state
setState(newState)
}
This last code sample reference the previous state the wrong way and will not work
const [form, setForm] = useState({}); // form is the previous jobExperience object
const onChange = (event) => {
const { name, value } = event.target;
let savedValue = value;
/*
condition your changes below, you can also extract
the content of the condition to separate functions
*/
if (name === 'jobDateRangeStart') {
savedValue = []; // whatever you need to do with the value
}
if (name === 'jobDateRangeEnd') {
savedValue = []; // whatever you need to do with the value
}
if (name === 'jobDateRangeEnd') {
savedValue = []; // whatever you need to do with the value
}
setForm({ ...form, [name]: savedValue });
};
return (
<div className="pl-5 pr-5 pb-2">
<div className="form-group">
<label>Company Name</label>
<input
className="form-control"
name="jobCompanyName"
onChange={handleChange}
placeholder="Add Company Name"
type="text"
value={form.jobCompanyName || ''}
/>
</div>
<div className="form-row">
<div className="col">
<div className="form-group">
<label>Start Date</label>
<Datetime
dateFormat="YYYY"
onChange={handleChange}
timeFormat={false}
value={form.jobDateRangeStart || ''}
/>
</div>
</div>
<div className="col">
<div className="form-group">
<label>End Date</label>
<Datetime
dateFormat="YYYY"
name="jobDateRangeEnd"
onChange={handleChange}
timeFormat={false}
value={form.jobDateRangeEnd || ''}
/>
</div>
</div>
</div>
<div className="pt-1">
<div className="form-group">
<label>Job Role/Responsibilities</label>
<textarea
name="jobDescription"
onChange={handleChange}
value={form.jobDescription || ''}
style={{width: '100%'}}
/>
<button
className="btn btn-success btn-sm btn-block"
onClick={handleChange}
type="submit"
>
Add Job Experience
</button>
</div>
</div>
</div>
);
As far as i understood you are trying to add an object into an array with multiple fields . and the value of object will come from the values of your form . Here's how can you do it.
# Step 1 :
first create a state that will hold the array of objects .
const [arrayOfObjects , setArrayOfObjects ] = useState([]) ; // empty array initially
# Step 2 :
grab the value from your form's submit function and create the object
onSubmitHandler = () => {
const newObject = {
property1 : "some value " // this values will come from your form
property2 : "some value " // depending on your implementation you may have to maintain separate state for each property
}
const newState = [ ...arrayOfObjects , newObject ];
setArrayOfObjects(newState);
}
After I submit my form, which contains data fields and a file field, only the data fields are cleared, but the uploaded file field is kept. See image: Here
OnChange Function
onChange = (e) => {
if(e.target.name === 'audio') {
this.setState({
[e.target.name]: e.target.files[0], loaded: 0,
}, () => console.log(this.state.audio))
} else {
this.setState({
[e.target.name]: e.target.value
}, () => console.log(this.state))
}
}
Submit Function
onSubmit = e => {
e.preventDefault();
let { title, content, audio} = this.state;
//const story = { title, content, audio};
let formDataStory = new FormData();
formDataStory.append('audio', audio);
formDataStory.append('title', title);
formDataStory.append('content', content);
this.props.addStory(formDataStory);
this.setState({
title: "",
content:"",
audio: ""
});
};
Form
render() {
const {title, content, audio} = this.state;
return (
<div className="card card-body mt-4 mb-4">
<h2>Add Story</h2>
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label>Title</label>
<input
className="form-control"
type="text"
name="title"
onChange={this.onChange}
value={title}
/>
</div>
<div className="form-group">
<label>Content</label>
<input
className="form-control"
type="text"
name="content"
onChange={this.onChange}
value={content}
/>
</div>
<div className="form-group">
<label>Audio</label>
<input
className="form-control"
type="file"
name="audio"
onChange={this.onChange}
//value={audio}
/>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
);
}
}
How can I reset the file upload field together with the other data fields after submitting the form?
Many thanks!
Since the file input is always uncontrolled you'll need to use a dom ref and manually clear the value.
Here's an example functional component that does this:
function ExampleFileInput() {
const ref = React.useRef();
function handleClick() {
ref.current.value = ""
}
return (
<div className="App">
<input type="file" ref={ref}/><br />
<button type="button" onClick={handleClick}>Clear file</button>
</div>
);
}
To use in a class component, see the docs. You can read about more ways to clear the file input in this question.
Docs
Create ref to file input this.inputRef = React.createRef();
add ref to input <input type="file" ref={this.inputRef} />
Inside submit function this.inputRef.current.value = '';
If you use Formik you can do this:
I had the same issue and I managed it creating a ref in the parent component (the one that use Formik) and passing it to the Field using innerRef prop.
Then on the onReset I used the ref to clear the real input value.
const UserForm = props => {
const logoRef = useRef();
const handleReset = (values, formikBag) => {
logoRef.current.value = null; //THIS RESETS THE FILE FIELD
}
const handleSubmit = (values, formikBag) => {
//...
axios({method, url, data})
.then(response => {
formikBag.resetForm();
})
}
//...
return (
<Formik initialValues={initialValues} onSubmit={handleSubmit} onReset={handleReset}>
...
<Field
type="file"
name="logo"
onChange={({target}) => setFieldValue(props.name, target.files[0])}
innerRef={logoRef}
/>
</Formik>
)
}
I do not quite understand yet how react is working.
As far as I noticed I am supposed to handle detection of a change of form fields (for example angular and knockout detect fields changes with observables).
I have the following snippet in react:
constructor(props) {
super(props);
this.state = {
products: [],
branchId: document.getElementById('branchId').value
};
this.updateproduct = this.updateproduct.bind(this);
this.deleteproduct = this.deleteproduct.bind(this);
}
render() {
return (
<Container>
{
this.state.products.map(product => (
<div key={product.id}>
<div className="col-xs-12 col-sm-3">
<div className="user-label">Title</div>
<input type="text" name="title" className="form-control" defaultValue={product.title} onChange={this.handleChange} />
</div>
<div className="col-xs-12 col-sm-3">
<div className="user-label">Upload image</div>
<input type="text" name="image" className="form-control" defaultValue={product.image} onChange={this.handleChange} />
<img src={product.image} height="100" width="auto" style={{ marginTop: '5px' }} />
</div>
<div className="col-xs-12 col-sm-5">
<div className="user-label">Description</div>
<textarea name="description" className="form-control" rows="7" defaultValue={product.description} onChange={this.handleChange}>
</textarea>
</div>
<div className="col-xs-12 col-sm-1">
<div className="user-label"> </div>
<span className="btn btn-danger pull-right" data-btntype="delete" onClick={() => this.deleteproduct(product.id)}>
×
</span>
<span className="btn btn-success pull-right" data-btntype="update" onClick={() => this.updateproduct(this)} style={{ marginTop: '2px' }}>
✓
</span>
</div>
<div className="clearfix"></div>
</div>
))
}
</Container>
)
}
and don't quite know how to write a function that detect change of every field?
Also how can I handle the form update?
The above snippet is part of a bigger form (that's why form tag is not there) and I need to handle only the product object update above (not the entire form).
UPDATE
I applied the following solution based on #Kejt answer - with just a typo/ two corrections:
In the input:
onChange={e => this.handleChange(product.id, 'name', e.target.value) // Note 'this' must be added to the function whenever it's called
And then the handleChange method:
handleChange = (id, propertyName, value) => {
this.setState(
state => ({ // mapped 'products' need to be 'this.state.products'
products: this.state.products.map(product => product.id === id ? {...product, [propertyName]: value} : product)
})
)
}
Plus I had to add #babel/plugin-proposal-class-properties plugin to make the syntax working.
And in the class contructor added the usual declaration:
this.handleChange = this.handleChange.bind(this);
you already have onChange listener assigned to input onChange={this.handleChange}
you need to add handleChange method to your component
handleChange = event => {
console.log(event.target.value)
}
you can rebuild this method with adding product id and input name
onChange={e => handleChange(product.id, 'name', e.target.value)
and then in handleChange method
handleChange = (id, propertyName, value) => {
this.setState(
state => ({
products: products.map(product => product.id === id ? {...product, [propertyName]: value} : product)
})
}