I've never used Formik before, but hearing positive things on the net it sounds like this is a great solution compared to rolling own...which I usually try to do.
The docs for Formik have a simple, single instance of some of their attributes, like initialValues and validationSchema. However, I need to make this reusable, since I have 2 versions of this in my app. So i want to pass in the fields as props, then create the initialValues as a form of state. This is OK, yes?
However, nothing renders...the value, errors params shown in the documents, always shows as undefined. Why is this? The state is updated and therefore, I assume the initialValues will update the values object. Scope inside the render method does not allow me to be able to use this.state.initial for example...
Errors object remains undefined, but I thought it should at least exist?
Basically, I have a Parent component that is rending inner components, one of which is a form group. So I am passing the array of fields, and a schema object to the container of Formik component like so:
const newCompanyFields = ["name", "address", "revenue", "phone"];
<Card
headerText="Create New Company"
id="new-company"
renderWith={() => (
<React.Fragment>
<div className="header">Add Company</div>
<Forms fields={newCompanyFields} schema={NewCompanySchema} />
</React.Fragment>
)}
/>
Then, inside the <Forms> component, we will create instance of Formik like so:
class Forms extends Component {
state = {
initial: {}
};
componentDidMount() {
// we need to get the list of fields and setState to be used by Formik below
if (this.props.fields) {
let initialItems = {};
this.props.fields.forEach(item => {
return (initialItems[item] = "");
});
this.setState({ initial: initialItems });
}
}
render() {
return (
<StyledForms>
<Formik
initialValues={this.state.initial}
validationSchema={this.props.schema}
onSubmit={values => {
console.log(values, " submitted");
}}
render={({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
isSubmitting
}) => (
<Form>
<FieldArray
name="field-company"
render={() => (
<div>
{values &&
Object.keys(values).map((field, index) => (
<div key={index}>
<Field name={field} />
{errors[field] && touched[field] ? (
<div>{errors[field]}</div>
) : null}
</div>
))}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</div>
)}
/>
</Form>
)}
/>
</StyledForms>
);
}
Link to Console screenshot: https://screencast.com/t/Pt7YOxU1Oq57
Thank you for clarification.
UPDATE
If I update my initialValues attribute to NOT rely on component state, it works.
// assume ComponentDidMount from above is removed now
const getInitialValues = passedFields => {
let initialItems = {};
passedFields.forEach(item => {
return (initialItems[item] = "");
});
return initialItems;
};
<Formik
initialValues={getInitialValues(this.props.fields)}
...
/>
Is this expected?
You need to use render() in your Formik component.
Something like:
<Formik render={({ values }) => {
<Form>
<FieldArray>
... use values
</FieldArray>
</Form>
}} />
Related
I'm having an odd issue with React and Formik(2.1).
I have a group of checkboxes on my page that the user can check on or off.
The behind-the-scenes part is working...the checkbox values that the user selects are being sent to the API backend and I see the selected values when I write them out to the browser console.
However, the actual checkbox that appears or disappeared inside the little checkbox square never shows.
I'm not sure why either. I tried updating to the latest version of Formik and React, but it doesn't change.
Since the actual values are still being passed, I can't figure out a way to debug it.
Here is the React component that generates the checkboxes:
const PlayerList = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/players',
);
setData(result.data);
};
fetchData();
}, []);
return (
<>
{data.map((item, index) => (
<label key={index}>
<Field id={item.id} key={index} type="checkbox" name="gamePlayers" value={item.id} />
{item.name}
</label>
))}
</>
);
}
export default PlayerList;
This component will generate form inputs that look like this:
<input name="gamePlayers" id="1" type="checkbox" value="1">
And here is the React component with the formik form:
<Formik
initialValues={{
gamePlayers: [],
email: "",
name: "",
phone: ""
}}
onSubmit={async values => {
await new Promise(resolve => setTimeout(resolve, 500));
axios({
method: "POST",
url: "https://localhost:44376/api/gameentry",
data: JSON.stringify(values)
});
console.log(JSON.stringify(values, null, 2));
}}
>
{props => {
const {
values,
touched,
isSubmitting,
handleSubmit,
handleReset,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<div id="gameSection">Game Information</div>
<div id="players">
<label htmlFor="gamePlayers">
Game Player Type (check all that apply)
</label>
<PlayerList />
</div>
);
}}
</Formik>
</div>
);
So everything looks ok, the form looks good. It's just the checkbox animations are not working and I can't think of a way to debug because, technically, it's working.
Has anyone ever seen something like this?
Here is a link to a code sandbox. It's a bit different because I had to modify it to work within the sandbox enviroment.
https://codesandbox.io/s/inspiring-hodgkin-exufn
Thanks!
Implementing a checkbox in Formik can be tricky. you see you have to pass the JSX of input along with its label into the component prop.
Please find my work below.
https://codesandbox.io/s/lingering-https-yw0jl
I'm sure this is a common problem with a well documented solution, but I cannot seem to find the pattern I'm looking for. Here's the situation...
<Mutation variables={formValues}>
{(login, { error, loading }) => (
<Formik onSubmit={login}>
{({ values: formValues, handleSubmit ) =>
<Form method="post">
// form inputs go here
<button type="submit" onClick={handleSubmit}>
Login
</button>
</Form>
}
</Formik>
)}
</Mutation>
Looking at the above code, you can see I have nested render props. But the output of the inner render prop formValues is the input to the outer render prop variables={formValues}. However, this value is obviously not available to the outer render prop.
Questions
What pattern(s) exist to resolve this dependency issue?
Bonus if you can tell me how to do this using something like react-adopt
As you already pointed out, variables can be passed directly to the mutate function instead of passing them to the Mutation component. With regards to react-adopt, according to the docs:
[Y]ou can pass a function instead of a jsx element to your mapper. This function will receive a render prop that will be responsible for your render, the props passed on Composed component, and the previous values from each mapper.
So I expect something like this should work:
const Composed = adopt({
mutation: ({ render }) => (
<Mutation mutation={LOGIN_MUTATION}>
{(login, { loading, data, error }) => render({ login, loading, data, error })}
</Mutation>
),
formik: ({ mutation: { login }, render }) => ( // **EDIT**
<Formik onSubmit={values => login({ variables: values })}>
{render}
</Formik>
)
})
const App = () => (
<Composed>
{({ mutation: { login, data, loading, error }, formik: { values, handleSubmit } }) => (
// ...
)}
</Composed>
)
Answers only Question #1 (may be better solutions)
I found a working solution, though there may be others (better?). I still haven't figured out how to compose the render props in this case. But here's my current solution...
<Mutation>
{(login, { error, loading }) => (
<Formik onSubmit={values => login({ variables: values })}>
{({ values: formValues, handleSubmit ) =>
<Form method="post">
// form inputs go here
<button type="submit" onClick={handleSubmit}>
Login
</button>
</Form>
}
</Formik>
)}
</Mutation>
Notable Changes
I changed <Mutation variables={formValues}> to <Mutation>
I changed onSubmit={login} to onSubmit={values => login({ variables: values })}
I am using React and Formik to handle my forms. In a component, I have a simple input text. That text is handled for error if being empty by formik. The problem is, I also want to asynchronously handle server validation (if the input.target.value already exist in the database).
Formik provides that functionality, but I am obviously doing something wrong because I get the following error.
Cannot read property .then of undefined
Code Sandbox here: https://codesandbox.io/s/nkm2zyy4z0
So far, I have done the following. According to formik documentation:
const asyncValidation = values =>
listItems.then(data => {
const errors ={};
if (data.includes(values.name)) {
errors.name = 'Username already exist';
}
if (!data.include(values.name) {
throw errors;
}
});
I also tried to create another iteration of the asyncValidation promise, as you see below:
const asyncValidation = values =>
new Promise((resolve, reject) => {
const errors = {};
if (listGroups.includes(values.name)) {
console.log(errors)
errors.email = 'Required';
}
if (!listGroups.includes(values.name)) {
console.log(errors)
reject(errors);
} else {
resolve();
}
});
But still, I get an error:
index.jsx:21 Uncaught (in promise) TypeError: Cannot read property 'name' of undefined
Not sure what to do. Name if a property of the values object. If I print on the console, the results, it will print this:
{name: "generalGroup1", description: ""}
description: ""
name: "generalGroup1"
__proto__: Object
Not sure what is wrong here...
Here, formik uses promises to handle this async functionality. I am a little perplex, on what error should be thrown. Maybe I made the mistake here, since I want to basically say. If the value matches something in the database, say that it already exists. If not don't throw any error.
Promises need to throw errors, so what should I do there, and how should I solve my console error.
I am also using a built in service to call the list of items from the API, in order to check against the value being on the input.
export const listItems = () => {
const options = {
method: httpMethod.GET,
url: endpoint.LIST_ITEMS
};
return Instance(options);
};
Below is the part of the component relevant to the input field:
class ItemDetailsForm extends React.Component {
static propTypes = {
...formPropTypes,
data: PropTypes.object
};
handleSubmit = values => {
const { id, onSubmit } = this.props;
onSubmit(id, values);
asyncValidation();
};
render() {
const { data } = this.props;
return (
<Formik
initialValues={{ ...data }}
onSubmit={this.handleSubmit}
validationSchema={validationSchema}
render={({ values, touched, errors, handleChange, handleBlur, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-md-3">
<div className="form-group">
<label htmlFor="itemName">
Item name <span className="text-danger">*</span>
</label>
<input
type="text"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
name="name"
className={classNames('form-control', {
'is-invalid': errors.name && touched.name
})}
id="itemsName"
placeholder="Some Item"
/>
{!!errors.name && touched.name && (
<div className="text-danger">{errors.name}</div>
)}
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</div>
</div>
</form>
)}
/>
);
}
}
export default ItemDetailsForm;
I have followed the formik docs, almost to the teeth, but something is obviously wrong. Can you help a bit. I am relatively new to programming, so if you could explain my mistake it would be great.
I have a wizard that has many forms, at the end of the wizard I want to take them back to the first step. However every form is filled in with the previous values.
I just want to unmount and remount it to wipe everything clean. How do I do this in reactjs?
<StepWizard>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
</StepWizard>
so how would I trigger something to just get "StepWizard" to render in a fresh state?
My components look something like this, I removed code that switches to the next step in the wizard.
export default class NewComponent extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
render() {
return (
<Formik
initialValues={{
name: "",
website: "",
}}
validationSchema={Yup.object().shape({
name: Yup.string().required('Company Name is Required'),
website: Yup.string().url('Company Url is Invalid'),
})}
onSubmit={(
values,
{ setSubmitting, setErrors}
) => {
}}
render={({
values,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched
}) => (
<form onSubmit={handleSubmit}>
<div className="field">
<label className="label">Name</label>
<div className="control">
<input
className="input"
type="text"
name="name"
maxLength="50"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
/>
<ErrorMessage name="name"/>
</div>
</div>
<div className="field">
<label className="label">Website</label>
<div className="control">
<Field className="input" name="website" type="text" />
<ErrorMessage name="website"/>
</div>
</div>
</form>
)}
/>
);
}
}
I am using Mbox State Tree, so I could store something in my store that could be used to trigger whatever needs to be triggered to cause a reset.
Edit
I should mention that I am using this plugin: https://github.com/jcmcneal/react-step-wizard
So I am not sure if stopping a step from rendering is an option, also then that would mean I would have to handle the previous step state everytime.
I am more looking for something that just blows away everything if possible as I spent already too much time on this area and don't want to rework tons.
Highlighting the above methods you can also do something like this. Lift the default state into an object that can be pre-filled by whatever, hydrate it into the state and then when you call a reset you can control how much you reset the state back to. This is a very generic example but it's one way to overcome your issue.
Click here to view a working example
import React from "react";
import ReactDOM from "react-dom";
// generic stage renderer
const Stage = ({ step, currentStep, children }) => {
return step === currentStep ? <div>{children}</div> : null;
};
// generic input controller
const Input = ({ stateKey, value, handleOnChange }) => (
<input
value={value}
onChange={evt => handleOnChange(stateKey, evt.target.value)}
/>
);
// default state that is used to reference
const defaultState = {
firstName: '',
lastName: '',
// default state can also be prefilled with data..
telephoneNumber: '0123456789',
}
class App extends React.Component {
state = {
step: 1,
...defaultState
};
handleStateUpdate = (key, value) => {
this.setState({
[key]: value
});
};
incrementStep = () => {
if (this.state.step < 3) {
this.setState({
step: this.state.step + 1
});
}
};
goBack = () => {
const { step, lastName, telephoneNumber } = this.state;
this.setState({
step: 1,
// always reset this one
firstName: defaultState.firstName,
// only reset this one if it's step 3
lastName: step > 2
? defaultState.lastName
: lastName,
// last step blargh, let's reset anyway
telephoneNumber: step === 3
? defaultState.telephoneNumber
: telephoneNumber,
});
}
render() {
const { step, firstName, lastName, telephoneNumber } = this.state;
return (
<div>
{JSON.stringify(this.state)}
<h1>Step Wizard - {step}</h1>
<Stage step={1} currentStep={step}>
<Input
stateKey="firstName"
value={firstName}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<Stage step={2} currentStep={step}>
<Input
stateKey="lastName"
value={lastName}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<Stage step={3} currentStep={step}>
<Input
stateKey="telephoneNumber"
value={telephoneNumber}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<button onClick={this.goBack}>Go Back to Step 1</button>
<button onClick={this.incrementStep}>Next</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can achieve that by storing the current state of the wizard in, you guessed it, state object. That state and actions to mutate it can be passed to children as props. After that, when you need to reset the wizard, you just reset the state.
Here's an oversimplified example:
class StepWizard extends React.Component {
constructor(props) {
super(props);
this.state = {
step1: {},
step2: {}
};
}
setStep(step, data) {
this.setState({ `step${ step }`: data });
}
resetWizard() {
this.setState({
step1: {},
step2: {}
});
}
render() {
return (
<React.Fragment>
<Step
data={ this.state.step1 }
setData={ (data)=> this.setStep(1, data) }
/>
<Step
data={ this.state.step2 }
setData={ (data)=> this.setStep(2, data) }
/>
</React.Fragment>
);
}
}
Now, call resetWizard whenever you'll need to reset the wizard.
How about creating a Step object that would have the render logic for each step? I understand your use case correctly, since you would want to render only one step at a time why not only render which is relevant at that particular step?
Something like below.
class Wizard {
constructor(props) {
super(props);
this.stepMap = {
first: <FirstStep />,
second: <SecondtStep />,
third: <ThirdStep />,
fourth: <FourthStep />
}
this.state = {
activeStep: "first"
}
}
changeStep = (stepId) => {
this.setState({activeStep: stepId});
}
render() {
const activeStepCmp = this.stepMap[this.state.activeStep];
return (
<StepWizard>
{activeStepCmp}
</StepWizard>
)
}
}
I need to create a redux-form that loads data into a modal with initial values. One of the fields( quoteField ) should render with readOnly when there is initial data. There will also be at least one quote in the data(initialValue) since that is a requirement.
But clicking a new quote, fields.push({})} , to add to the form should be free text (not readonly)
I have tried using readOnly={pristine}, readOnly={pristine && initial} etc ... in the component. This works until i add another field; fields.push({})} OR if i click inside another form element. This action causes the quote field (with the initial value) to be editable again. It should stay readOnly.
I gave tried different variations of the meta:objects but none seems to give the functionality i need.
Everything works fine except this issue
// removed imports
class EditReduxForm extends React.Component {
constructor(props) {
super(props);
// removed handlers and state
}
// removed function for submitting
render() {
const { valueForPRI } = this.state;
const {fields:{addingName, quoteField}, array: { push }, handleSubmit, addCustom, onNewRequest, form, input, valid, reset, pristine, submitting } = this.props;
return (
<span>
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<Field name="addingName" component={formElement.renderName} value={valueForPRI} >{this.menuItemsPRI(valueForPRI)}</Field>
<FieldArray name="quoteField" component={formElement.renderEditQuote} /> // ***** <= this element ******
{actions} <span className="require"> <span className="ast">*</span> required field</span>
<br/>
</form>
</span>
);
}
}//end edit class
EditReduxForm = reduxForm({
form: 'editForm',
fields: ['addingName', 'quoteField[]'],
enableReinitialize: true,
validate,
})(EditReduxForm);
EditReduxForm = connect(
state => ({
initialValues: {
addingName: state.loadFormValues.addingName,
quoteField : state.loadFormValues.quoteField,
}
})
)(EditReduxForm )
export default EditReduxForm;
and the form fields are pulled in from another file.
// removed imports
export const renderName = ({ field, input, label, type, meta: { touched, error, pristine}, children, ...custom }) => (
<ul>
<li>
<Field
{...input}
component ={SelectField}
onChange ={(event, index, value) => input.onChange(value)}
children ={children}
className ="priorityAutoCompleter"
id ="addingPriority"
errorText ={touched && error }
{...custom}
/>
</li>
</ul>
)
const renderEditQuote = ({ fields, field, input, value, label, type, meta: { pure, touched, error, pristine, dirty, initial}, children, ...custom, }) => (
<ul>
<li>
<label htmlFor="quoteField">Quote(s)</label>
<IconButton
tooltipPosition ="top-right"
onTouchTap ={() => fields.push({})}
</IconButton>
</li>
{fields.map((quote, index) =>
<ul className="quoteList" key={index}>
<IconButton
type ="button"
tooltip ="remove"
tooltipPosition ="top-left"
className ="floatRight marginRight"
onTouchTap ={() => fields.remove(index)}
</IconButton>
<li className="quoteListLi">
<Field
{...input}
component ={TextField}
id ="addingQuote"
name ={`${quote}.addingQuote`}
type ="text"
multiLine
errorText ={touched && error }
children ={children}
{...custom}
readOnly ={ ????? } // // ***<= this element ***
/>
<span className="error">{error }</span>
</li>
<li>
<Field
{...input}
component ={TextField}
name ={`${quote}.addingLabel`}
id ="addingLabel"
type ="text"
onKeyDown ={(event) => {if (keycode(event) === 'enter') { event.preventDefault() }} }
errorText ={touched && error}
readOnly ={ ????? } // // ** <= this element **
/>
<span className="error">{error }</span>
<Field
{...input}
component ={TextField}
name ={`${quote}.addingSource`}
id ="addingSource"
type ="text"
onKeyDown ={(event) => {if (keycode(event) === 'enter') {event.preventDefault() }} }
errorText ={touched && error }
readOnly ={ ????? } // *** <= this element **
/>
<span className="error">{error }</span>
</li>
</ul>
)}
</ul>
)
For something like this, you can try to force Redux-Form to do the work for you, but I'd suggest taking over yourself.
Since the logic you describe doesn't need to be related to the overall application state, and just the component state, I'd recommend going down that route.
What I mean - you can pass an onChange handler to your onTouchTap and disable or enable editing in your field and utilize local component state for this, something like this.setState({canEdit : true}) when the onTouchTap gets executed.
We had a similar requirement wherein the key field (unlike other fields) has to be written & persisted (to back-end) only once. However it can be edited any number of times between: "array push() API and saving to backend". Once saved into backend it becomes readOnly/disabled field. We accomplished it with something like below:
const TextCell = ({
input,
label,
meta: { touched, error, visited },
elStyle,
writeOnce,
...custom
}) =>
<TextField
style={{ width: "100%" }}
hintText={label}
disabled={!visited && writeOnce && !!input.value} // ** This works for us **
errorText={touched && error}
{...input}
{...custom}
/>
The above component will be initialized as below:
<Field
name={`${rowData}.${fieldName}`}
component={TextCell}
label={title}
writeOnce={true}
/>