I'm trying to get the value of an autocomplete field outside of my formik component everytime it changes, and i did something like this:
const formRef=useRef<any>(null);
useEffect(() => {
console.log("eee!!",formRef.current?.values?.flagTR)
},[formRef.current?.values]);
return (
<Formik
initialValues={initialValues}
onSubmit={handleSumbit}
enableReinitialize={true}
validateOnChange={true}
validationSchema={ProduitSchema}
innerRef={formRef}
>
the useEffect is triggered the first time the component renders,but then when i change the value of the autocomplete the useEffect doesn't get triggered.
what should i do in order to trgger useEffect everytime a value from formik changes?
Hey so I don't think the approach you are going with here is going to work, it's really not what the useRef hook is for.
The correct approach for what it looks like you want to do is to properly use Formik's context to mapValuesToProps or get access to the values, errors, and validation states.
You can use withFormik() to set up your initial form values and mapValuesToProps. Then you can use within the form component formik's useFormikContext() which will give you access to values, errors, setValues, etc
export default withFormik({
handleSubmit: () => {},
validateOnChange: true,
validateOnBlur: true,
validateOnMount: true,
mapPropsToValues: () => ({ name: '', email: '' }),
initialValues: { name: '', email: '' }
})(MyFormComponent)
In the MyFormComponent you can then call useFormikContext() and do whatever you want when the values change.
const { setValues, values, isValid, validateForm } = useFormikContext()
If for some reason this is not what you want to do, or it does not solve your problem, the only way to achieve what you want in React alone is to useState and and setState each time onChange eg
const MyFormComponent = () => {
const [nameField, nameMeta] = useField(name)
const [emailField, emailMeta] = useField(email)
const [formState, setFormState] = useState({name: '', email: ''})
return (
<Formik
enableReinitialize
validateOnBlur={false}
initialValues={formState}
onSubmit={() => {}}
>
{props => {
const onChange = e => {
const targetEl = e.target
const fieldName = targetEl.name
setFormState({
...formState,
[fieldName]: targetEl.value
})
return props.handleChange(e)
}
return (
<>
<input {...nameField} onChange={onChange}>
<input {...emailField} onChange={onChange}>
</>
)
</Formik>
)
Related
I'm trying to separate formik validation to a custom hook. The hook returns yup falidation schema, but it needs functions from formik bag to build that schema. How could I get the reference to formik in the component root, and pass it down to custom hook? The component looks like this:
export const MyView = () => {
let formikRef: FormikProps<FormData> | undefined = undefined;
const { validationSchema } = useValidationSchema(formikRef);
...
return (
<Formik
initialValues={initialValues}
onSubmit={submitForm}
enableReinitialize={true}
validateOnChange={false}
validateOnBlur={false}
validationSchema={validationSchema}
>
{(formik) => {
formikRef = formik;
return (
<>
...
</>
);
}}
</Formik>
);
}
This method of getting the ref does not work. The ref actually receives the correct value, but the custom hook only receives initial value (undefined). I also tried storing the ref to state, but that leads to infinite render loop. Additionally I tried using innerRef, but again, custom hook always receives undefined reference.
EDIT:
Managed to get it working by using useFormik and FormikProvider. Now there's a problem with reference order: formik uses validationSchema and other way around. I solved this by adding a function to custom hook:
const { validationSchema, setFormik } = useValidationSchema();
const formik = useFormik<FormData>({
initialValues: initialValues,
onSubmit: submitForm,
enableReinitialize: true,
validateOnChange: false,
validateOnBlur: false,
validationSchema: validationSchema,
});
setFormik(formik);
Is there a more elegant solution?
Ended up with
export const MyView = () => {
const { validationSchema, setFormikForValidation } = useValidationSchema();
const formik = useFormik<FormData>({
initialValues: initialValues,
onSubmit: submitForm,
enableReinitialize: true,
validateOnChange: false,
validateOnBlur: false,
validationSchema: validationSchema,
});
setFormikForValidation(formik);
return (
<FormikProvider formik={formik}>
...
</FormikProvider>
);
}
You can you useFormikContext hook. This is a hook provided by formik and you can use it in any of the children of a formik instance.
Your form:
import Formik from 'formik';
const Form = (props) => {
return (
<Formik {...props}>
<FormBody/>
</Formik>
);
}
your form body:
import {useFormikContext} from 'formik';
const FormBody = () => {
const {values, ...etc} = useFormikContext();
return (
...
);
}
I have rather simple problem.
I don't know why my useState function won't change state -> props
onChangeText={(text) => {
setProps({ ...props, inputValue: text });
}}
I declared my state as here:
const [props, setProps] = useState({
pageNumber: 2,
inputValue: "",
});
Thanks in advance!
prevState received by the updater function is guaranteed to be up-to-date. It's a reference to the component state at the time the change is being applied. Official docs
onChange={(event) => {
setProps(prevState => {
return { ...prevState, inputValue: event.target.value }
});
}}
Also if you have input just use native event onChange instead of custom onChangeText
You wrote 'onChangeText' instead of 'onChange'. And, it gives you the event object rather than the text itself. It should be like this:
<input onChange={(event) => {
setProps({ ...props, inputValue: event.target.value });
}} />
What you doing is correct and nothing is wrong with it.
you need to pass your state value to the input
import { useState } from "react";
export default function App() {
const initialState = {
pageNumber: 2,
inputValue: ""
};
const [props, setProps] = useState(initialState);
const textInputChangeHandler = (e) => {
setProps({ ...props, inputValue: e.target.value });
};
return (
<div className="App">
<input
value={props.inputValue}
type="text"
onChange={textInputChangeHandler}
/>
</div>
);
}
Consider this basic form fields component with a custom form hook to handle input changes:
import React, { useState, useCallback } from 'react';
const useFormInputs = (initialState = {})=> {
const [values, setValues] = useState(initialState);
const handleChange = useCallback(({ target: { name, value } }) => {
setValues(prev => ({ ...prev, [name]: value }));
}, []);
const resetFields = useCallback(() =>
setValues(initialState), [initialState]);
return [values, handleChange, resetFields];
};
const formFields = [
{ name: 'text', placeholder: 'Enter text...', type: 'text', text: 'Text' },
{ name: 'amount', placeholder: 'Enter Amount...', type: 'number',
text: 'Amount (negative - expense, positive - income)' }
];
export const AddTransaction = () => {
const [values, handleChange, resetFields] = useFormInputs({
text: '', amount: ''
});
return <>
<h3>Add new transaction</h3>
<form>
{formFields.map(({ text, name, ...attributes }) => {
const inputProps = { ...attributes, name };
return <div key={name} className="form-control">
<label htmlFor={name}>{text}</label>
<input {...inputProps} value={values[name]}
onChange={handleChange} />
</div>;
})}
<button className="btn">Add transaction</button>
</form>
<button className="btn" onClick={resetFields}>Reset fields</button>
</>;
};
Is there really any reason / advantage for me to use useCallback to cache the function in my custom hook? I read the docs, but I just coudln't grasp the idea behind this usage of useCallback. How exactly it memoizes the function between renders? How exactly does ti work, and should I use it?
Inside the same custom hook, you can see the new values state being updated by spreading the previous state and creating a new object like so: setValues(prev => ({ ...prev, [name]: value }));
Would there be any difference if I did this instead? setValues({ ...prev, [name]: value })
as far as I can tell, doesn't look like it has any difference right? I am simply accessing the state directly.. Am I wrong?
Your first question:
In your case it doesn't matter because everything is rendered in the same component. If you have a list of things that get an event handler then useCallback can save you some renders.
In the example below the first 2 items are rendered with an onClick that is re created every time App re renders. This will not only cause the Items to re render it will also cause virtual DOM compare to fail and React will re create the Itms in the DOM (expensive operation).
The last 2 items get an onClick that is created when App mounts and not re created when App re renders so they will never re render.
const { useState, useCallback, useRef, memo } = React;
const Item = memo(function Item({ onClick, id }) {
const rendered = useRef(0);
rendered.current++;
return (
<button _id={id} onClick={onClick}>
{id} : rendered {rendered.current} times
</button>
);
});
const App = () => {
const [message, setMessage] = useState('');
const onClick = (e) =>
setMessage(
'last clicked' + e.target.getAttribute('_id')
);
const memOnClick = useCallback(onClick, []);
return (
<div>
<h3>{message}</h3>
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={onClick} />
))}
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={memOnClick} />
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Another example is when you want to call a function in an effect that also needs to be called outside of the effect so you can't put the function inside the effect. You only want to run the effect when a certain value changes so you can do something like this.
//fetchById is (re) created when ID changes
const fetchById = useCallback(
() => console.log('id is', ID),
[ID]
);
//effect is run when fetchById changes so basically
// when ID changes
useEffect(() => fetchById(), [fetchById]);
Your second question:
The setValues({ ...prev, [name]: value }) will give you an error because you never defined pref but if you meant: setValues({ ...values, [name]: value }) and wrap the handler in a useCallback then now your callback has a dependency on values and will be needlessly be re created whenever values change.
If you don't provide the dependency then the linter will warn you and you end up with a stale closure. Here is an example of the stale closure as counter.count will never go up because you never re create onClick after the first render thus the counter closure will always be {count:1}.
const { useState, useCallback, useRef } = React;
const App = () => {
const [counts, setCounts] = useState({ count: 1 });
const rendered = useRef(0);
rendered.current++;
const onClick = useCallback(
//this function is never re created so counts.count is always 1
// every time it'll do setCount(1+1) so after the first
// click this "stops working"
() => setCounts({ count: counts.count + 1 }),
[] //linter warns of missing dependency count
);
return (
<button onClick={onClick}>
count: {counts.count} rendered:{rendered.current}
</button>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have a component within a component. The parent component (Form) is expensive to render because of complex caluculations. Because of this if I hold down a key in the username input in the child component (UserForm) the page starts dropping frames (new characters don't show for a bit) because it renders the parent (which is expensive) as quickly as possible. To solve this I started storing a local instance of the username prop into state in the child component and then debouncing that state before passing it up to the parent.
The issue is when the parent component modifies the username (such as by reverting the changes) while the debounce in the child component is still in progress, the parent component's modification is overwritten by the child component's debounced output.
const UserForm = ({ value, onChange }) => {
const debouncedOnChange = useCallback(
lodash.debounce(updatedValue => {
onChange(updatedValue);
}, 500),
[onChange]
);
const [username, setUsername] = useState(value.username);
useEffect(() => {
setUsername(value.username);
}, [value.username]);
const handleUsernameOnChange = useCallback(
event => {
setUsername(event.target.value);
debouncedOnChange({
...value,
username: event.target.value,
});
},
[debouncedOnChange]
);
return (
<div>
<input value={username} onChange={handleUsernameOnChange} />
</div>
);
};
const INITIAL_STATE = {
user: {
username: 'John',
},
dog: {
name: 'Cliffard'
}
};
// <Form/> Is expensive to render because of complex calculations
const Form = () => {
const [value, setValue] = useState(INITIAL_STATE);
const [backup, setBackup] = useState(INITIAL_STATE);
return (
<form>
<UserForm
value={value.user}
onChange={updatedUserFormValue => {
setValue({
...value,
updatedUserFormValue,
});
}}
/>
<button
onClick={() => {
setValue({
...value,
user: { ...backup.user },
});
}}
>
Reset User Form
</button>
</form>
);
};
When I try to get reference on formik, I get null in current field of ref object.
const formikRef = useRef();
...
<Formik
innerRef={formikRef}
initialValues={{
title: '',
}}
onSubmit={(values) => console.log('submit')}
>
And next:
useEffect(() => {
console.log(formikRef);
}, []);
Get:
Object {
"current": Object {
"current": null,
},
}
How can I fix this problem?
Help please.
If you want to call submit function outside Formik, you can use useImperativeHandle. Document
// Children Component
const Form = forwardRef((props, ref) => {
const formik = useFormik({
initialValues,
validationSchema,
onSubmit,
...rest // other props
})
useImperativeHandle(ref, () => ({
...formik
}))
return ** Your form here **
})
and using:
// Parent Component
const Parent = () => {
const formRef = useRef(null)
const handleSubmitForm = (values, actions) => {
// handle logic submit form
}
const onSubmit = () => {
formRef.current.submitForm()
}
return (<>
<Form ref={formRef} onSubmit={handleSubmitForm} />
<button type="button" onClick={onSubmit}>Submit</button>
</>)
}
Read the ref only when it has value, and add the dependency in useEffect on the ref.
useEffect(() => {
if (formikRef.current) {
console.log(formikRef);
}
}, [formikRef]);
Remember, that refs handle it's actual value in .current property.
What worked for me was adding variables inside useEffect's [].
For my case, it was [ref.current, show].
Add an if(ref.current) {...} before any ref.current.setFieldValue in useEffect body as well or ref.current?.setFieldValue.
This cost me several hours, I hope you're better off.