I have a component that implements a simle Formik form. The component also has listeners for an event generated by clicking on a LeafletJS map. I would like to be able to update the lat and lng fields whenever the event handler is called so that they can optionally be filled by clicking on the map instead of entering values directly.
import React, { useEffect } from 'react';
import { Formik, Form, Field } from 'formik';
const FormComponent = () => {
const onMapClick = e => {
console.log(e.detail.lat, e.detail.lng);
};
useEffect(() => {
document.addEventListener('map:click', onMapClick);
return () => {
document.removeEventListener('map:click', onMapClick);
};
}, []);
return (
<Formik
initialValues={{ username: 'guest', lat: 50.447243, lng: 30.524933 }}
onSubmit={values => { console.log(values); }}
>
{() => (
<Form id="test">
<Field type="text" name="username" />
<Field type="text" name="lat" />
<Field type="text" name="lng" />
<button type="submit">
Submit
</button>
</Form>
)}
</Formik>
);
};
export default FormComponent;
I have come across the Formik setFieldValue function but this only seems to be accessible from within the form. Likewise I cannot use UseEffect within the form function.
I have had some success by changing the intial values and using enableReinitialize but this resets other fields unless I keep track of all fields and update the initial values on each change.
What would be the recommended way of acheiving this? I should add that the code is a trimmed down example. The real form has way more fields.
This is how I would make this.
Imagine that you have component with form, component with map and some parent component that contain both of them.
I would use some useState in parent component that will handle change of selected lat and lng and provide this data to my form.
Then in form I'll use useEffect, so whenever those data is changed it will setFieldValue in form.
All this stuff will look like that (abstract example):
Parent component
const [coords, setCoords] = useState(null)
return (
<>
<Form coords={coords} />
<Map onChange={setCoords} />
</>
)
Form
useEffect(() => {
if(coords !== null) {
setFieldValue("lat", coords.lat)
setFieldValue("lng", coords.lng)
}
}, [coords])
This should work because setFieldValue can set values for fields in every moment of time, even after initialization. I'm not sure what problems have you had with useEffect and setFieldValue, so if my answer didn't helped you, provide some more code examples
Related
I have a react-final-form form, which has 2 inputs. Let's call them from and to.
What I want to do is that whenever input from changes, I set a value for input to based on input from's value.
I do that in validate function because i don't know where else i can do that. And it causes re-rendering the component in a loop.
Since I change the value of to in validate, it causes validate function to run again and again and again. How can I avoid that?
The actual code is much more complex than this but this is where i run into problems.
Thanks
const validate = (v) => {
const calculateFrom = calculate(v.from);
window.setTo(calculateFrom);
};
<Form
onSubmit={onSubmit}
validate={validate}
mutators={{
setTo: (a, s, u) => {
u.changeValue(s, 'to', () => a[0]);
},
setMax: (a, s, u) => {
u.changeValue(s, 'from', () => getMaxBalance(selectedAsset1));
},
}}
subscription={{ submitting: true, pristine: true }}
render={({
form,
pristine,
invalid,
handleSubmit,
}) => {
if (!window.setTo) {
window.setTo = form.mutators.setTo;
}
return (
<form onSubmit={handleSubmit}>
<Field name="from">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
<Field name="to">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
/>
First of all you could use an existing decorator
https://codesandbox.io/s/oq52p6v96y
I'm not really sure why it is re-rendering infinitely. Might have something to do with reusing the function setTo that you put on the window.
If you don't want to add that mutator library I'd try the following solutions.
use parse prop on the the Field where you can get the value and compare it with the other value that you need and return exactly what is should be check attached example parse prop
Final form allows to nest Fields inside custom components so you could just handle everything there by using useForm hook
React Hook Forms detect that I change the value of text input when I type something (onChange). But can it also detect the change if I enter data to the input by value={value}?
const validator = register(name);
I need something like
onValueSet={(e) => {
validator.onChange(e);
}}
I mean if I just set data by value={value} I get an error that this input is required. I don't want this error, because data was set by value={value}.
Here is my input:
<StyledInput
name={name}
maxLength={100}
value={value}
onChange={(e) => {
validator.onChange(e);
}}
/>
and my validation schema:
const validationSchema = Yup.object().shape({
first_name: Yup.string()
.required("required"),
});
You have to use reset here and call it when you received your initial form data. I assume you're doing an api call and want to set the result to the form. You can do so with useEffect, watching when you're data has resolved and then reset the form with the actual values. This way you don't have to set the value via the value prop and let RHF manage the state of your inputs. You also don't need to set name prop, as RHF's register call returns this prop as well.
const Component = (props) => {
const { result } = useApiCall();
const { register, reset } = useForm();
useEffect(() => {
reset(result)
}, [result]);
return (
...
<StyledInput
{...register('first_name')}
maxLength={100}
/>
...
)
}
Here is a little CodeSandbox demonstrating your use case:
You can define an onfocus or onkeyup event and call the validation function there instead of onChange event.
<StyledInput
name={name}
maxLength={100}
value={value}
onfocus={(e) => {
validator.onChange(e);
}}
/>
Instead of triggering the validator when input changes, you can instead call your validator through the onBlur event. And I am not sure how you are using react-hook-form .. but the useForm hook has a config mode (onChange | onBlur | onSubmit | onTouched | all = 'onSubmit') on when to trigger the validation:
I have an app in MeteorJS, which makes use of React (I am ok with JavaScript, but am on a learning curve starting with React). The current search input makes use of the onchange function of the input box BUT this is actually not desired as this slows the app considerably - making requests every time the user types.
I basically want the input to be basic input and then have a button to trigger the search.
Inline code, for calling the searchinput where needed;
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={this.filterGames} value={filter} />
</div>
searchinput component;
import PropTypes from 'prop-types';
import Icon from '../Icon';
import Styles from './styles';
const SearchInput = ({ placeholder, value, onChange }) => (
<Styles.SearchInput className="SearchInput">
<Icon iconStyle="solid" icon="search" />
<input
type="text"
name="search"
className="form-control"
placeholder={placeholder}
value={value}
onChange={onChange}
/>
</Styles.SearchInput>
);
SearchInput.defaultProps = {
placeholder: 'Search...',
value: '',
};
SearchInput.propTypes = {
placeholder: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
};
export default SearchInput;
Hoping you all could help ;)
Basically what you need to do is use a state to store the value from the onChange event and later, on button click/form submit action you pass this value to the function that will actually fetch data.
Here is a small example on code sandbox where you can see this being applied both on a functional component and on a class component
lets assume your "wrapper" component is something like this:
const Form = () => {
const filterGames = (event) => {
// handle event and calls API
}
return (
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={filterGames} value={filter} />
</div>
)
}
What we need to do here, is basically adding the state and handle it without calling the API and a button to actually call the API.
const Form = () => {
const [inputValue, setInputValue] = useState('');
const filterGames = (event) => {
// handle event and calls API
}
// this will store the value locally on the state
const handleInputOnChange = (event) => {
setInputValue(event.target.value);
}
return (
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={handleInputOnChange} value={inputValue} />
<button type='submit' onClick={filterGames}>Submit</button>
</div>
)
}
ps: you can also wrap the input + button with a form and use form.onSubmit instead of button.onClick.
I have a form loaded with certain values in in its field. I'm unable to type in the value of fields or delete the existing default values when the form was rendered. Basically, I'm trying to add an Update operation in my form in which values are shown in field (e.g. On clicking an edit button, form is displayed with the values).
I tried to capture the event with onChange method but it's not working as I expected.
The default values are fetched as props from its parent and passed to the value argument of the form. I'm using semantic UI React components for the form.
Here is the example in codesandBox of what I'm trying to implement:
codesandbox
In the above example, variable is passed to value in form.
if you look at the error, it clearly says: A component is changing an uncontrolled input of type text to be controlled. Which means you have to store the values of name, email in state on initialization, and have those values changed on onChange event. And not update values only on onChange.
import React, { Component } from "react";
import { Form } from "semantic-ui-react";
class FormExampleClearOnSubmit extends Component {
state = {
name: "james",
email: ""
};
handleChange = (e, { name, value }) => this.setState({ [name]: value });
handleSubmit = () => this.setState({ email: "", name: "" });
render() {
const { name, email } = this.state;
return (
<Form onSubmit={this.handleSubmit}>
<Form.Group>
<Form.Input
placeholder="Name"
name="name"
value={name}
onChange={this.handleChange}
/>
<Form.Input
placeholder="Email"
name="email"
value={email}
onChange={this.handleChange}
/>
<Form.Button content="Submit" />
</Form.Group>
</Form>
);
}
}
export default FormExampleClearOnSubmit;
When I enter a character in the input box
The state updates with the new character
Then I loose focus on the input box
so I can only modify the box 1 keypress at at time
The input box is nested in 4 other components which includes 1 higher Order component (see below)
Page component
header(Modify)
InputForm
When I move the form code to the Page component it works.
How I keep the components separate (and reusable) and have the functionlity I need?
The form code is below
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
The full code for the entiure component is here
mport React, { Fragment } from 'react';
const InputForm = (props) => {
//prepare props
console.log('currentObject', props.currentObject);
const { editorState, stateModifier, userFunctions } = props.editorEssentials;
// const urlFormVisible = props.urlFormVisible;
//Styles
const componentStyle = 'container-flex-column';
// console.log('MOdify: currentState', editorState);
// console.log('MOdify: targetObject', currentObject);
// console.log('MOdify: stateModifier', stateModifier);
console.log('currentObject.name', props.currentObject.name);
return (
<Fragment>
<form
className={componentStyle}
// onSubmit={(event) =>
// userFunctions.submitItem(editorState, currentObject, stateModifier, event)
// }
>
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
{props.urlFormVisible && (
<input
type='url'
value={props.currentObject.url}
onChange={(event) =>
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'url'
)
}
/>
)}
</form>
</Fragment>
);
};
export default InputForm;
The function operates on the state and is bound in the master component
WHAT I HAVE TRIED
There are some similar posts on stack overflow but they do not seem to answer my problem.
Changed the Key value in the input (although my original version of this had a no ket defined)
Double checked that the modifyItem function is bound correctly - it looks like it is and I guess if it wasn't the state would not update at all
Tried simplifing code to reduce the number of functions needed to run
MOVING THE COMPONENT THE CODE HAS MADE
I am not sure why but the Higher order component was the problem
when I changed the config from
Page component
header(Modify)
InputForm
to
Page component
header
Modify
InputForm
/header
It worked
Any ideas why I had this problem?