Since I'm learning how to build React forms with hooks, I went through the 3 quicks posts that culminate with this one. Everything is going well until I get to the last step when you create your custom hook with:
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
return {
value,
onChange: handleChange
};
}
The Input is:
const Input = ({ type, name, onChange, value, ...rest }) => (
<input
name={name}
type={type}
value={value}
onChange={event => {
event.preventDefault();
onChange(name, event.target.value);
}}
{...rest}
/>
);
And the Form is:
const Form = () => {
const email = useFormInput("");
const password = useFormInput("");
return (
<form
onSubmit={e =>
e.preventDefault() || alert(email.value) || alert(password.value)
}
>
<Input
name="email"
placeholder="e-mail"
type="email"
{...email}
/>
<Input
name="password"
placeholder="password"
type="password"
{...password}
/>
<input type="submit" />
</form>
);
};
So in useFormInput() Chrome complains about
TypeError: Cannot read property ‘value’ of undefined at handleChange
which I'm pretty sure is pointing me to
function handleChange(e) {
setValue(e.target.value);
}
If I console.log(e) I get 'email', as expected (I think?), but if I try console.log(e.target) I get undefined. So obviously e.target.value doesn't exist. I can get it working by just using
setValue(document.getElementsByName(e)[0].value);
but I don't know what kind of issues this might have. Is this a good idea? Are there drawbacks to getting it to work this way?
Thanks
The issue comes from the onChange prop in the Input component
onChange={event => {
event.preventDefault();
onChange(name, event.target.value);
}}
you're calling onChange like this onChange(name, event.target.value); (two arguments, the first one is a string), while in your custom hook you define the callback like this
function handleChange(e) {
setValue(e.target.value);
}
it's expecting one argument, an event.
So either call onChange with one argument (the event) :
onChange={event => {
event.preventDefault();
onChange(event);
}}
or change the implementation of the callback.
Try this out:
const handleChange = e => {
const { inputValue } = e.target;
const newValue = +inputValue;
setValue(newLimit);
};
Had this issue with a calendar picker library react-date-picker using Register API. Looking at the documentation found out that there's another way of handling components that don't return the original event object on the onChange function using the Controller API.
More details on Controller API Docs
Example:
/*
* React Function Component Example
* This works with either useForm & useFormContext hooks.
*/
import { FC } from 'react'
import { Controller, useFormContext } from 'react-hook-form'
import DatePicker,{ DatePickerProps } from 'react-date-picker/dist/entry.nostyle'
const FormDateInput: FC<Omit<DatePickerProps, 'onChange'>> = ({
name,
...props
}) => {
const formMethods = useFormContext()
const { control } = formMethods ?? {}
return (
<Controller
render={({ field }) => <DatePicker {...props} {...field} />}
name={name ?? 'date'}
control={control}
/>
)
}
export default FormDateInput
Related
I have written a re-usable input component for url if a url dont start with http then it will be added http in the beginning.
Here you go for the componet
import React, {useContext, useCallback} from 'react';
const InputURL = ({ name, onChange, ...rest}) => {
const sanitizeURLonChange = React.useCallback((value, actionMeta) => {
if (value.target.value) {
if (!value.target.value.startsWith('http')) {
value.target.value = 'http://' + value.target.value
}
}
}, [onChange])
return (
<>
<input
name={name}
{...rest}
onChange={sanitizeURLonChange}
/>
</>
);
}
export default InputURL;
But when i try to use it in my some component, the onChange doesn't work
I try this way
<InputURL onChange={(e) => console.log(e.target.value)} />
unfortunately the inputURL onChange not working anymore, can you please help me in this case?
I want to achieve. if user input url without http, it will add http,
Like i input it stackoverflow.com/ and then it will return https://stackoverflow.com/ in Onchange
You are closing the bracket right after the event argument : {(e)}. Try like this:
<inputURL onChange={(e, val) => console.log(val)} />
also you have to use the onChange you're passing as props:
const sanitizeURLonChange = (e, actionMeta) => {
let newValue = e.target.value
if (newValue) {
if (!newValue.startsWith('http')) {
newValue = "http://" + newValue
}
}
setVal(newValue);
onChange(event, newValue)
}
but it seems anyway the onChange you are passing as a props to inputURL is not used anywhere so I am not sure what you want to achieve. Also you are calling the component inputURL instead of InputURL and first letter uppercase is very important in JSX.
I think your problem is here:
value.target.value = 'http://' + value.target.value
You are trying to update input value by not using an hook.
Try to rewrite your code in this way:
import React, { useState } from 'react';
const InputURL = ({ name, onChange, ...rest}) => {
const [val, setVal] = useState("");
const sanitizeURLonChange = (value, actionMeta) => {
if (value.target.value) {
if (!value.target.value.startsWith('http')) {
setVal('http://' + value.target.value);
}
else setVal(value.target.value);
}
}
return (
<>
<input
name={name}
{...rest}
value={val}
onChange={sanitizeURLonChange}
/>
</>
);
}
export default InputURL;
Here codesandbox working example.
I am kind of new to React and especially to the hooks and functional components,
I building a website, and like any other website, I need to avoid repeating myself, and abstract my code for reuse,
I created abstracted forms using classes and inherited the form and added on it like this:
class InputForm extends React.Component {
state: {}
validate = () => {
const { error } = Joi.validate(this.state.data, this.schema)
if (!error) {
return null
} else {
const errors = {}
error.details.map((oneError) => {
errors[oneError.path[0]] = oneError.message
return errors
})
return errors
}
}
validateProperty = ({ name, value }) => {
const obj = { [name]: value }
const schema = Joi.object({ [name]: this.schema[name] })
const errors = {}
const { error } = Joi.validate(obj, schema)
if (!error) {
return null
} else {
error.details.map((oneError) => {
errors[oneError.path[0]] = oneError.message
return errors
})
return errors ? errors : null
}
}
handleSubmit = (e) => {
console.log('calling handleSubmit line 1')
let { errors } = { ...this.state }
e.preventDefault()
if (!errors) {
console.log('no errors')
return
} else {
errors = this.validate() || {}
console.log('errors values are: ', errors)
if (Object.keys(errors).length === 0) {
console.log('calling do submit in handleSubmit')
this.doSubmit(e)
}
this.setState({ errors })
}
console.log('done submitting')
}
handleOnChange = ({ currentTarget: input }) => {
let { data, errors } = { ...this.state }
data[input.name] = input.value
errors = this.validateProperty(input) || {}
this.setState({ data, errors })
}
/// you can see I use this to renender any input
renderInputField = (
name,
label,
type,
message,
onChangeParams = this.handleOnChange,
...rest
) => {
const { data, errors } = { ...this.state }
return (
<InputField
{...rest}
name={name}
value={data[name]}
label={label}
onChange={(e) => {
this.handleOnChange(e)
onChangeParams(e)
}}
type={type}
message={message}
errors={errors[name]}
></InputField>
)
}
/// you can see I use this to renender any drop down input
renderInputFieldDD = (name, label, type, message, options, ...rest) => {
const { data, errors } = { ...this.state }
return (
<InputFieldDD
{...rest}
name={name}
value={data[name]}
label={label}
onChange={this.handleOnChange}
type={type}
message={message}
options={options}
errors={errors[name]}
></InputFieldDD>
)
}
renderButton = (label) => {
return (
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
>
{label}
</button>
)
}
}
export default InputForm
I wanted to do the same thing using functional components, I tried using HOC, but that is quite messed if I had to pass all the info I need in an input via props,
composing components using other ones with props is not as easy as inheritance using class-based components either!
I could achieve some composition and make code more reusable, but I don't know if there is anything specific that will make code more reusable with functional components!
So you want to create a form and extract whatever filled in it and store it inside an object using states.
You'll import useState() from react and create an empty object
import {useState} from 'react';
const FormComponent = () =>{
const [data,setData] = useState({name:"",age:""})
// this hook returns 2 values, the state and a function to update it that takes a single argument that is the updated state value
}
Create a form with onChange attribute in every input tag inside FormComponent defined earlier.
const FormComponent = () =>{
return (
<form>
<input type="text" value={data.name} placeholder="Name" onChange={(e)=>setData({...data,data.name:e.target.value})}/>
<input type="text" value={data.age} placeholder="Age" onChange={(e)=>setData({...data,data.age:e.target.value})}/>
</form>
)
}
In the above code the e.target.value extracts the value from the html tag that is the input tag here using the event object. The onChange attribute triggers every time you change something or type in the input tag.
You can also refer to this code here
I am using a in a react native functional component and onChange the state does not seem to be updating for "name".
I am console logging the new state using useEffect to check whether or not the state is updated and it is returning undefined as if the input text was never set as the state "name"
function AddTask () {
const [name, setName] = useState('');
useEffect(() => {
console.log(name),name;
})
const updateTaskInfo = event => setName(event.target.value);
return (
<TextInput
onChange={updateTaskInfo}
placeholder="Name"
value={name}
/>
);
}
export default AddTask;
use onChange like that event.nativeEvent.target.value
<TextInput onChange={ event => setName(event.nativeEvent.target.value) } />
or use onChangeText like that
<TextInput onChangeText ={ value => setName(value ) } />
The state is updated only on the next keystroke but with the previous state. Screen 1
When you click on updateForm (), it is also empty, only after the second click, the state is updated. Screen 2
I understand that this is due to asynchrony, but in this case I do not know how to use it.
Home.jsx
import React, { useState } from 'react';
import { Form } from '../components/Form/Form';
const Home = () => {
const [dateForm, setDataForm] = useState({});
const updateForm = eachEnry => {
setDataForm(eachEnry);
console.log(dateForm);
};
return (
<div>
<Form updateForm={updateForm} />
</div>
);
};
export default Home;
Form.jsx
import React, { useState } from 'react';
import './Form.scss';
export const Form = ({ updateForm }) => {
const initInputState = {
name: '',
password: ''
};
const [dataForm, setDataForm] = useState(initInputState);
const { name, password } = dataForm;
const onChange = e => {
setDataForm({
...dataForm,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
updateForm(dataForm);
};
return (
<div>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Name"
value={name}
onChange={onChange}
name="name"
/>
<input
placeholder="Password"
onChange={onChange}
value={password}
type="text"
name="password"
/>
<button type="submit">Login</button>
</form>
</div>
);
};
Your code is working fine. You just doing console.log before the state is updated. State updates happen not when you using an update state function. It's happening when all component action and nested components actions are done.
Check your code with console log on another place click to check
As you can see I placed a console log on every Home component rerender. You can check that all works fine.
P.S. I did some improvements to your code. Check if u like it. And add a comment to updateForm function. Check this one too, please.
You evidently are not setting your state properly, here
setDataForm({
...dataForm,
[e.target.name]: e.target.value
});
should be
setDataForm(c => ({
...c,
[e.target.name]: e.target.value
}));
I am trying to reset my input field after I submit the form using only React.js, using hooks from React itself.
Here is my code:
import React, { useState } from 'react';
const Searchbar = (props) => {
const { updateSelectedCity } = props;
const [newCity, setCity] = useState("");
const handleChange = (e) => {
setCity(e.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
updateSelectedCity(newCity);
console.log(newCity);
}
return (
<div>
<form onSubmit={handleSubmit}>
<input name="cityName" placeholder={placeholder} onChange={handleChange} />
<button type="submit">Check Weather</button>
</form>
</div>
)
};
export default Searchbar;
What do I need to add in order to accomplish that? Do I need to use useEffect? Or is there any way I can do it without using external libraries?
Thanks for the help!
Set the <input> to get its value from state:
<input name="cityName" placeholder={placeholder} onChange={handleChange} value={newCity} />
And just update state after handling the submit event:
const handleSubmit = (e) => {
e.preventDefault();
updateSelectedCity(newCity);
console.log(newCity);
setCity(""); // <--- here
}