working on an issue with the react-select AsyncSelect component that loads options from an API.But I can't pass the information to React-Hook Form through the controller.AsyncSelect works perfectly. The data goes back well in my "SelelectedValue" state. Can anyone help me ?
const [inputValue, setValue] = useState('');
const [selectedValue, setSelectedValue] = useState(null);
// handle input change event
const handleInputChange = value => {
setValue(value);
};
// handle selection
const handleChange = value => {
setSelectedValue(value);
}
const loadOptions = async (inputValue, callback) => {
const response = await fetch(`APIurl`);
const json = await response.json();
const object = json.records;
callback(object.map(i => ({ label: `${i.fields.firstName} - ${i.fields.lasName} , value: i.fields.firstName })))
}
<Controller
name="company"
control={control}
rules={{ required: true }}
render={({ field: { onChange, value } }) => (
<AsyncSelect
isClearable
value={selectedValue}
placeholder={'Your information'}
loadOptions={loadOptions}
onInputChange={handleInputChange}
onChange={handleChange}
styles={customStyles}
/>)}
/>
react-hook-form manages some common event and state (like value, onChange, onBlur etc.) for you so there is no need to define your own state in most case except onInputChange in AsyncSelect.
You can try to select the option and submit the form.
<Controller
name="company"
control={control}
rules={{ required: true }}
render={({ field }) => (
<AsyncSelect
{...field}
isClearable
defaultOptions
placeholder={"Your information"}
loadOptions={loadOptions}
onInputChange={handleInputChange}
// styles={customStyles}
/>
)}
/>
Here is the codesandbox
Related
Re-render problem
I have two components:
MainForm
CFNumericInput
The values coming from CFNumericInput are correct, but setValue won't render the old one.
Do I have to use an useEffect?
MainForm
const { control, watch, setValue, register } = useFormContext();
return (
<CFNumericInput
name="number1"
control={control}
setValueOnChange={(nextValue: string, oldValue: string) => {
let nextValidValue = checkNumericType(nextValue, "f6.2");
if (nextValidValue !== "") setValue("number1", nextValidValue);
else if (oldValue) setValue("number1", oldValue);
}}
/>;
)
CFNumericInput
export const CFNumericInput: React.FC<any> = ({
name,
control,
setValueOnChange,
}) => {
return control ? (
<Controller
name={name}
control={control}
render={({ field }) => {
return (
<NumericInput
{...field} // onChange, onBlur, value, name, ref
title={title}
onChange={(e) => {
field.onChange(e);
setValueOnChange && setValueOnChange(e, field.value);
}}
/>
);
}}
/>
) : (
<></>
);
};
Working but heavy solution
This solution it's working, but it's really heavy.
const [number1] = watch(["number1"]);
const [old, setOld] = useState("");
useEffect(() => {
let nextValidValue = checkNumericType(number1, "f6.2");
if (nextValidValue !== "") {
setValue("number1", nextValidValue);
setOld(nextValidValue);
} else if (old) setValue("number1", old);
}, [number1]);
Isn't possible that you call checkNumericType on onSubmit?
Also you can try on looking to use Yup (take a look at: https://react-hook-form.com/advanced-usage/#CustomHookwithResolver)
let me know what have you tried
I'm trying to customize the onChange in formik input to convert the value that is string to number, however, the behavior is not changed, the console.log is also not shown on screen. I believe it is not overwriting Formik's default behavior. What am I doing wrong?
Control Input
<App.FormField name={'monthly_salary'}>
{({field, form}) => (
<C.InputGroup>
<C.InputLeftAddon bg={'primary.100'}>
{'R$'}
</C.InputLeftAddon>
<Custom.Input
variant={'secondary'}
placeholder={t('form.placeholder_value_zero')}
mask={'currency'}
handleChange={(e) => {
console.log(parseValue(e.currentTarget.value))
form.setFieldValue(
field.name,
parseValue(e.currentTarget.value)
)
}}
{...field}
/>
</C.InputGroup>
)}
</App.FormField>
My Custom Component Input
export const Input = ({mask, handleChange, ...props}: InputProps) => {
const handleInput = useCallback(
(e: React.FormEvent<HTMLInputElement>) => {
if (mask === 'currency') {
currency(e)
}
},
[mask]
)
return (
<C.Input
inputMode={'numeric'}
onInput={handleInput}
onChange={handleChange}
{...props}
/>
)
}
I cannot reproduce your code to test it, but I suggest you try this
onChange={(e) => {
handleChange(e);
console.log(parseInt(e.currentTarget.value))
form.setFieldValue(
field.name,
parseInt(e.currentTarget.value)
)
}}
instead of
handleChange={(e) => {
console.log(parseValue(e.currentTarget.value))
form.setFieldValue(
field.name,
parseValue(e.currentTarget.value)
)
}}
You can try something like that:
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
let newEvent = event;
newEvent.target.value = "asd";
return field.onChange(newEvent);
};
could you please tell me how to set value in dropdown in react js ?
I am getting dropdown data after few seconds 3000 and then I need to set value on dropdown
const App = ({ children }) => {
const val = "ax";
const [state, setState] = useState([]);
setTimeout(() => {
setState(countryOptions);
}, 2000);
return (
<Container style={{ margin: 20 }}>
<Example countryOptions={state} />
</Container>
);
};
https://codesandbox.io/s/semantic-ui-example-utev4
expected output
Aland Islands should be selected.
{ key: "ax", value: "ax", text: "Aland Islands" },
as after three second I want to select this element
const val = "ax";
As stavros answer suggested; it may be better to keep state in App component and pass the setVal to the dropdown:
App:
const App = ({ children }) => {
const [state, setState] = useState([]);
//added val and setVal in App state
const [val,setVal]=useState('ax');
setTimeout(() => {
setState(countryOptions);
}, 2000);
return (
<Container style={{ margin: 20 }}>
//pass val and setVal so dropdown can set val on change
<Example countryOptions={state} val={val} onChange={setVal}/>
</Container>
);
};
Dropdown:
const DropdownExampleClearableMultiple = ({ countryOptions,val,onChange }) => (
<Dropdown
clearable
fluid
search
closeOnChange
selection
options={countryOptions}
//set value to passed in val
value={val}
//use setVal that was passed in as onChange
onChange={(_,i)=>onChange(i.value)}
placeholder="Select Country"
/>
);
You should update your question because only after visiting the codesandbox was I able to get enough info for an answer..
In your index.js you should update setState(countryOptions) to :
setState({countryOptions:countryOptions},()=>setState({val:"ax"})
Then line 39 to :
<Example countryOptions={state.countryOptions:countryOptions} val={state.val} />
Then in your example.js update const DropdownExampleClearableMultiple to:
const DropdownExampleClearableMultiple = ({ countryOptions, val }) => (
<Dropdown
clearable
fluid
search
closeOnChange
selection
options={countryOptions}
placeholder="Select Country"
value={val}
/>
);
Use the "value" prop.
// index.js
const App = ({ children }) => {
const val = "ax";
const [state, setState] = useState([]);
setTimeout(() => {
setState(countryOptions);
}, 2000);
return (
<Container style={{ margin: 20 }}>
<Example countryOptions={state} value={val} />
</Container>
);
};
// example.js
const DropdownExampleClearableMultiple = ({ countryOptions, value }) => (
<Dropdown
clearable
fluid
search
closeOnChange
selection
value={value}
options={countryOptions}
placeholder="Select Country"
/>
);
You can pass value to value property of Dropdown. Use state property for selected value.
Make sure that you change/update state on onchange event.
My Component has form input fields. These made use of a useState hook with their value and setValue for each input field. I want to optimize my component so the input fields made use of the same custom Hook which I called useFormInput
Inspired by Dan Abramov https://youtu.be/dpw9EHDh2bM see at 49:42
This works perfectly. However now I want to update the username after a new exercise is created. This is in the onSubmit method. But I'm not sure how to do this. Before I refactored I could use setUserName(), but now username is set by the generic custom hook function useFormInput
the username has an onChange method, so I thought I can maybe use this. However this uses the e.target.value because it is used for an input field.
Component:
I commented out the setUserName(''), here I want to update the username
const CreateExercise = () => {
const inputEl = useRef(null)
const username = useFormInput('')
const description = useFormInput('')
const duration = useFormInput(0)
const date = useFormInput(new Date())
const [users, setUsers] = useState([])
useEffect(() => {
axios
.get('http://localhost:5000/users/')
.then(res => {
if (res.data.length > 0) {
setUsers(res.data.map(user => user.username))
}
})
.catch(err => console.log(err))
}, [])
const onSubmit = e => {
e.preventDefault()
const exercise = {
username: username.value,
description: description.value,
duration: duration.value,
date: date.value
}
axios
.post('http://localhost:5000/exercises/add', exercise)
.then(res => console.log(res.data))
debugger
// setUsername('')
window.location = '/'
}
custom Hook useFormInput:
const useFormInput = initialValue => {
const [value, setValue] = useState(initialValue)
const handleChange = e => {
const newValue = e.target ? e.target.value : e
setValue(newValue)
}
return {
value,
onChange: handleChange
}
}
I expect the value in the state of username is updated to an empty string ' '
Complete code is on my repo on https://github.com/jeltehomminga/mern-tracker
Instead of trying to maintain more than 1 state, I'd recommend combining all state into one object. Then you can move everything into your custom hook. In addition, always make sure you handle and communicate any errors to the user.
Working example:
State as an object
hooks/useFormHandler (the API defined below is an object with functions to mimic API calls -- you'll replace this with real API calls. Also, if you wanted to make this hook reusable for other form components, then you'll need to remove the useEffect and handleSubmit functions from the custom hook and place them inside the specified functional component instead)
import { useCallback, useEffect, useState } from "react";
import API from "../../API";
// create a custom useFormHandler hook that returns initial values,
// a handleChange function to update the field values and a handleSubmit
// function to handle form submissions.
const useFormHandler = initialState => {
const [values, setValues] = useState(initialState);
// on initial load this will attempt to fetch users and set them to state
// otherwise, if it fails, it'll set an error to state.
useEffect(() => {
API.get("http://localhost:5000/users/")
.then(res => {
if (res.data.length > 0) {
setValues(prevState => ({
...prevState,
users: res.data.map(({ username }) => username)
}));
} else {
setValues(prevState => ({
...prevState,
error: "Unable to locate users."
}));
}
})
.catch(err =>
setValues(prevState => ({ ...prevState, error: err.toString() }))
);
}, []);
// the handleChange function will first deconstruct e.target.name and
// e.target.value, then in the setValues callback function, it'll
// spread out any previous state before updating the changed field via
// [name] (e.target.name) and updating it with "value" (e.target.value)
const handleChange = useCallback(
({ target: { name, value } }) =>
setValues(prevState => ({ ...prevState, error: "", [name]: value })),
[]
);
// the handleSubmit function will send a request to the API, if it
// succeeds, it'll print a message and reset the form values, otherwise,
// if it fails, it'll set an error to state.
const handleSubmit = useCallback(
e => {
e.preventDefault();
const exercise = {
username: values.username,
description: values.description,
duration: values.duration,
date: values.date
};
// if any fields are empty, display an error
const emptyFields = Object.keys(exercise).some(field => !values[field]);
if (emptyFields) {
setValues(prevState => ({
...prevState,
error: "Please fill out all fields!"
}));
return;
}
API.post("http://localhost:5000/exercises/add", exercise)
.then(res => {
alert(JSON.stringify(res.message, null, 4));
setValues(prevState => ({ ...prevState, ...initialState }));
})
.catch(err =>
setValues(prevState => ({ ...prevState, error: err.toString() }))
);
},
[initialState, setValues, values]
);
return {
handleChange,
handleSubmit,
values
};
};
export default useFormHandler;
components/CreateExerciseForm
import isEmpty from "lodash/isEmpty";
import React, { Fragment } from "react";
import { FaCalendarPlus } from "react-icons/fa";
import Spinner from "react-spinkit";
import Button from "../Button";
import Input from "../Input";
import Select from "../Select";
import useFormHandler from "../../hooks/useFormHandler";
const fields = [
{ type: "text", name: "description", placeholder: "Exercise Description" },
{ type: "number", name: "duration", placeholder: "Duration (in minutes)" },
{
type: "date",
name: "date",
placeholder: "Date"
}
];
// utilize the custom useFormHandler hook within a functional component and
// pass it an object with some initial state.
const CreateExerciseForm = () => {
const { values, handleChange, handleSubmit } = useFormHandler({
username: "",
description: "",
duration: "",
date: "",
error: ""
});
// the below will show a spinner if "values.users" hasn't been fulfilled yet
// else, it'll show the form fields. in addition, if there's ever a
// "values.error", it'll be displayed to the user.
return (
<form
style={{ width: 500, margin: "0 auto", textAlign: "center" }}
onSubmit={handleSubmit}
>
{isEmpty(values.users) ? (
<Spinner name="line-scale" />
) : (
<Fragment>
<Select
name="username"
placeholder="Select a user..."
handleChange={handleChange}
value={values.username}
selectOptions={values.users}
style={{ width: "100%" }}
/>
{fields.map(({ name, type, placeholder }) => (
<Input
key={name}
type={type}
name={name}
placeholder={placeholder}
onChange={handleChange}
value={values[name]}
/>
))}
<Button type="submit">
<FaCalendarPlus style={{ position: "relative", top: 2 }} />
Create Exercise
</Button>
</Fragment>
)}
{values.error && <p>{values.error}</p>}
</form>
);
};
export default CreateExerciseForm;
State as independent data types
Or, if you insist on using separated states, then create a resetValue function in the useFormInput hook:
const useFormInput = initialValue => {
// initialize state from "initialValue"
const [value, setValue] = useState(initialValue)
// handle changes to the "value" state via updating it
// with e.target.value
const handleChange = useCallback(({ target: { value } => {
setValue(value)
}, []);
// reset the value back to initialValue
const resetValue = useCallback(() => {
setValue(initialValue);
}, []);
return {
value,
handleChange,
resetValue
}
}
Then, destructure properties for the username (and other states, if needed):
const CreateExercise = () => {
// use ES6 destructure and aliasing to extract and rename the
// "value" (as username), "handleChange" function (as
// handleUsernameChange) and "resetValue" function (as resetUsername)
const {
value: username,
handleChange: handleUsernameChange,
resetValue: resetUsername
} = useFormInput('')
...other form state
...useEffect(() => {}, [])
const handleSubmit = useCallback(e => {
e.preventDefault();
const exercise = {
username: username,
description: description,
duration: duration,
date: date
};
axios
.post('http://localhost:5000/exercises/add', exercise)
.then(res => {
console.log(res.data)
// only reset the username if the exercise was successfully
// created
resetUsername();
})
.catch(err => console.log(err.toString());
}, [date, description, duration, resetUsername, username]);
return ( ...form )
}
I took a look and did a PR - Formik implementation w/validation.
Here is the PR - https://github.com/jeltehomminga/mern-tracker/pull/1
UI View
<>
<h3>Create New Exercise Log</h3>
<pre>{JSON.stringify({ formData }, null, 2)}</pre>
<ExerciseForm {...{ users }} onChange={data => setFormData(data)} />
</>
CreateExercise Form
import React from "react";
import * as Yup from "yup";
import { Formik, Form, Field } from "formik";
import DatePicker from "react-datepicker";
import cx from "classnames";
const requiredMessage = "Required";
const exerciseFormSchema = Yup.object().shape({
username: Yup.string().required(requiredMessage),
description: Yup.string()
.min(2, "Too Short!")
.required(requiredMessage),
duration: Yup.number()
.integer()
.min(1, "Min minutes!")
.max(60, "Max minutes!")
.required(requiredMessage),
date: Yup.string().required(requiredMessage)
});
const ExerciseForm = ({ users = [], onChange }) => {
return (
<Formik
initialValues={{
username: "",
description: "",
duration: "",
date: ""
}}
validationSchema={exerciseFormSchema}
onSubmit={values => onChange(values)}
>
{({
values,
touched,
errors,
handleChange,
handleBlur,
isSubmitting,
setFieldValue
}) => {
const getProps = name => ({
name,
value: values[name],
onChange: handleChange,
onBlur: handleBlur,
className: cx("form-control", {
"is-invalid": errors[name]
})
});
return isSubmitting ? (
// Replace this with whatever you want...
<p>Thanks for the Exercise!</p>
) : (
<Form>
<FormControl label="Username">
<>
<select {...getProps("username")}>
<>
<option value="default">Select user...</option>
{users.map(person => (
<option key={person} value={person.toLowerCase()}>
{person}
</option>
))}
</>
</select>
<FormErrorMessage {...{ errors }} name="username" />
</>
</FormControl>
<FormControl label="Description">
<>
<Field {...getProps("description")} />
<FormErrorMessage {...{ errors }} name="description" />
</>
</FormControl>
<FormControl label="Duration in minutes">
<>
<Field {...getProps("duration")} type="number" />
<FormErrorMessage {...{ errors }} name="duration" />
</>
</FormControl>
<FormControl label="Date">
<>
{/* Was present before refactor */}
<div>
<DatePicker
{...getProps("date")}
selected={values.date}
minDate={new Date()}
onChange={date => setFieldValue("date", date)}
/>
<FormErrorMessage {...{ errors }} name="date" />
</div>
</>
</FormControl>
<button type="submit" className="btn btn-primary">
Create Exercise log
</button>
</Form>
);
}}
</Formik>
);
};
export default ExerciseForm;
// Created to manage label and parent className
const FormControl = ({ label, children }) => (
<div className="form-group">
<label>{label}:</label>
{children}
</div>
);
const FormErrorMessage = ({ name, errors }) => {
const error = errors && errors[name];
return error ? (
<div
class="invalid-feedback"
// Add inline style override as error message cannot sit as sibling to datePicker (bootstrap css)
style={{ display: "block" }}
>
{error}
</div>
) : null;
};
I'm starting out with the formik library for react, and I can't figure out the usage of the props handleChange and handleBlur.
According to the docs, handleBlur can be set as a prop on a <Formik/>, and then has to be passed manually down to the <input/>.
I've tried that, with no success :
(I'm keeping the code about handleBlur for more clarity)
import React from "react";
import { Formik, Field, Form } from "formik";
import { indexBy, map, compose } from "ramda";
import { withReducer } from "recompose";
const MyInput = ({ field, form, handleBlur, ...rest }) =>
<div>
<input {...field} onBlur={handleBlur} {...rest} />
{form.errors[field.name] &&
form.touched[field.name] &&
<div>
{form.errors[field.name]}
</div>}
</div>;
const indexById = indexBy(o => o.id);
const mapToEmpty = map(() => "");
const EmailsForm = ({ fieldsList }) =>
<Formik
initialValues={compose(mapToEmpty, indexById)(fieldsList)}
validate={values => {
// console.log("validate", { values });
const errors = { values };
return errors;
}}
onSubmit={values => {
console.log("onSubmit", { values });
}}
handleBlur={e => console.log("bluuuuurr", { e })}
render={({ isSubmitting, handleBlur }) =>
<Form>
<Field
component={MyInput}
name="email"
type="email"
handleBlur={handleBlur}
/>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>}
/>;
What is wrong with this approach ?
How are handleBlur and handleChange actually supposed to be used ?
You'll need to remove the first handleBlur from Formik as blur event is only valid on the field level and do something like the following in your Field element:
<Field
component={MyInput}
name="email"
type="email"
onBlur={e => {
// call the built-in handleBur
handleBlur(e)
// and do something about e
let someValue = e.currentTarget.value
...
}}
/>
See https://github.com/jaredpalmer/formik/issues/157
According to the above mention question code, you just need to change only one thing i.e. onBlurCapture={handleBlur}
which works on html input element. onBlur, onFocusOut are not supported by the react. However, I got the same issue and finally resolved with component in react-formik with render props.
i faced the same problem using onChange method, which i think do not exist in formik props.
so i used the onSubmit method as it's available in the formik props which gives us the fields values and then passed that values to the concern function like so ...
<Formik
initialValues={initialValues}
validationSchema={signInSchema}
onSubmit={(values) => {
registerWithApp(values);
console.log(values);
}}
>
and there you can use, i simply updated the state and passed it to axios like so...
const [user, setUser] = useState({
name: "",
email: "",
password: ""
});
const registerWithApp = (data) => {
const { name, email, password } = data;
setUser({
name:name,
email:email,
password:password
})
if (name && email && password) {
axios.post("http://localhost:5000/signup", user)
.then(res => console.log(res.data))
}
else {
alert("invalid input")
};
}
and its working ...
I hope its helps you.