Reusable form fields in React - javascript

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> ); } }

Related

Empty Material-UI textfield after submitting the form

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>
)
}
}

Set the value of a form based on state in React

Using Formik for validating some fields, if a state value is true I want that the value of a fields to be copied to the value of another one.
For example, value of mainAddress to be copied to address.
There is a state variable, setAddress which is set on false but it can be changed to true when a checkbox is clicked.
When this variable is set on true I want that the value of mainAddress to be copied to address
This is the code that works fine without copying that value:
import React from 'react';
import { Formik, Form, Field } from 'formik';
import { Input, Button, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
import { Creators } from '../../../actions';
import './CreateCompanyForm.scss';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = { // state variables
address: '',
mainAddress: '',
setAddress: false,
};
}
handleSubmit = values => {
// create company
};
toggleAddress = () => { // it toggles the value of setAddress
this.setState(prevState => ({
setAddress: !prevState.setAddress,
}));
};
render() {
const initialValues = { // the address and mainAddress are set to '' at the start
address: '',
mainAddress: '',
};
const validationSchema = Yup.object({
address: Yup.string().required('error'),
mainAddress: Yup.string().required('error'),
});
return (
<>
<Button type="submit" form="amazing"> // button for submit
Create company
</Button>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues, setFieldValue }) => (
<Form id="amazing">
<Grid>
<Grid.Column> // mainAddress value
<Label>Main Address</Label>
<Field name="mainAddress" as={Input} />
</Grid.Column>
<Grid.Column> // address value
<Label>Address</Label>
<Field name="address" as={Input} />
<div>
{this.state.setAddress // here is how I've tried to set that value
? values.address.setFieldValue(values.mainAddress)
: console.log('nope')}
{touched.address && errors.address ? errors.address : null}
</div>
</Grid.Column>
</Grid>
<Checkbox
label="Use same address"
onClick={() => this.toggleAddress()}
/>
</Form>
)}
</Formik>
</>
);
}
}
So I've tried more ways to solve it but without success. Now in the code it is:
{this.state.setAddress
? values.address.setFieldValue(values.mainAddress)
: console.log('nope')}
Other one was: (this.state.setAddress ? values.address = values.mainAddress)
None of them work. Is it possible to get the value from values.mainAddress and copy it to values.address? Or to its input?
You can rewrite the Field component for address accordingly.
<Field name="address">
{({
field, // { name, value, onChange, onBlur }
}) => {
values.address = values.mainAddress;
return (
<div>
<input
{...field}
type="text"
placeholder="Address"
/>
</div>
);
}}
</Field>
Here we are setting the value of address field to values.mainAddress if setAdress checkbox is checked else we let formik value to fill it.
Here is the working codesandbox code - https://codesandbox.io/s/fervent-tereshkova-sro93?file=/index.js
Formik also provides values object you could use that for updating address value same as mainAddress. Just provide name attribute to both input fields then assign like this - props.values.address = {...props.values.mainAddress}

Formik field from nested component not updating

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);
}}

React Context API and component methods [duplicate]

