Formik FieldArray - dynamically generate name - reactjs

I'm making a "Primary Caregiver" information form, and there's a button to dynamically add "Emergency Contacts".
Using the <FieldArray name="emergencyContacts" />, is there a way to automatically prefix the name of <Field /> components with the parent's name, and the child's index, so that Formik knows where to update it in values?
Here's my simplified code:
const DEFAULT_CAREGIVER = {
firstName: '',
lastName: '',
};
function ContactInfoForm({ parentName }) {
// I have to prefix the names so that Formik updates the correct values
// I'd like to remove this prefix logic, and hopefully use existing properties:
// "This component is rendered within a <FieldArray name="emergencyContacts" />"
function prefix(name) {
return parentName ? `${parentName}.${name}` : name;
}
return (
<React.Fragment>
<Field
component={TextField}
id="firstName"
label="First Name"
name={prefix('firstName')}
required
/>
<Field
component={TextField}
id="lastName"
label="Last Name"
name={prefix('lastName')}
required
/>
</React.Fragment>
);
}
function CaregiverForm({ name }) {
return (
// I'm hoping to not have to pass the prefix path along
// We have lots of reusable components in this form
<ContactInfoForm parentName={name} />
);
}
class PrimaryCaregiverForm extends React.Component {
renderEmergencyContacts = fieldArray => {
const { values } = this.props;
return (
<React.Fragment>
{values.emergencyContacts.length > 0 &&
values.emergencyContacts.map((contact, index) => (
<div key={index}>
<PageTitle>Emergency Contact {index + 1}</PageTitle>
<CloseButton onClick={() => fieldArray.remove(index)} />
<CaregiverForm
name={`${fieldArray.name}.${index}`}
{...this.props}
/>
</div>
))}
<AddEmergencyContactButton
onClick={() => fieldArray.push(DEFAULT_CAREGIVER)}
/>
</React.Fragment>
);
};
render() {
const { handleSubmit } = this.props;
return (
<Form onSubmit={handleSubmit}>
<PageTitle>Primary Caregiver</PageTitle>
<CaregiverForm {...this.props} />
<FieldArray
name="emergencyContacts"
render={this.renderEmergencyContacts}
/>
<Button type="submit">Save & Continue</Button>
</Form>
);
}
}
const caregiverValidationSchema = {
firstName: Yup.string().required('First name is required.'),
lastName: Yup.string().required('Last name is required.'),
};
const PrimaryCaregiverPage = withFormik({
mapPropsToValues: () => ({
...DEFAULT_CAREGIVER,
emergencyContacts: [],
}),
validationSchema: Yup.object().shape({
...caregiverValidationSchema,
emergencyContacts: Yup.array().of(
Yup.object().shape(caregiverValidationSchema),
),
}),
})(PrimaryCaregiverForm);

Related

React - pass props into input component

What I need is to be able to customize textAreaCount and other props in each separate instance of <Texarea/>. The textAreaCount is different for each <Texarea/> so how do I modify the component to be able to pass in custom textAreaCount for each <Texarea/>?
https://codesandbox.io/s/rkv88-forked-vjo0rn?file=/src/App.js:0-948
import React, { useState } from "react";
const Textarea = (value, id, maxLength, textAreaLimit) => {
const [textAreaCount, ChangeTextAreaCount] = React.useState(0);
const [state, setstate] = useState({
headline: "",
title: ""
});
const { headline, title } = state;
const changevalue = (e) => {
setstate({
...state,
[e.target.name]: value
});
ChangeTextAreaCount(e.target.value.length);
};
return (
<>
<p>{textAreaCount}/{textAreaLimit}</p>
<textarea
type="text"
rows={5}
id={id}
value={value}
maxLength={maxLength}
onChange={(e) => {
changevalue(e);
}}
/>
</>
);
};
export default function FullWidthTabs() {
return (
<div>
<Textarea value={headline} id="test" maxLength={5} textAreaLimit={5}/>
<Textarea value={title} id="test2" maxLength={10} textAreaLimit={10}/>
</div>
);
}
Forward the props you need.
const Textarea = (props) => {
const [textAreaCount, setTextAreaCount] = React.useState(0);
const recalculate = (e) => {
setTextAreaCount(e.target.value.length);
};
return (
<>
<p>{textAreaCount}/5</p>
<textarea type="text" rows={5} maxLength={5} onChange={recalculate} {...props} />
</>
);
};
Now it will forward any props into the textarea element. This will set the id and will overwrite the rows prop.
<Textarea id="textarea-1" rows={4} />
<Textarea id="textarea-2" rows={5} maxLength={10} />
As we can see you try to pass props as below:
<Textarea value={title} id="test2" maxLength={10} textAreaLimit={10}/>
But In Your Textarea Component you received props argument as multiple args as below:
const Textarea = (value, id, maxLength, textAreaLimit) => {
return (
<>
</>
);
};
Instead that you need to destruct your props argument or you can set whole passed value props as single object props as below:
Method 1:
const Textarea = ({value, id, maxLength, textAreaLimit}) => {
return (
<>
<textarea type="text" id={id} value={value} rows={5} maxLength={maxLength} onChange={recalculate} textAreaLimit={textAreaLimit} />
</>
);
};
Method 2:
const Textarea = ({...props}) => {
return (
<>
<textarea type="text" id={id} value={value} rows={5} maxLength={maxLength} onChange={recalculate} textAreaLimit={textAreaLimit} />
</>
);
};
Method 3:
const Textarea = (props) => {
return (
<>
<textarea type="text" id={props.id} value={props.value} rows={5} maxLength={props.maxLength} onChange={recalculate} textAreaLimit={props.textAreaLimit} />
// Or Instead you can do as below
// <textarea type="text" rows={5} maxLength={5} onChange={recalculate} {...props} />
</>
);
};

