I am using Formik form in my React application to create a new post and I have an input field of type "file". The problem I face now is I cannot implement a cancelation of the action. With the code I am proving I do update the form values and the preview image disappears but I still can see the name of the file on the screen which should be "no file chosen" instead. I went through Formik documentation but did not find a solution. Any ideas will help. Thank you.
Setting value={values.image} gives these errors:
Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string
A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
import { Formik, Field } from "formik";
import { createPost } from "../actions";
const NewPost = () => {
const dispatch = useDispatch();
const history = useHistory();
const handleImageUpload = async (event, setFieldValue) => {
// .... some code where I obtain the image URLs
setFieldValue("image", file.secure_url);
setFieldValue("largeImage", file.eager[0].secure_url);
};
return (
<div>
<Formik
initialValues={{ image: "", largeImage: "", title: "", body: "" }}
validate={(values) => {
const errors = {};
//... validation
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
const { title, body, image, largeImage } = values;
dispatch(
createPost(
{
title,
body,
image,
largeImage,
},
history
)
);
setSubmitting(false);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
isSubmitting,
/* and other goodies */
}) => (
<form onSubmit={handleSubmit} className="new__post-form">
<div>
<label htmlFor="image">Image</label>
<div>
<div>
<label htmlFor="image">Upload File</label>
<input
type="file"
type="file"
name="image"
id="image"
placeholder="Upload an image"
onChange={(e) => handleImageUpload(e, setFieldValue)}
onBlur={handleBlur}
/>
</div>
<div>
{values.image && (
<>
<img src={values.image} alt="Upload Preview" />
<button
type="button"
onClick={() => {
setFieldValue("image", "");
setFieldValue("largeImage", "");
}}
>
X
</button>
</>
)}
</div>
</div>
</div>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
</Formik>
</div>
);
};
export default NewPost;
Related
I am new to react and I have just started using Formik
I like how simple it makes making forms and handling forms in react.
I have created multiple custom fields using formik, I am putting the react-select field I created as an example here.
import { ErrorMessage, Field } from "formik";
import React from "react";
import Select from 'react-select'
const SelectInput = (props) => {
const { label, name, id,options, required, ...rest } = props;
const defaultOptions = [
{label : `Select ${label}`,value : ''}
]
const selectedOptions = options ? [...defaultOptions,...options] : defaultOptions
return (
<div className="mt-3">
<label htmlFor={id ? id : name}>
{label} {required && <span className="text-rose-500">*</span>}
</label>
<Field
// className="w-full"
name={name}
id={id ? id : name}
>
{(props) => {
return (
<Select
options={selectedOptions}
onChange={(val) => {
props.form.setFieldValue(name, val ? val.value : null);
}}
onClick = {(e)=>{e.stopPropagation}}
{...rest}
// I want someting like onReset here
></Select>
);
}}
</Field>
<ErrorMessage
name={name}
component="div"
className="text-xs mt-1 text-rose-500"
/>
</div>
);
};
export default SelectInput;
This is the usual code I use for submitting form as you can see I am using resetForm() method that is provided by formik, I want to attach the reseting logic in on submit method itself.
const onSubmit = async (values, onSubmitProps) => {
try {
//send request to api
onSubmitProps.resetForm()
} catch (error) {
console.log(error.response.data);
}
};
If you want to reset the selected value after the form is submitted, you need to provide a controlled value for the Select component.
The Formik Field component provides the value in the props object, so you can use it.
For example:
SelectInput.js
import { ErrorMessage, Field } from 'formik';
import React from 'react';
import Select from 'react-select';
const SelectInput = ({ label, name, id, options, required, ...rest }) => {
const defaultOptions = [{ label: `Select ${label}`, value: '' }];
const selectedOptions = options ? [...defaultOptions, ...options] : defaultOptions;
return (
<div className='mt-3'>
<label htmlFor={id ? id : name}>
{label} {required && <span className='text-rose-500'>*</span>}
</label>
<Field
// className="w-full"
name={name}
id={id ? id : name}
>
{({
field: { value },
form: { setFieldValue },
}) => {
return (
<Select
{...rest}
options={selectedOptions}
onChange={(val) => setFieldValue(name, val ? val : null)}
onClick={(e) => e.stopPropagation()}
value={value}
/>
);
}}
</Field>
<ErrorMessage name={name} component='div' className='text-xs mt-1 text-rose-500' />
</div>
);
};
export default SelectInput;
and Form.js
import { Formik, Form } from 'formik';
import SelectInput from './SelectInput';
function App() {
return (
<Formik
initialValues={{
firstName: '',
}}
onSubmit={async (values, { resetForm }) => {
console.log({ values });
resetForm();
}}
>
<Form>
<SelectInput
name='firstName'
label='First Name'
options={[{ label: 'Sam', value: 'Sam' }]}
/>
<button type='submit'>Submit</button>
</Form>
</Formik>
);
}
export default App;
Therefore, if you click the Submit button, value in the Select component will be reset.
You can also make a useRef hook to the Fromik component and then reset the form within the reset function without adding it as a parameter to the function.
https://www.w3schools.com/react/react_useref.asp
It's one of the really nice hooks you'll learn as you progress through React :)
So if I understood you correctly you want to reset a specif field value onSubmit rather than resetting the whole form, that's exactly what you can achieve using actions.resetForm().
Note: If nextState is specified, Formik will set nextState.values as the new "initial state" and use the related values of nextState to update the form's initialValues as well as initialTouched, initialStatus, initialErrors. This is useful for altering the initial state (i.e. "base") of the form after changes have been made.
You can check this in more detail here.
And here is an example of resetting a specific field using resetForm() whereby you can see as you input name, email and upon submit only email field will get empty using resetForm.
import "./styles.css";
import React from "react";
import { Formik } from "formik";
const initialState = {
name: "",
email: ""
};
const App = () => (
<div>
<h1>My Form</h1>
<Formik
initialValues={initialState}
onSubmit={(values, actions) => {
console.log(values, "values");
actions.resetForm({
values: {
email: initialState.email
}
});
}}
>
{(props) => (
<form onSubmit={props.handleSubmit}>
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.name}
name="name"
/>
<br />
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.email}
name="email"
/>
<br />
<br />
{props.errors.name && <div id="feedback">{props.errors.name}</div>}
<button type="submit">Submit</button>
</form>
)}
</Formik>
</div>
);
export default App;
Is it possible to substitute the value from useState with the one coming from input?
Or is there a way to do this using dispatch?
I have tried many ways, but none of them work.
const renderInput = ({
input,
label,
type,
meta: { asyncValidating, touched, error },
}) => {
const [value, setValue] = useState('default state');
const onChange = event => {
setValue(event.target.value);
// + some logic here
};
return (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} value={value} onChange={onChange} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
};
const SelectingFormValuesForm = props => {
const { type, handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name</label>
<div>
<Field
name="name"
component={renderInput}
type="text"
placeholder="Dish name..."
/>
</div>
</div>
</form>
);
};
SelectingFormValuesForm = reduxForm({
form: 'selectingFormValues',
validate,
asyncValidate,
// asyncBlurFields: ['name'],
})(SelectingFormValuesForm);
export default SelectingFormValuesForm;
This way, unfortunately, the value sent to the submit remains empty.
I am using React Hook Form. I've made a custom checkbox which looks like this:
const Checkbox = ({ text, className, setCheckbox, checkbox }) => {
const { register } = useFormContext();
const statute = register("statute");
return (
<Wrapper className={className}>
<StyledLabel>
<div>{text}</div>
<StyledInput
type="checkbox"
name="statute"
onChange={(e) => {
statute.onChange(e);
setCheckbox && setCheckbox(!checkbox);
}}
/>
<Checkmark />
</StyledLabel>
</Wrapper>
);
};
Checkbox (Checkbox.js) is nested in a parent component (Register.js):
const Register = ({ setRegisterView }) => {
const validationSchema = Yup.object().shape({
statute: Yup.bool().oneOf([true], "You need to accept the page statute."),
});
const methods = useForm({
mode: "onSubmit",
resolver: yupResolver(validationSchema),
});
const {
register: validate,
formState: { errors },
handleSubmit,
} = methods;
return (
<Wrapper>
<FormProvider {...methods}>
<Form onSubmit={handleSubmit(registerProcess)}>
<StyledCheckbox
text="Accept the page statute."
setCheckbox={null}
checkbox={null}
/>
{errors.statute && <Error>{errors.statute.message}</Error>}
<LoginButton type="submit">SIGN UP</LoginButton>
</Form>
</FormProvider>
</Wrapper>
);
};
export default Register;
The problem is that when I check the checkbox I get an information in errors.statute.message: statute must be a `boolean` type, but the final value was: `"on"`..
When I change this:
onChange={(e) => {
statute.onChange(e);
setCheckbox && setCheckbox(!checkbox);
}}
to this:
{...register("statute")}
then it works great and errors.statute.message shows correct message just when checked=false in checkbox input. But I need to have the extended version of onChange.
The problem is, that you not link the returned ref of the register call to your <StyledInput />. So just spread the return value of register - in the code example below i also omitted the name prop as it is included in statute. Here you can find all the props register will return.
<StyledInput
type="checkbox"
{...statute}
onChange={e => {
statute.onChange(e);
setCheckbox && setCheckbox(!checkbox);
}}
/>
I am working on a login form where each input is created dynamically as a field.
This is my Login.js file:
import _ from 'lodash';
import React, { Component } from 'react';
import {reduxForm, Field } from 'redux-form';
import{ Link } from 'react-router-dom';
import FIELDS from './loginFields';
import LoginField from './LoginField'
import { connect } from 'react-redux';
import * as actions from '../../actions'
class LoginForm extends Component {
constructor(){
super();
this.state={
username: '',
password: ''
};
};
handleChange = (e)=>{
this.setState({username: e.target.value, password: e.target.value});
};
renderFields(){
return _.map(FIELDS, ({ label, name, type })=> {
return <Field onChange={this.handleChange} className='purple-text' key={name} component={ LoginField } type={type} label={label} name={name} />
});
};
render(){
const { username, password } = this.state;
const isEnabled = username.length > 0 && password.lenth>7;
return (
<div className='valign-wrapper row login-box' style={{marginTop:'100px'}}>
<div className='col card hoverable s10 pull-s1 m6 pull-m3 l4 pull-l4'>
<form method='POST' action='/api/login'>
<div className = 'card-content'>
<span className='card-title purple-text' style={{textAlign:'center'}}>Login<a href='/register'> Not a member? sign up!</a></span>
<div className='center-align row'>
<li key='google' style={{marginLeft: '30px'}} className='col m6 center-align white-text darken-3'><a className='white-text' href='/auth/google'><img alt="" src="https://img.icons8.com/cute-clipart/64/000000/google-logo.png"/></a></li>
<li key='facebook' className='col center-align white-text darken-3'><a className='white-text' href='/auth/facebook'><img alt = "" src="https://img.icons8.com/cute-clipart/64/000000/facebook-new.png"/></a></li>
</div>
<div className='row input-field col s12'>
{this.renderFields()}
<Link to='/' className='purple btn-flat left white-text'>Back</Link>
<button disabled={!isEnabled} type='submit' className='purple btn-flat right white-text'>Login
<i className='material-icons right'>done</i>
</button>
</div>
</div>
</form>
</div>
</div>
);
};
};
function validate(values){
const errors = {};
_.each(FIELDS, ({name})=>{
if(!values[name]){
errors[name] = 'You must enter a value!'
}
});
return errors;
};
const form = reduxForm({
validate,
form: 'LoginForm'
});
export default connect(null, actions)(form(LoginForm));
Here is loginFields.js
export default
[
{ label: 'Username', name: 'username', type: 'text'},
{ label: 'Password', name: 'password', type: 'password'}
];
and here is LoginField.js
import React from 'react';
export default ({ input, label, type, meta })=>{
return(
<div>
<label className='purple-text'>{label}</label>
<input {...input} type={type} style= {{marginBottom: '5px'}}/>
<div className = "red-text" style={{ marginBottom: '20px'}}>
{meta.touched && meta.error}
</div>
</div>
);
};
I am having trouble properly setting onChange and my constructor to disable the login button until all fields are filled. I have been able to disable the button until a single input has started to be filled in, not disabled at all, and not enabled at all. but have not been able to achieve the desired outcome.
I have tried using lodash to map over each field grabbing values by the input name property, and moving functions around.
Any help would be greatly appreciated, if i can provide any more information for this question please let me know.
The initial problem I see is the onChange function will update state for both password and username whenever either of them is changed. The function takes the event and does not distinguish as to which input is the target. You can pass an additional parameter from the Field that includes the field name, or you can check the target's id or something so you know which input's state should be updated.
In LoginForm
handleChange = (e, name)=>{
this.setState({[name]: e.target.value});
};
You also need to pass the onChange callback down to the actual input in LoginField.js
import React from 'react';
export default ({ name, label, type, meta, onChange, ...props })=>{
return(
<div>
<label className='purple-text'>{label}</label>
<input onChange={(e) => onChange(e, name)} {...props} type={type} style= {{marginBottom: '5px'}}/>
<div className = "red-text" style={{ marginBottom: '20px'}}>
{meta.touched && meta.error}
</div>
</div>
);
};
Here's a codeSandbox.
just adding this as an answer in case anyone else comes across this issue.
after tons of digging I finally found documentation. in redux form has a built in prop called {invalid} which checks against the validate function. instead of messing with state all i had to do was add
const {invalid} = this.props;
inside the render method. constructor and handle change and onChange were no longer necessary.. then.
<button disabled={invalid}>
I am trying to use redux-form to generate a quiz form. My data source for an individual redux-form field component comes from an array - questions in my case. Everything works as expected except validation. Any thoughts how this can be fixed?
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { Input, Button } from 'reactstrap';
const validate = values => {
const errors = {};
if (!values.question) { // this is just an example of what I am trying to do, validation does not work
errors.question = 'Required';
} else if (values.question.length < 15) {
errors.question = 'Must be 15 characters or more';
}
return errors;
};
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<Input {...input} type={type} />
{touched && (error && <span>{error}</span>)}
</div>
</div>
);
const renderQuestions = questions => {
return questions.map(question => {
return (
<Field key={question.id} name={question.prompt} type="textarea" component={renderField} label={question.prompt} />
);
});
};
const QuizStepForm = props => {
const { handleSubmit, pristine, reset, submitting, questions } = props;
return (
<form onSubmit={handleSubmit}>
<Field name="username" type="textarea" component={renderField} label="username" />
{renderQuestions(questions)}
<div>
<br />
<Button color="primary" style={{ margin: '10px' }} type="submit" disabled={submitting}>
Submit
</Button>
<Button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</Button>
</div>
</form>
);
};
export default reduxForm({
form: 'quizStepForm',
validate
})(QuizStepForm);
Your validation function assumes there is one field named "question." But your code creates a set of fields whose name is set by {question.prompt}. If you stick with this implementation, your validation code will need to know about all the question.prompt array values and check values[question.prompt] for each one, then set errors[question.prompt] for any failures. That would probably work, though it seems like a suboptimal design.
This might be a good use case for a FieldArray. In FieldArrays, the validation function is called for you on each field; your validation code doesn't have to know the names of all the fields.