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;
};
Related
i want to improve a contact form for my next.js website using nodemailer. so far i have been able to get elements such as the name, email, subject, and message fields of my form to successfully submit and send via email.
in the same javascript file that loads the form components (using chakra-ui), this is what the code looks like:
import { ui components } from from '#chakra-ui/react'
import React, { useState } from "react";
import { sendContactForm } from '../lib/api'
const initValues = {name: "", email: "", subject: "", message: ""};
const Page = () => {
const [state, setState] = useState(initState);
const [touched, setTouched] = useState({});
const {values, isLoading, error} = state
const onBlur = ({target}) => setTouched((prev) => ({...prev,
[target.name]:true,
}))
const handleChange = ({target}) => setState((prev)=>({
...prev,
values: {
...prev.values,
[target.name]: target.value,
},
}));
const onSubmit = async () => {
values.pricing = checked
setState((prev) => ({
...prev,
isLoading:true,
}));
try{
await sendContactForm(values);
setTouched({}),
setState(initState);
toast({
title: "message sent!!",
});
} catch(error){
setState((prev) => ({
...prev,
isLoading:false,
error:error.message
}));
}
}
//i just show the name in this example, but its exactly the same for the other three elements
return (
<FormControl isRequired isInvalid={touched.name && !values.name}>
<FormLabel>Name</FormLabel>
<Input type="text" name="name" value = {values.name} onChange={handleChange}/>
</FormControl>
//the other three values...
<Button onClick={onSubmit} isLoading={isLoading} disabled={!values.name || !values.email || !values.subject || !values.message}>Submit</Button>
)
}
export default Page
}
i tried to implement a radio input, so that a user can choose between 4 limited pricing options. i have done research on the next ui and how it was implemented with nodemailer using node.js, however, there are no resources on loading this kind of elements with nodemailer and next.js.
i am unsure of how to implement the logic to add something like pricing: "" to the initValues if i had an element that looked like this:
<FormControl isRequired isInvalid={touched.pricing && !values.pricing}>
<FormLabel align="left" as='legend'>Pricing</FormLabel>
<RadioGroup onChange={handleSubmit} value ={values.pricing}>
<HStack spacing='24px'>
<Radio value="5$">5$</Radio>
<Radio value="13$">13$</Radio>
<Radio value="25$">25$</Radio>
<Radio value="Other">Other</Radio>
</HStack>
</RadioGroup>
</FormControl>
I have search component with validate js.
Problem: when my input in foucs first time, validate and request dont work, but when i lose focus my input, and click it again, and try again, search working without validation
interface IProps {
onSearchChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const Search: React.FC<IProps> = ({ onSearchChange }) => {
const inputRef = useRef<HTMLInputElement>(null);
const [inputIsTouched, setInputIsTouched] = useState(false);
const currentValue = inputRef.current?.value && inputRef.current.value;
const validateErrors = validate({ currentValue }, constraints);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (validateErrors?.currentValue) {
return;
}
currentValue && onSearchChange && onSearchChange(event);
setInputIsTouched(true);
};
const debouncedOnChange = debounce(handleChange, 1000);
return (
<div className={classes['Root']}>
<Input
type="text"
autoComplete="off"
placeholder="..."
onChange={debouncedOnChange}
ref={inputRef}
onBlur={() => setInputIsTouched(true)}
isError={inputIsTouched && !!validateErrors?.currentValue}
/>
<div className={classes['ErrorContainer']}>
{inputIsTouched && validateErrors?.currentValue && (
<Text color="error" size="s">
{validateErrors.currentValue}
</Text>
)}
</div>
</div>
);
};
That's expected because on first render, currentValue is undefined (as inputRef.current is null) and there's nothing calling handleChange to trigger the search.
You need to make sure the handleChange logic also runs on the initial render, so it should look something like this:
const Search: React.FC<IProps> = ({ onSearchChange }) => {
// Use a single object for all input state props:
const [{
isTouched,
validateErrors,
}, setInputState] = useState({
isTouched: false,
validateErrors: null,
});
const inputRef = useRef<HTMLInputElement>(null);
// Debounce only search callback:
const debouncedSearchChange = debounce(onSearchChange, 1000);
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
// Get the current value:
const currentValue = e.currentTarget.value;
// Validate it:
const validateErrors = validate({ currentValue }, constraints);
if (validateErrors?.currentValue) {
// And handle error:
setInputState(prevState => ({ ...prevState, validateErrors }));
return;
}
// Or success:
setInputState(prevState => ({ ...prevState, validateErrors: null }));
// And trigger the debounced search if needed:
if (currentValue && debouncedSearchChange ) debouncedSearchChange(event);
}, [constraints, debouncedSearchChange]);
// Trigger validation and search on first render:
useEffect(() => {
const inputElement = inputRef.current;
// TypeScript will complain about this line, so you might want to
// re-structure the logic above to accommodate this:
if (inputElement) handleChange({ currentTarget: inputElement });
}, []);
return (
<div className={classes['Root']}>
<Input
type="text"
autoComplete="off"
placeholder="..."
onChange={handleChange}
ref={inputRef}
onBlur={() => setInputState(prevState => ({ ...prevState, isTouched: true }))}
isError={inputIsTouched && !!validateErrors?.currentValue}
/>
<div className={classes['ErrorContainer']}>
{inputIsTouched && validateErrors?.currentValue && (
<Text color="error" size="s">
{validateErrors.currentValue}
</Text>
)}
</div>
</div>
);
};
I have two React components, namely, Form and SimpleCheckbox.
SimpleCheckbox uses some of the Material UI components but I believe they are irrelevant to my question.
In the Form, useEffect calls api.getCategoryNames() which resolves to an array of categories, e.g, ['Information', 'Investigation', 'Transaction', 'Pain'].
My goal is to access checkboxes' states(checked or not) in the parent component(Form). I have taken the approach suggested in this question.(See the verified answer)
Interestingly, when I log the checks it gives(after api call resolves):
{Pain: false}
What I expect is:
{
Information: false,
Investigation: false,
Transaction: false,
Pain: false,
}
Further More, checks state updates correctly when I click into checkboxes. For example, let's say I have checked Information and Investigation boxes, check becomes the following:
{
Pain: false,
Information: true,
Investigation: true,
}
Here is the components:
const Form = () => {
const [checks, setChecks] = useState({});
const [categories, setCategories] = useState([]);
const handleCheckChange = (isChecked, category) => {
setChecks({ ...checks, [category]: isChecked });
}
useEffect(() => {
api
.getCategoryNames()
.then((_categories) => {
setCategories(_categories);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
{categories.map(category => {
<SimpleCheckbox
label={category}
onCheck={handleCheckChange}
key={category}
id={category}
/>
}
)
}
const SimpleCheckbox = ({ onCheck, label, id }) => {
const [check, setCheck] = useState(false);
const handleChange = (event) => {
setCheck(event.target.checked);
};
useEffect(() => {
onCheck(check, id);
}, [check]);
return (
<FormControl>
<FormControlLabel
control={
<Checkbox checked={check} onChange={handleChange} color="primary" />
}
label={label}
/>
</FormControl>
);
}
What I was missing was using functional updates in setChecks. Hooks API Reference says that: If the new state is computed using the previous state, you can pass a function to setState.
So after changing:
const handleCheckChange = (isChecked, category) => {
setChecks({ ...checks, [category]: isChecked });
}
to
const handleCheckChange = (isChecked, category) => {
setChecks(prevChecks => { ...prevChecks, [category]: isChecked });
}
It has started to work as I expected.
It looks like you're controlling state twice, at the form level and at the checkbox component level.
I eliminated one of those states and change handlers. In addition, I set checks to have an initialState so that you don't get an uncontrolled to controlled input warning
import React, { useState, useEffect } from "react";
import { FormControl, FormControlLabel, Checkbox } from "#material-ui/core";
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Form />
</div>
);
}
const Form = () => {
const [checks, setChecks] = useState({
Information: false,
Investigation: false,
Transaction: false,
Pain: false
});
const [categories, setCategories] = useState([]);
console.log("checks", checks);
console.log("categories", categories);
const handleCheckChange = (isChecked, category) => {
setChecks({ ...checks, [category]: isChecked });
};
useEffect(() => {
// api
// .getCategoryNames()
// .then(_categories => {
// setCategories(_categories);
// })
// .catch(error => {
// console.log(error);
// });
setCategories(["Information", "Investigation", "Transaction", "Pain"]);
}, []);
return (
<>
{categories.map(category => (
<SimpleCheckbox
label={category}
onCheck={handleCheckChange}
key={category}
id={category}
check={checks[category]}
/>
))}
</>
);
};
const SimpleCheckbox = ({ onCheck, label, check }) => {
return (
<FormControl>
<FormControlLabel
control={
<Checkbox
checked={check}
onChange={() => onCheck(!check, label)}
color="primary"
/>
}
label={label}
/>
</FormControl>
);
};
If you expect checks to by dynamically served by an api you can write a fetchHandler that awaits the results of the api and updates both slices of state
const fetchChecks = async () => {
let categoriesFromAPI = ["Information", "Investigation", "Transaction", "Pain"] // api result needs await
setCategories(categoriesFromAPI);
let initialChecks = categoriesFromAPI.reduce((acc, cur) => {
acc[cur] = false
return acc
}, {})
setChecks(initialChecks)
}
useEffect(() => {
fetchChecks()
}, []);
I hardcoded the categoriesFromApi variable, make sure you add await in front of your api call statement.
let categoriesFromApi = await axios.get(url)
Lastly, set your initial slice of state to an empty object
const [checks, setChecks] = useState({});
Before asking this I did a Google search and I didn't find any good resource to manage a form only with Redux. All of the examples use redux-forms. It is only one form and I don't want to install that library to use only on one, minimal, small form.
I can't use local state because the user has the option to go to another screen and then be back on the screen which contains the form, so at some point the component could be unmounted and mounted again and I want it to keep its state.
This is the component I have so far and the way I've been working on it:
import { compose } from 'redux';
import { connect } from 'react-redux';
import React from 'react';
import FormField from '../FormField/FormField';
import {
startupThirdStepFormAction,
} from '../../pages/StartupApplication/actions/startupApplicationActions';
const StepThreeForm = ({
startupThirdStepForm,
startupThirdStepFormActionHandler,
}) => (
<>
<Container>
<Row>
<Col>
<Form>
<FormField
value={startupThirdStepForm.firstName}
label="First Name"
controlId="firstName"
onChange={e =>
startupThirdStepFormActionHandler({
firstName: e.target.value,
})
}
/>
<FormField
value={startupThirdStepForm.middleName}
label="Middle Name"
controlId="middleName"
onChange={e =>
startupThirdStepFormActionHandler({
middleName: e.target.value,
})
}
/>
</Form>
</Col>
</Row>
</Container>
</>
);
export default compose(
connect(
store => ({
startupThirdStepForm: store.startupApplicationReducer.startupThirdStepForm,
}),
dispatch => ({
isStepDoneActionHandler: index => {
dispatch(isStepDoneAction(index));
},
startupThirdStepFormActionHandler: form => {
dispatch(startupThirdStepFormAction(form));
},
}),
),
)(StepThreeForm);
Right now as you may see I am trying to send the value to the store like this:
onChange={e =>
startupThirdStepFormActionHandler({
firstName: e.target.value,
})
That is for the firstName field, but when I do the same for the middleName field, it obviously cleans the firstName field.
Here is the reducer:
const initialState = {
startupThirdStepForm: {},
};
const handlers = {
[ActionTypes.STARTUP_THIRD_STEP_FORM](state, action) {
return {
...state,
startupThirdStepForm: action.payload.startupThirdStepForm,
};
},
}
export default createReducer(initialState, handlers);
And the action:
export const startupThirdStepFormAction = startupThirdStepForm => ({
type: ActionTypes.STARTUP_THIRD_STEP_FORM,
payload: { startupThirdStepForm },
});
So what can I do to keep the state of the form fields without cleaning the others?
Try doing the following for your reducer instead:
const handlers = {
[ActionTypes.STARTUP_THIRD_STEP_FORM]: (state, action) {
return {
...state,
startupThirdStepForm: {
// to preserve old state
...state.startupThirdStepForm,
// to update with new data
...action.payload.startupThirdStepForm,
},
};
},
}
A very quick suggestion is to create a copy of the state. For you to easily identify what field to update you can add name and value inside payload.
onChange={e =>
startupThirdStepFormActionHandler({
name: "firstName"
value: e.target.value,
})
const handlers = {
[ActionTypes.STARTUP_THIRD_STEP_FORM](state, action) {
let newStartupThirdStepForm = Object.assign({}, state.startupThirdStepForm);
newStartupThirdStepForm[action.payload.name] = action.payload.value;
return {
...state,
startupThirdStepForm: newStartupThirdStepForm,
};
},
}
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.