I have the following scenario:
main component with a few fields that Formik handles. Everything fine here.
sub component that renders inside the main form and uses Formik's Field component, same as the fields in the main component do. These fields are not getting updated.
Main component:
...
return (
<Formik
enableReinitialize
initialValues={{
name: this.state.name,
newName: this.state.newName, // this field is inside the nested component
}}
validationSchema={mySchema}
onSubmit={...}
>
{
({ errors, values, ... }) => (
<Form ref={this.formRef}>
...
<Field name="name" type="text" />
...
<NewNameForm />
</Form>
)
}
</Formik>
);
NewNameForm component:
...
return (
<div>
<Field name="newName" type="text" />
</div>
);
Is my approach wrong, can I just nest components with extra fields like this? newName isnt' getting updated so I'm obviously doing something wrong.
I've solved this by passing Formik's setFieldValue method to subcomponent's props like this:
onNameChange={(name) => {
setFieldValue('newName', name);
}}
Related
I am new to this topic.
In the parent component App I have two siblings : SideMenu and Document
The idea is that the user inputs values (SideMenu) which will be renedered on the Document. There will be more than 20 inputs. Since this is the first time I do this sort of state management, what are the best or maybe easiest approaches for this attempt of project.
function App() {
const [fullName, setFullName] = useState("")
const [address, setAddress] = useState("")
return (
<div className='app'>
<SideMenu />
<Document />
</div>
)
}
export default App
const SideBar = () => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input type='text' />
</div>
<div className='input-group'>
<label>Address:</label>
<input type='text' />
</div>
</div>
)
}
const Document = () => {
return (
<div>
<h1>{fullName}</h1>
<p>{address}</p>
</div>
)
}
You can create an object for your form and store the form inputs in this object. Shared state can be stored in the most closest and common component (in your situation this is your parent component) according to your child components. [1]
When you make an update from a child component other child component that is sharing state will be syncronized and your state will be updated. You shouldn't use redux like state management tools unless you are need to set a global state.
I have made a revision for your example, this scenario allows you to pass the state in the parent component to the child components and update the state in the parent component from the child components.
I used a common event handler in the parent component, this functions captures the html event and we parse this event and update the state via this function. [2][3]
import "./styles.css";
import { useState } from "react";
import SideBar from "./SideBar";
import Document from "./Document";
export default function App() {
const [values, setValues] = useState({
fullName: "",
address: "",
postalCode: ""
});
function handleChange(event) {
setValues({ ...values, [event.target.name]: event.target.value });
}
return (
<div className="app">
<SideBar values={values} setValues={handleChange} />
<Document values={values} setValues={handleChange} />
</div>
);
}
export default function Document({ values }) {
return (
<div>
<h1>Document</h1>
<p>Full Name: {values.fullName}</p>
<p>Address: {values.address}</p>
<p>Postal Code: {values.postalCode}</p>
</div>
);
}
export default function Sidebar({ setValues }) {
return (
<div>
<div className="input-group">
<label>Full Name:</label>
<input type="text" name="fullName" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="address" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="postalCode" onChange={setValues} />
</div>
</div>
);
}
Code Sandbox Link: https://codesandbox.io/s/stackoverflow-74961591-wpmcnd
[1]: Passing Props to a component: https://beta.reactjs.org/learn/passing-props-to-a-component
[2]: Updating Objects in State: https://beta.reactjs.org/learn/updating-objects-in-state
[3]: HTML Change Event: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event
The default, go-to solution would be to use a container component that controls your form inputs state (this would be App in your case). Just pass the values and setters down one level by props and everything should be ok, simple and predictable.
If things start to get complicated then libraries such as Formik or react-hook-form help a lot. When it comes to managing multiple or complex forms that may also need validation they are your best bet. I suggest you take this approach.
Using Redux for this kind of situation is a huge anti-pattern. Redux global store should be only used for global state, not local form state.
Context API is well suited when you need to pass data to multiple deeply nested children. This way you do not need to pass props dozens of levels down the tree. However, it is usually used by 3rd party libraries such as the ones mentioned above (all of them).
You can use Formik library for handling many inputs. Wrap both components inside Formik and use formik's methods.
import { Formik } from 'formik';
<Formik
initialValues={{ fullName: '', address: '' }}
onSubmit={(values) => {
alert(JSON.stringify(values, null, 2));
}}
>
{({handleChange, values, handleSubmit}) => (
<form onSubmit={handleSubmit}>
<div className='app'>
<SideMenu
handleChange={handleChange}
/>
<Document values={values} />
<button type="submit">Submit</button>
</form>
)}
</Formik>
You dont need to create multiple states for each input. handlChange will handle itself. You just need add name or id attribute to input. Also you can access values of each input using the values parameter like values.fullName.
const SideBar = ({handleChange}) => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input
type='text'
onChange={handleChange}
name="fullName"
/>
</div>
<div className='input-group'>
<label>Address:</label>
<input
type='text'
onChange={handleChange}
name="address"
/>
</div>
</div>
)
}
const Document = ({values}) => {
return (
<div>
<h1>{values.fullName}</h1>
<p>{values.address}</p>
</div>
)
}
I have a component for 4 digit code of phone validation. By itself it works fine and looks good as well. The only issue I am facing - I can't autotab between numbers. I have to go to each input manually and write the number. Is it possible to do with Formik Field?
This is my piece of code:
<Formik
onSubmit={values =>
VerifyGarageFunc({ code: values.code.join(''), requestId: PhoneCodeData.data }, data.showModal)
}>
{({ values, handleChange, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<FieldArray
name="code"
render={arrayHelpers => (
<div className={styles.inputWrapper}>
{values.code.map((item, index) => (
<div key={index}>
<Field
name={`code.${index}`}
type="text"
component={CustomInput}
onChange={handleChange}
value={values.code[index]}
/>
</div>
))}
</div>
)}
/>
<LoginActionButton onSubmit={handleSubmit} text={'Send'} />
<FieldArray />
</form>
)}
</Formik>
I tried https://www.npmjs.com/package/react-auto-tab but it works only with <input/>, for some reason it doesn't work at all with Formik Field.
P.S. I am using Next.js with React.js
You'll likely have more luck with a hook based solution. Install https://github.com/Romr1ch/react-pin-input-hook, which just does the logic without being opinionated about display.
Create a new component called PinInput and create a new field using the formik hook primitives.
Ive setup an example codesandbox: https://codesandbox.io/s/react-pin-input-hook-custom-input-1ze5dv?file=/src/App.js, but note this doesn't use your exact components as I don't have them. The below code should match closer your exact case.
import React from 'react'
import { useField } from 'formik'
import { usePinInput } from 'react-pin-input-hook'
export const PinInput = (props) => {
const [field, meta, helpers] = useField(props)
const { fields } = usePinInput({
values: field.value,
onChange: (values) => {
helpers.setValue(values)
},
})
return fields.map((fieldProps, index) =>
<CustomInput key={index} type="text" {...fieldProps} />
)
}
Then in your main file do this (by the way if you use Form component from Formik you dont need to do any of the onSubmit binding yourself, so I changed that along the way -- the button can just be "submit" type):
<Formik
onSubmit={values =>
VerifyGarageFunc({ code: values.code.join(''), requestId: PhoneCodeData.data }, data.showModal)
}>
<Form>
<PinInput name="code" />
<LoginActionButton type="submit" text={'Send'} />
</Form>
</Formik>
Note that this lib requires your CustomComponent to attach a ref to the underlying thing that needs focusing so you'll need to use forwardRef on that component and attach it to the underlying input. It also needs to support onBlur, onFocus, onChange and onKeyDown.
How can I clear value from the TextField after submitting the form? All of the components in the code are functional components, not the class ones. Here is one of the TextFields, the others are similar to this one. I can make the type='search' as in the code bellow but then a user needs to press a button to clear the value and even then the error check is complaining about the field being empty.
Would it be better to try and refresh the component? This form is in a sidebar in my app.
<form onSubmit={onSubmitHandler} className={classes.root}>
<TextField
name='firstName'
inputRef={register({
required: "Please enter first name",
validate: value => isEmpty(value)
})}
label={translate('invitation.first_name')}
error={!!errors.firstName}
type='search'
autoFocus
fullWidth
/>
There is a value property that you have to pass to the TextField component. check example below:
class SomeComponent extends Component {
state = {value: ''}
resetValue = () => {
this.setState({value: ''});
}
render() {
return (
<div>
<TextField
...
value={this.state.value}
/>
<button onClick={this.resetValue}>Reset</button>
</div>
)
}
}
If i have the following dialog/modal:
<Modal
open={this.state.createAccountModalOpen}
trigger={<Link size="m" theme="bare" href="#" className="main-menu-item" onClick={this.handleOpenModalCreateAccount}>Create account</Link>}
closeIcon
onClose={() => { this.setState({
createAccountModalOpen: false,
}); }}
>
<Header icon='add user' content='Create account' />
<Modal.Content>
<Form />
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={this.handleSubmit}>
<Icon name='add user' /> Create account
</Button>
</Modal.Actions>
</Modal>
Basically this is a React Semantic-ui Modal/Dialog. Now What i want to do is make Form reusable (the Form component contains 4 input fields), so i can use it in other modals or components. What would be the best way so that when I click on Create account, it gathers the data from the form and then submits it?
Do I have to pass functions to the Form to try store the data in the main Modal component? or is there a better way to get the validated data from the form?
I’m on my phone so I’m limited.
You want to define your custom function in the parent component where you call your Modal. Then pass that function to it as a prop modal onComplete={this.submitEmail}
Then in your modal component call this.props.onComplete in your handleSubmit.
Then from here out you can define the custom function you want to use wiTh the model and pass it through with onComplete={whateverFunction}
In order to only show the inputs that you want you could set up a series of render if statements. Then when you call your Modal you can pass through renderIfText={“email”} and in your model if this.props.renderIfText=email render email input.
import React from 'react';
class ReusableModalForm extends React.Component {
constructor(props){
super(props);
this.state ={
};
}
handleChange(e) {
let {name, value} = e.target;
this.setState({
[name]: value,
usernameError: name === 'username' && !value ? 'username must have a value' : null,
emailError: name === 'email' && !value ? 'email must have a value' : null,
passwordError: name === 'password' && !value ? 'password must have a value' : null,
});
}
handleSubmit(e) {
e.preventDefault();
this.props.onComplete(this.state)
}
render() {
return (
<Modal
open={this.state.createAccountModalOpen}
trigger={<Link size="m" theme="bare" href="#" className="main-menu-item" onClick={this.handleSubmit}>{this.props.buttonText}</Link>}
closeIcon
onClose={() => { this.setState({
createAccountModalOpen: false,
}); }}
>
<Header icon='add user' content='Create account' />
<Modal.Content>
<Form />
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={this.handleSubmit}>
<Icon name='add user' /> {this.props.buttonText}
</Button>
</Modal.Actions>
</Modal>
);
}
}
export default ReusableModalForm;
In order to make your <Form /> reusable you need to determine what are the inputs/outputs to your Form and allow any potential parent component to access/manipulate it via props.
Perhaps something like:
<CreateAccountForm
input1DefaultValue={...}
input2DefaultValue={...}
onSubmit={yourCreateAccountFormHandler}
/>
Do I have to pass functions to the Form to try store the data in the main Modal component? or is there a better way to get the validated data from the form?
It depends on how you implement Form and your input fields.
I would recommend react-form library or, if you want to have your own implementation - using redux state and wire your inputs/form to redux.
If no redux then you will need to store the state of inputs in the modal.
Whenever you compose components, you share data between them using props. I will be passing "name and label" props to stateless functional component named;
input.js
import React from "react";
const Input = ({name,label}) => {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<input
autoFocus
name={name}
id={name}
className="form-control"
aria-describedby="emailHelp"
/>
);
};
export default Input;
form.js
import React, { Component } from "react";
import Input from "./common/input";
class RegisterForm extends Form {
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input name="username" label="username" />
<input name="email" label="email" />
<input name="password" label="password" />
</form>
</div> ); } }
I am having some concept problem in using FieldArray, I have multiple Select components wired to FieldArray, each select component should update value onChange and should delete it through api. To be able to perform api request I need to access other details for the field like id. I have setup my component something like shown below, it works fine without api call..
renderAdditionalSpeakers({fields}){
return (<div>
{
fields.map((speaker,index)=>
<Field
name={`${speaker}.speakerid`}
label="Choose Speaker"
type="select"
component={this.renderSelectWithDelete}
mandatory='false'
placeholder='Select Speaker'
opts={this.props.speakers}
key={`KEY-${index}`}
deleteaction={() => fields.remove(index)}
onChange={this.onSpeakerChange.bind(this)}
/>
)
}
<div className="form-group row">
<div className="col-sm-3"> </div>
<div className="col-sm-9 col-sm-offset-0">
<Button className="button-theme button-theme-blue" type="button" onClick={() => fields.push({})}>Link More Speaker</Button>
</div>
</div>
</div>);
}
and render method look like this
render() {
var {handleSubmit, invalid, pristine, submitting,speakers,tracks} = this.props;
return(
<div>
<form onSubmit={handleSubmit(this.onFormSubmit.bind(this))}>
<FieldArray name="additionalspeakers" component={this.renderAdditionalSpeakers.bind(this)}/>
</form>
</div>
);
}
From the above code, I need to call api with in ondeleteaction callback and onChange action. This is only possible when I can access the json object which consist of following values
{
"id":"1",
"speakerid":"23",
"sessionid":"102",
"eventid":"200"
}
How to achieve?
Thanks for help.
You can pass further arguments to your field array and use them within your render method, like
<FieldArray name="additionalspeakers" component={this.renderAdditionalSpeakers.bind(this)} props={{ onDeleteAction: this.ondeleteaction, onChange: this.onChange }} />
(where this.ondeleteaction and this.onChange are the callback function defined in your component).
Then you can declare the FieldArray as:
renderAdditionalSpeakers({fields, onDeleteAction, onChange}) {
and use the function callbacks within the component.