I need to dynamically add new input fields on button click as well as get the user input of those inputs in an array. This is what I have and Im not sure how to do the array. Also on the screen the components only update when I change the state. Not on button click.
This is what I have:
import React, { useState } from 'react'
const CreatePoll = () => {
const [formData, setFormData] = useState({
question: '',
options: ['hi', 'there']
});
const {
question,
options
} = formData;
const addOption = e => {
e.preventDefault();
options.push([''])
console.log(options.length);
}
const handleQuestionChange = (e) => setFormData({
...formData,
[e.target.name]: e.target.value
})
const handleOptionChange = e => setFormData({
...formData
// options: []
})
const handleSubmit = async e => {
e.preventDefault();
console.log(formData)
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="enter your question"
type="text"
onChange={handleQuestionChange}
name="question" />
{options.map(() => {
return (
<input
placeholder="option"
type="text"
onChange={handleOptionChange}
name="option" />
)
})}
<input type="button" value="Add new option" onClick={addOption} />
<input type="submit" value="Submit" />
</form>
)
}
export default CreatePoll
I tried when addOption button is clicked, I add to the options state an empty string. The length of the array updates but the components on the screen dont until I type in the input box and the state changes. Also I need to map the values of the input boxes to their respective place in the array. They should also be able to edit at any time. How is this done?
Several things are wrong here :
You don't set your state in addOption, don't modify direcly the state object, prefere to destructure array, modify it and set the state.
Your map function don't take any parameter, so it will be the same input names every time, use parameter and index to now which option to change in handleQuestionChange
Your addOption could be improved by using question property directly in you setFormData (it worked like you did it, but it seems to me more clean with this)
import React, { useState } from 'react';
const CreatePoll = () => {
const [formData, setFormData] = useState({
question: '',
options: ['hi', 'there'],
});
const {
question,
options,
} = formData;
const addOption = e => {
e.preventDefault();
const newOptions = [...options];
newOptions.push('');
setFormData({ ...formData, options: newOptions });
console.log(options.length);
};
const handleQuestionChange = e => {
setFormData({
...formData,
question: e.target.value,
});
};
const handleOptionChange = (e, index) => {
const newOptions = [...options];
newOptions[index] = e.target.value;
setFormData({
...formData,
options: newOptions,
});
};
const handleSubmit = async e => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder="enter your question"
type="text"
onChange={handleQuestionChange}
name="question"
/>
{options.map((opt, index) => (
<input
value={opt}
key={`option_${index}`}
placeholder="option"
type="text"
onChange={e => handleOptionChange(e, index)}
name={opt}
/>
))}
<input type="button" value="Add new option" onClick={addOption} />
<input type="submit" value="Submit" />
</form>
);
};
export default CreatePoll;
to add new options on button click you need to change this function:
const addOption = e => {
e.preventDefault();
options.push([''])
console.log(options.length);
}
to be
const addOption = e => {
e.preventDefault();
const newOptions = {...formData.options}
newOptions.concat([''])
setFormData({...formatData, options: newOptions)}
}
Related
I'm hoping I'm close to translating my old react hook forms to RHF7 but I'm stuck trying to share refs twice with 2 different inputs. Can someone help me finish this code...So I need a ref on both dobMonth and dobYear but I can't use the same ref twice so I need to create a second one but it won't let me.
const dobMonthInput = useRef(null)
const dobYearInput = useRef(null)
const {
register
} = useForm()
const { ref, ...rest } = register('dobMonth', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})
/*const { ref, ...rest } = register('dobYear', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})*/
....
<input
name="dobDay"
type="text"
{...register('dobDay', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})}
/>
<input
name="dobMonth"
type="text"
{...rest}
ref={(e) => {
ref(e)
dobMonthInput.current = e
}}
/>
<input
name="dobYear"
type="text"
{...rest}
ref={(e) => {
ref(e)
dobYearInput.current = e
}}
/>
I eventually need to use dobMonthInput.current.focus() and dobYearInput.current.focus()
I get the error Identifier 'ref' has already been declared for obvious reasons but I don't know how to get around it.
Thanks
You have an issue with ref declaration between react hook refs which its assigned to the same input, you need to create ref but without using already assigned ref to another input.
The trick here to resolve issue:
const dopMReg = register("dobMonth");
const dopYReg = register("dobYear");
for example:
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const dobMonthInput = useRef(null);
const dobYearInput = useRef(null);
const onSubmit = (data) => {
console.log(data);
dobMonthInput.current.focus();
}
const dopMReg = register("dobMonth");
const dopYReg = register("dobYear");
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
name="dobMonth"
type="text"
{...dopMReg}
ref={(e) => {
dopMReg.ref(e);
dobMonthInput.current = e;
}}
/>
<input
name="dobYear"
type="text"
{...dopYReg}
ref={(e) => {
dopYReg.ref(e);
dobYearInput.current = e;
}}
/>
<button>Submit</button>
</form>
);
}
and this the demo url
you can use like this too:
const dobYearInput = useRef(null)
const {
register
} = useForm()
const { ref:refDobMonth, ...restDobMonth } = register('dobMonth', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})
const { ref:refDobYear, ...restDobYear } = register('dobYear', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})
I try to show all data onChange inputs in Object to post it in API in React and I tried the following:
import React, { useState} from "react";
const Counter = () => {
const [form, setForm] = useState({});
const FormData = (event, {name, value}) => {
setForm({...form, [name]: value});
};
return (
<div>
<input value={form.username || ''} name="username" onChange={FormData} type="text"/>
<input value={form.email || ''} name="email" onChange={FormData} type="email"/>
<input value={form.password || ''} name="password" onChange={FormData} type="password"/>
</div>
);
}
export default Counter;
But it shows issue onChange : "Cannot destructure property 'name' of 'undefined' as it is undefined"
It can be done as follows:
const Counter = () => {
const [form, setForm] = useState({});
const FormData = (event) => {
const { target: { value, name } } = event;
setForm({...form, [name]: value});
};
return (
<div>
<input value={form.username || ''} name="username" onChange={FormData} type="text"/>
// same for other inputs
</div>
);
}
i am trying to send data to the DataBase with Axios and I normally do it with literal object. But i want to create an Object like this:
const Food = (foodName, proteins) => {
this.foodName = foodName;
this.proteins = proteins;
...
};
And then, set a new Object with the user values as input, something like this:
<input
id="food name"
name="foodName"
type="text"
value={inputValue.foodName}
onChange={(event) => handleChange(event)}
/>
I mean the user can add a food to his own list like:
when he click on the add button, it create a new Food object with all fields needed (proteins, bran and so...) : const pasta = Object.create(Food,name : {value: ...})
then all field are sends to the DataBase.
I know it's a little crazy and complicated. But I would like to know if it is possible to proceed like this and if it is a good practice. Or am I just bothering myself for nothing ^^
By advance, thank you!
The ReactJS code.
// import core
import React, { useState, useEffect, Fragment } from "react";
import Axios from "axios";
import Foods from "./Foods";
function FoodsAdd() {
const [food, setFood] = useState("");
const [inputValue, setInputValue] = useState("");
class Food {
constructor(foodName, proteins) {
this.foodName = foodName;
this.proteins = proteins;
}
}
// testing to set state with an Object.value
const addFood = () => {
const url = "";
Axios.post(url, food);
};
// get inputs values
const handleChange = (event) => {
event.preventDefault();
const { name, value } = event.target;
const newFood = { ...inputValue, [name]: value }; // standard way of proceeding
// what i want to do :
// const newFood = Object.create(Food, name: {value : { ...inputValue, [name]: value }}) // Or something like that
console.log(name, value);
setInputValue(newFood);
};
useEffect(() => {
setFood(inputValue);
console.log(food.foodName);
}, [inputValue]);
return (
<Fragment>
<form noValidate autoComplete="off">
<fieldset>
<legend>Nom de l aliment</legend>
<input
id="food name"
name="foodName"
type="text"
value={inputValue.test}
onChange={(event) => handleChange(event)}
/>
<label for="food name"></label>
</fieldset>
<fieldset>
<legend>Protéines</legend>
<input
id="proteins"
name="proteins"
type="number"
value={inputValue.proteins}
onChange={(event) => handleChange(event)}
/>
<label for="proteins"></label>
</fieldset>
</form>
<button>Ajouter</button>
</Fragment>
);
}
export default FoodsAdd;
I have a react component that looks like the one given below.
The form inputs are handled using the onInputChange function and form submit is handled by onFormSubmit
function RegisterForm() {
// formData stores all the register form inputs.
const [formData, setFormData] = useState(registerDefault);
const [errors, posting, postData] = useDataPoster();
function onInputChange(event: ChangeEvent<HTMLInputElement>) {
let update = { [event.target.name]: event.target.value };
setFormData(oldForm => Object.assign(oldForm, update));
}
function onFormSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const onSuccess: AxiosResponseHandler = response => {
setFormData(Object.assign(formData, response.data));
};
postData("/api/register", formData, onSuccess);
}
return (
<form onSubmit={onFormSubmit}>
<FormTextInput
name="full_name"
label="Name"
errors={errors.full_name}
onChange={onInputChange}
/>
<FormTextInput
name="email"
label="Email address"
type="email"
errors={errors.email}
onChange={onInputChange}
/>
<button type="submit" className="theme-btn submit" disabled={posting}>
{posting && <span className="fas fa-spin fa-circle-notch"></span>}
Create
</button>
</form>
);
}
My app has more than 50 similar forms and I wonder if I have to copy paste these two functions on all the other forms. onInputChange won't be changing a bit and the url is the only variable in onFormSubmit.
I am thinking of a class based approach with setFormData and postData as properties and the functions in question as class methods. But in that case, I have to bind the handlers with the class instance, so that handlers have a valid this instance.
Is there any other way to do this? How would you avoid the repetition of these two code blocks in all the form components?
Thanks
you could create a custom hook, something like this:
const [formState, setFormState] = useFormStateHandler({name: ''})
<input value={formState.name} onChange={event => setFormState(event, 'name')} />
where the definition looks like this:
export default function useFormStateHandler(initialState) {
const [state, setState] = useState(initialState)
const updater = (event, name) => {
setState({...state, [name]: event.target.value})
}
return [state, updater]
}
Create an HOC to inject input handlers to the form components with added params for url.
function RegisterForm(props) {
// specific function
const specific = () => {
const formData = props.formData; // use passed state values
// use form data
}
}
function withInputHandlers(Component, params) {
return function(props) {
// states
function onInputChange(...) {...}
function onFormSubmit(...) {
// use params.url when submitting
postData(params.url, formData, onSuccess);
}
// inject input handlers to component and state values
return (
<Component {...props} formData={formData} onChange={onInputChange} onSubmit={onFormSubmit} />
);
}
}
// Usage
const EnhancedRegisterForm = withInputHandlers(
RegisterForm,
{ url: 'register_url' } // params
);
const EnhancedSurveyForm = withInputHandlers(
Survey,
{ url: 'survey_url' } // params
)
This change may help you
function RegisterForm() {
// formData stores all the register form inputs.
const [formData, setFormData] = useState(registerDefault);
const [errors, posting, postData] = useDataPoster();
const onInputChange = name => event => {
let update = { [name]: event.target.value };
setFormData(oldForm => Object.assign(oldForm, update));
}
const onFormSubmit = url => event =>{
event.preventDefault();
const onSuccess: AxiosResponseHandler = response => {
setFormData(Object.assign(formData, response.data));
};
postData(url, formData, onSuccess);
}
return (
<form onSubmit={onFormSubmit("/api/register")}>
<FormTextInput
name="full_name"
label="Name"
errors={errors.full_name}
onChange={onInputChange("full_name")}
/>
<FormTextInput
name="email"
label="Email address"
type="email"
errors={errors.email}
onChange={onInputChange("email")}
/>
<button type="submit" className="theme-btn submit" disabled={posting}>
{posting && <span className="fas fa-spin fa-circle-notch"></span>}
Create
</button>
</form>
);
}
I must post {input} data to http://localhost:4000/prediction with Axios. But {input} turns undefined.
I am using const instead of class Main extends component. onChange, it sets form data.
const Main = ({ value, suggestions, auth: { user } }) => {
const [formData, setFormData] = useState("");
const [messages, setMessages] = useState([]);
const { input } = formData;
const onChange = e => setFormData(e.target.value);
const onSubmit = event => {
event.preventDefault();
setMessages(prevMsgs => [...prevMsgs, formData]);
console.log({ input });
Axios post.
axios
.post(
`http://localhost:4000/prediction`,
{ input },
{ crossdomain: true }
)
.then(res => {
console.log(res.data);
//setMessages(prevMsgs => [...prevMsgs, formData]);
})
.catch(error => {
console.log(error.message);
});
};
Return (form) with onSubmit, onChange.
return (
<div className="true">
<br />
<form noValidate onSubmit={e => onSubmit(e)}>
<div className="input-group mb-3">
<input
name="input"
type="text"
className="form-control"
placeholder="Type text"
onChange={e => onChange(e)}
/>
)}
<div className="input-group-append">
<button className="btn btn-outline-secondary">Send</button>
</div>
</div>
</form>
</div>
);
};
As I have mentioned in the comment section formData is a string as I see which does not have a property called input what you try to destructure and that's why it is undefined always.
If you really need that format for axios then you can try change the structure of formData with useState as the following first:
const [formData, setFormData] = useState({input: null});
Then maybe you can try updating as:
const onChange = e => setFormData({input: e.target.value});
I hope that helps!