Textfield onChange not works with react and MobX

I have a textField which are be controlled by a MobX store, but if I enter something to the textfield only on letter will change. Here my code. There is my mistake?
I made my component as observer and my Store is makeAutoObservable inside the constructor.
class SettingsStore {
constructor() {
makeAutoObservable(this)
makePersistable(this, {
name: 'SampleStore',
properties: ['roadmapDescription'],
storage: window.localStorage
})
}
async onChangeRoadmapTitle(name: string): Promise<void> {
setTimeout(() => {
console.log('input', name)
this.roadmapDescription.title = name
console.log(
'this.roadmapDescription.title',
this.roadmapDescription.title
)
}, 50)
}
}
Here is my React Code
const SettingsGeneral: React.FC<ISettingsGeneral> = observer(({ onSubmit }) => {
return (
<div>
<Form<{ title: string; description: string; dataSource: string }>
onSubmit={(data) => onSubmit(data)}
>
{({ formProps = 'test', submitting }) => (
<form {...formProps}>
<FormSection>
<Field
aria-required={true}
name='title'
label='Name of the Roadmap'
isRequired
>
{({ fieldProps }) => (
<Fragment>
<TextField
testId='roadmap-title-text-field'
autoComplete='off'
{...fieldProps}
className='w-48'
onChange={async (
e: React.ChangeEvent<HTMLInputElement>
) =>
await settingsStore.onChangeRoadmapTitle(e.target.value)
}
value={settingsStore.roadmapDescription.title}
/>
</Fragment>
)}
</Field>
</form>
)}
</Form>
</div>
)
})
The mistake was I use value and not defaultVaule
Correct solution is:
<Fragment>
<TextField
testId='roadmap-title-text-field'
autoComplete='off'
{...fieldProps}
className='w-48'
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
settingsStore.onChangeRoadmapTitle(e.target.value)
}
defaultValue={settingsStore.roadmapDescription.title}
/>
</Fragment>

Is it possible to simple-react-code-editor as a Formik field component?

Trying to get this form field component to take a simple-react-code-editor:
not sure if I'm going about this the right way by trying to pass props form the useField hook, but it works for textfield tags, so thought the same method could apply to this as well. Although, I get the feeling the onValueChange callback is different from the onChange callback that this component doesn't have. Is there a way to add it somehow?
Editor Component:
const MyEditor = ({value, onChange}) => {
const highlight = (value) => {
return(
<Highlight {...defaultProps} theme={theme} code={value} language="sql">
{({ tokens, getLineProps, getTokenProps }) => (
<>
{tokens.map((line, i) => (
<div {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span {...getTokenProps({ token, key })} />
))}
</div>
))}
</>
)}
</Highlight>
)
};
return (
<Editor
value={value}
onValueChange={onChange}
highlight={highlight}
padding={'40px'}
style={styles.root}
/>
);
}
export default MyEditor;
Form with Field Component as MyEditor (tried using useField hook):
const FormQueryTextBox = ({...props}) => {
const [field] = useField(props);
return (
<MyEditor onChange={field.onChange} value={field.value}/>
)
}
const validationSchema = yup.object({
query_name: yup
.string()
.required()
.max(50)
});
const AddQueryForm = () => {
return (
<div>
<Formik
validateOnChange={true}
initialValues={{
query:""
}}
validationSchema={validationSchema}
onSubmit={(data, { setSubmitting }) => {
console.log(data);
}}
>
{() => (
<Form>
<div>
<Field
placeholder="query name"
name="query_name"
type="input"
as={TextField}
/>
</div>
<div>
<Field
name="query"
type="input"
as={FormQueryTextBox}
/>
</div>
</Form>
)}
</Formik>
</div>
)
}
components render without errors, but as I type the text doesn't appear.
I figured out that I just need to customize my onChange with the setter from the useFormikContext hook like this:
const FormQueryTextBox = ({...props}) => {
const [field] = useField(props);
const { setFieldValue } = useFormikContext();
return (
<MyEditor {...field} {...props} onChange={val => {
setFieldValue(field.name, val)
}}/>
)
}