This question already has answers here:
Access React Context outside of render function
(5 answers)
Closed 3 years ago.
I've followed a few online examples, where they have a counter and an increment function in Context, and on a distant component, call the increment method and the results shows. All great, but ... I am trying to expand on this and create a login box, that sets an isAthenticated flag.
I have a very basic context:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = {
isAuthenticated: false,
user: {
name: "Craig",
email: "craig#here.com"
},
changeEmail: (newEmail) => {
let user = this.state.user;
user.email = newEmail;
console.log(user);
this.setState({ user: user})
},
changeAuthenticated: () => {
this.setState ({ isAuthenticated: !this.state.isAuthenticated });
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer;
In it I allow the user to change email, and change isAuthenticated state.
My component (Remove style stuff) looks like this:
import React from 'react';
import { Input, Label, Button, Container } from 'reactstrap';
import { Consumer } from '../context';
class Login extends React.Component {
render() {
return (
<Consumer>
{value => {
return (
<Container style={containerStyle} >
<div style={loginBoxStyle}>
<div>
<h3>Login</h3>
</div>
<div style={loginBoxFieldsStyle}>
<div style={loginBoxFieldStyle}>
<div style={loginBoxLabelStyle}>
<Label for="email">Email:</Label>
</div>
<div style={loginBoxLabelStyle}>
<Input type="email" name="email" id="email" placeholder="Your Email" value={value.user.email} onChange={e=>value.changeEmail(e.target.value)} />
</div>
</div>
</div>
<div style={loginBoxFieldsStyle}>
<div style={loginBoxFieldStyle}>
<div style={loginBoxLabelStyle}>
<Label for="password">Password:</Label>
</div>
<div style={loginBoxLabelStyle}>
<Input type="password" name="password" id="password" placeholder="Your Password" />
</div>
</div>
</div>
<div style={loginBoxButtonStyle}>
<Button color="info" onClick={value.changeAuthenticated}>Login</Button>
</div>
</div>
</Container>
)}
}
</Consumer>
)
}
}
export default Login;
So when I change the email, the Context state is updated. And when I click the Login button, for now, it simply toggles IsAuthenticated.
I don't want the state to update as I type in the email box. I'd prefer to update the state when the Login button is clicked. So I feel I need a local component state, or something, which updates that state when I edit the data in the text boxes. And then updates the Context when I click Login.
But... How do I set up the state? 'values' (from context) is only available inside the Render. I need to set my component state outside of the render. So how would I go about achieving this?
My login button onClick should also fire a local method which has all the validation etc, and then update my route to redirect to a page on success. But then it needs access to the Context.UpdateMethod - from outside of the tags. Not sure how to achieve this.
You should probably just create a sub-component and then use the props to initialize the state.
class Login extends React.Component {
render() {
return (
<Consumer>
{value => (
<Container style={containerStyle}>
<SubComponent
changeAuthenticated={value.changeAuthenticated}
// ...etc

Using react HOC with a form. Passing common JSX and state down using a HOC

I just finished up a pretty big project that uses a form in different parts of a react app. The form is identical throughout however the functionality is different depending on where the form is being used.
So right now I have multiple Components with duplicate forms. All of these forms are controlled i.e (use this.state... for the value) the only difference is the render methods and what happens to the form data on certain button events.
I realize this is horrible coding and really want to use a HOC to make these components way more flexible and cleaner.
this is one of the forms, There are 2 more similar to this.
sampleForm = (
<form action="" name="sample">
<div className="group">
<label htmlFor="">Descriptive Name:</label>
<input type="text" name="name" value={this.state.name}
onChange={this.handleChange} placeholder="Descriptive Name" />
</div>
<div className="group">
<label>Sample Codename:</label>
<input type="text" name="codeName" value={this.state.codeName}
onChange={this.handleChange} placeholder="Ex: MM_MG_01" />
</div>
<div className="group">
<label htmlFor="">GPS Coordinates:</label>
<input type="text" name="coords" value={this.state.coords}
onChange={this.handleChange} placeholder="GPS Coordinates" />
</div>
<div className="group">
<label htmlFor="">Metagenomic Data:</label>
<textarea type="text" name="METAdesc" value=
{this.state.METAdesc}
onChange={this.handleChange} placeholder="Image Description" rows={7} />
<input type="file" name="METAimage"
onChange={this.handleChange} />
</div>
{notes}
</form>
)
I currently have these three duplicated in the render method ( four times :/ )
How can I pass these three down to components?
A Higher Ordered Component is a React pattern where:
a function takes a Component and returns a new Component.
https://reactjs.org/docs/higher-order-components.html
One thing an HOC can do for a Form is to manage state, handling Events such as onChange and onSubmit, etc, etc. As such, consider a Form Component as a functional component that is passed as a parameter to your FormHandler HOC.
For instance,
In FormHandler.js
const withFormHandling = FormComponent => class extends Component {/* Component Logic */}
export default withFormHandling
In Form.js
import withFormHandling from './path/to/Components/FormHandler'
const Form = props => {/* Component Logic */}
export default withFormHanlding(Form);
How then do we handle form specifics, props and state for multiple different forms?
Identify the state or props that every form should have
In your case, perhaps the following:
formAction
formName
handleChange
handleSubmit
inputNames
notes
errors
I would consider passing in inputNames and errors as props (they should match in structure). You can add all sorts of complexity here or keep it simple. Personally, I keep in my state a fields object and an errors object, both with matching keys. One for maintaining user-entered values, the other for storing the results of field validation.
Let's then fill out our HOC
const withFormHandling = FormComponent => class extends Component {
constructor(props) {
super(props)
this.state = {
fields: {...props.inputNames},
errors: {...props.errors},
selectedFile: null
}
this.handleChange = this.handleChange.bind(this);
this.handleFile = this.handleFile.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// https://codeburst.io/save-the-zombies-how-to-add-state-and-lifecycle-methods-to-stateless-react-components-1a996513866d
static get name() {
return Component.name;
}
handleChange(e) {
const target = e.target;
let value = target.type === 'checkbox' ? target.checked : target.value;
let name = target.name;
const fields = {...this.state.fields}, errors = {...this.state.errors};
const error = //create a validation function that returns an error based on field name and value
fields[name] = value;
errors[name] = error;
this.setState({ fields, errors })
}
handleFile(e) {
this.setState({selectedFile: event.target.files[0]})
}
handleSubmit(e) {
//validate form
//flatten fields into structure of data for form submission
//handle submission of data and also file data
}
render() {
return <FormComponent
{...this.props} //to access form specific props not handled by state
fields={this.state.fields}
errors={this.state.errors}
handleChange={this.handleChange}
handleFile={this.handleFile}
handleSubmit={this.handleSubmit}
/>
}
}
export default withFormHandling
This Pattern works because the render function of the returned Component renders the Form Component passed as a parameter to the HOC function.
So, you can create any number of Forms with this HOC as the handler. You can consider passing into the HOC a tree representing the input structure of the form to make this even more modular and reusable.
For now, let's fill out Form.js for the example you provided:
import withFormHandling from './path/to/Components/FormHandler'
const Form = ({formAction, formName, handleChange, handleFile, handleSubmit, fields, errors, notes}) => {
return (
<form action={formAction} name={formName} onSubmit={handleSubmit}>
<div className="group">
<label htmlFor="name">Descriptive Name:</label>
<input type="text" name="name" value={fields.name}
onChange={handleChange} placeholder="Descriptive Name" />
</div>
<div className="group">
<label htmlFor="codeName">Sample Codename:</label>
<input type="text" name="codeName" value={fields.codeName}
onChange={handleChange} placeholder="Ex: MM_MG_01" />
</div>
<div className="group">
<label htmlFor="coords">GPS Coordinates:</label>
<input type="text" name="coords" value={fields.coords}
onChange={handleChange} placeholder="GPS Coordinates" />
</div>
<div className="group">
<label htmlFor="METAdesc">Metagenomic Data:</label>
<textarea type="text" name="METAdesc" value=
{fields.METAdesc}
onChange={handleChange} placeholder="Image Description" rows={7} />
<input type="file" name="METAimage"
onChange={handleFile} />
</div>
{notes}
</form>
)
}
export default withFormHanlding(Form);
Finally, in some other Component, you call the Form Component as often as you like, passing in unique props.
//...some other Component Render Method
// form one, with its own internal state managed by HOC
<Form formAction={'https://someendpoint1'} formName={"some form 1"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 1"}/>
// form two, with its own internal state managed by HOC
<Form formAction={'https://someendpoint2'} formName={"some form 2"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 2"}/>
// form three, with its own internal state managed by HOC
<Form formAction={'https://someendpoint3'} formName={"some form 3"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 3"}/>
This is how I have handled an app with many different forms that have a similar structure.
Another pattern to consider is render props, but I'll leave that up to another question and another respondent.

Categories

Resources