How to set pre-field data in initial value of formik form in reactjs

I am creating a Formik form using map function in react js, but I also want to fill the initial data from the state. I am able to it in a static form via setting initial value like the following code
<Formik
initialValues={{
firstName: Data.firstName,
lastName: Data.lastName,
}}
validationSchema={SignupSchema}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
console.log("inside form", values);
this.updateForm(values);
setSubmitting(false);
}, 400);
}}>
So, How can I do it if I am creating my input like the following code
const masterCategory = Object.keys(campaignObj.newMasterCategory).map(item => {
return (
<Field name={`master_category${fc}`} key={fc}>
{({ field }) => <input {...field} className="form-control nopadding-r input-width" type="text" placeholder="Enter Master Category" />}
</Field>
);
});
and rendering it like this
<div className={`col-6 ${this.state.retailflag}`}>
{masterCategory}
</div>
it works for me.
import React, { Component } from "react";
import { Formik, Field } from "formik";
export default class Form extends Component {
formlist = ["username", "password"];
render() {
return (
<Formik
initialValues={{ username: "", password: "" }}
onSubmit={this.props.handleSubmit}
{...this.props}
>
{formikProps => {
return (
<ul>
{this.formlist.map((item, i) => {
return (
<li key={i}>
<Field type="email" name={item} placeholder="Email" />
</li>
);
})}
<li>
<button type="submit" onClick={formikProps.handleSubmit}>
Login
</button>
</li>
</ul>
);
}}
</Formik>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Example => https://codesandbox.io/s/empty-monad-obfvx

Unable to change text box value in React JS

Hi I am working on React JS application. I have assigned default value to textbox on page load. User should be allowed to change this but user is not able to edit the value in textbox. Below is my code.
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextField value="Current" label="LSPL Manual" isEditable="true" />
</InputGroup>
</form>
);
Below is my TextField.js
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = _.uniqueId();
return (
<div className={className}>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{getStatusIcon(state)}
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
Can someone help me to fix this issue? Any help would be appreciated. Thanks
Using Uncontrolled input, you may use defaultValue
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = 1;
return (
<div>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form>
<h2>LSPL (Low Stock Presentation Level)</h2>
<TextField defaultValue="Current" label="LSPL Manual" isEditable="true" />
</form>
);
class Hello extends React.Component {
render() {
return <div><EditStyleFormComponent/></div>;
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
See the fiddle here https://jsfiddle.net/0f6n85ym/
Alternatively you can also do it in controlled input.
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = 1;
return (
<div>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
const EditStyleFormComponent = ({
submitting,
invalid,
value,
onChange
}) => (
<form>
<h2>LSPL (Low Stock Presentation Level)</h2>
<TextField value={value} onChange={onChange} label="LSPL Manual" isEditable="true" />
</form>
);
class Hello extends React.Component {
constructor(props){
super(props);
this.state = {
name: 'Current'
}
}
onChange = (e)=>{
this.setState({name: e.target.value});
}
render() {
return <div><EditStyleFormComponent value={this.state.name} onChange={this.onChange}/></div>;
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
See the fiddle here
https://jsfiddle.net/bshumpy0/
You can use logical OR operator to set defaultValue if value is not provided(if you use controlled input)
Like that:
class App extends Component {
constructor() {
super();
this.state = {
value: ''
};
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<div>
<TextField value={this.state.value} defaultValue='213' onChange={this.handleChange} />
</div>
);
}
}
And in TextField component:
<input type='text' value={value || defaultValue} onChange={onChange}/ >
Full example - https://stackblitz.com/edit/react-4daxck

Resources