React Formik props isSubmitted with react-query do not take affect - formik

I have my Formik form below (I'm new with formik). An this is the simple form which have two fields and makes api call using react-query (react -query works well).
But the problem is that formik state isSubmitting do not take effect when click. (Button do not start spinning, but if it is direct true, it works well)
I wonder what i'm missing? Why it has no effect? and How it could be solved?
Api Call:
const createUser = async ({name, password}: Record<string, string>) => {
return await axiosInstance.post(`users/create_user/`, {
name: name,
password: password,
});
};
React-query Mutation:
const createNewUser = useMutation(createUser, {
onSuccess: () => {
queryClient.refetchQueries(["getAllUsers"])
},
onError: (newUser) => {
console.log("onError", newUser);
}
Formik Form:
<Formik
initialValues={{ username: username, password: password }}
validateOnChange={false}
validateOnBlur={false}
onSubmit={(values, {setSubmitting, resetForm }) => {
setSubmitting(true)
createNewUser.mutate({
name: values.username,
password: values.password,
});
setSubmitting(false);
resetForm();
}}
>
{({ values, errors, isSubmitting, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label
htmlFor="Имя"
className="block text-900 font-medium mb-2"
>
</label>
<Field
type="text"
name="username"
className="w-full mb-3"
as={InputText}
/>
<Field
type="text"
name="password"
className="w-full mb-3"
as={InputText}
/>
<Button
label="save new user"
icon="pi pi-user"
className="w-full"
type="submit"
loading={isSubmitting} // to not take effect? why?
/>
</div>
</form>

Try using setTimeout, to setSubmitting(false) after few seconds not immediately like this:
setTimeout(() => setSubmitting(false), 2000);
Or, check if mutation is successfully executed, then setSubmitting to false
if(createNewUser.status == 'success' || createNewUser.status == 'error') {
setSubmitting(false);
}
Alternatively, without using formik isSubmitting you can use
createNewUser.isLoading
<Button
label="save new user"
icon="pi pi-user"
className="w-full"
type="submit"
loading={createNewUser.isLoading}
/>

Related

Retriving data for input radio won't make a default selection

Goal:
Retrieve data 'smartphones', by API, and apply it as a default selection in the input radio component for the react hook form.
Problem:
The code do not work that makes smartphones as a preselection in the input radio button.
However, if I make a change by using hard coding, it works but hard coding do not solve the case.
I don't know how to solve it.
Info:
*Using React TS and React hook form.
*Newbie in react TS and hook form.
Stackblitz:
https://stackblitz.com/edit/react-ts-z9cnzl
Thank you!
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import './style.css';
type FormValues = {
lastName: string;
favShow: string;
};
export default function App() {
const [category, setCategory] = useState('');
React.useEffect(() => {
async function FetchData() {
var data = await fetch('https://dummyjson.com/products/1').then((res) => {
return res.json();
});
console.log(data.category);
setCategory(data.category);
}
FetchData();
}, []);
const [data, setData] = useState(null);
const { register, handleSubmit } = useForm<FormValues>({
defaultValues: {
lastName: 'asaaaaaaf',
favShow: category,
//favShow: 'smartphones',
},
});
const onSubmit = (data) => {
setData(data);
console.log('asdf');
};
return (
<React.Fragment>
<form onSubmit={handleSubmit(onSubmit)} className="form-container">
<h1 className="form-heading">React Hook Form Example</h1>
<input
{...register('lastName', { required: true })}
className="form-control"
type="text"
placeholder="Last name"
maxLength={15}
name="lastName"
/>
<br />
<br />
<label htmlFor="ted-lasso">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="smartphones"
id="smartphones"
/>{' '}
smartphones
</label>
<label htmlFor="got">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="GOT"
id="got"
/>
GOT
</label>
<br />
<br />
<button className="submit-btn" type="submit">
Submit
</button>
</form>
{data && <p className="submit-result">{JSON.stringify(data)}</p>}
</React.Fragment>
);
}
I got some help and the solution is:
Stackblitz:
https://stackblitz.com/edit/react-ts-m2s6ev?file=index.tsx
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import './style.css';
type FormValues = {
lastName: string;
favShow: string;
};
export default function App() {
const [category2, setCategory2] = useState();
const [data, setData] = useState(null);
const { register, handleSubmit, resetField } = useForm<FormValues>({
defaultValues: {
lastName: '',
favShow: '',
},
});
React.useEffect(() => {
async function FetchData() {
var data = await fetch('https://dummyjson.com/products/1').then((res) => {
return res.json();
});
setCategory2(data);
}
FetchData();
}, []);
React.useEffect(() => {
if (category2) {
const obj = JSON.parse(JSON.stringify(category2));
console.log(obj);
resetField('lastName', { defaultValue: obj.discountPercentage });
resetField('favShow', { defaultValue: obj.category });
}
}, [resetField, category2]);
const onSubmit = (data) => {
setData(data);
};
return (
<React.Fragment>
<form onSubmit={handleSubmit(onSubmit)} className="form-container">
<h1 className="form-heading">React Hook Form Example</h1>
<input
{...register('lastName', { required: true })}
className="form-control"
type="text"
placeholder="Last name"
maxLength={15}
name="lastName"
/>
<br />
<br />
<label htmlFor="ted-lasso">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="smartphones"
id="smartphones"
/>{' '}
smartphones
</label>
<label htmlFor="got">
<input
{...register('favShow', { required: true })}
type="radio"
name="favShow"
value="GOT"
id="got"
/>
GOT
</label>
<br />
<br />
<button className="submit-btn" type="submit">
Submit
</button>
</form>
{data && <p className="submit-result">{JSON.stringify(data)}</p>}
</React.Fragment>
);
}

How to fix Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'status') at app.js

I had the following code and everything was working as expected.
<template>
<div id="login">
<form #submit.prevent="submit">
<p class="loading" :class="{ hideLoad: !loading }">Loading...</p>
<label for="email">Email</label>
<input type="text" id="email" v-model="fields.email" />
<span v-if="errors && errors.email" class="error">{{
errors.email[0]
}}</span>
<label for="password">Password</label>
<input type="password" id="password" v-model="fields.password" />
<span v-if="errors && errors.password" class="error">{{
errors.password[0]
}}</span>
<button type="submit">Log In</button>
<span
>Don't have an account?
<router-link :to="{ name: 'Register' }">Sign Up</router-link></span
>
</form>
</div>
</template>
<script>
export default {
data() {
return {
fields: {},
errors: {},
loading: false,
};
},
methods: {
submit() {
this.loading = true;
axios
.post("/api/login", this.fields)
.then((res) => {
if (res.status == 201) {
localStorage.setItem("authenticated", "true");
this.$router.push({ name: "Dashboard" });
}
})
.catch((error) => {
if (error.response.status == 422) {
this.errors = error.response.data.errors;
this.loading = false;
}
});
},
},
};
</script>
I then decided to use pinia to manage state and came up with the following.
<template>
<div id="login">
<form #submit.prevent="loginStore.submit">
<p class="loading" :class="{ hideLoad: !loginStore.loading }">Loading...</p>
<label for="email">Email</label>
<input type="text" id="email" v-model="loginStore.fields.email" />
<span v-if="loginStore.errors && loginStore.errors.email" class="error">{{
loginStore.errors.email[0]
}}</span>
<label for="password">Password</label>
<input type="password" id="password" v-model="loginStore.fields.password" />
<span v-if="loginStore.errors && loginStore.errors.password" class="error">{{
loginStore.errors.password[0]
}}</span>
<button type="submit">Log In 2</button>
<span
>Don't have an account?
<router-link :to="{ name: 'Register' }">Sign Up</router-link></span
>
</form>
</div>
</template>
<script setup>
import {useLoginStore} from '../stores/login'
const loginStore = useLoginStore();
</script>
My login.js
import { defineStore } from 'pinia'
export const useLoginStore = defineStore('login', {
state: () => {
return {
fields: {},
errors: {},
loading: false,
}
},
actions: {
submit() {
this.loading = true;
axios
.post("/api/login", this.fields)
.then((res) => {
if (res.status == 201) {
localStorage.setItem("authenticated", "true");
this.$router.push({ name: "Dashboard" });
}
})
.catch((error) => {
if (error.response.status == 422) {
this.errors = error.response.data.errors;
this.loading = false;
}
});
},
},
})
Everything is working as before and the only error is that when I fill the form with the correct credential and submit then I get Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'status') at app.js
I've tried all I can and I just can't figure out what it is that I'm doing wrong.
After debugging it further, I realized the error was coming from this.$routerinstance since a user gets login successfully and the status code is okay when I console log.
The instance is available in components only https://router.vuejs.org/api/#component-injected-properties
So, I fixed the problem by importing router in the store.js file,
import router from '../router'
then I pushed to another route by just using
router.push({ name: "Dashboard" });
instead of this.$router.push({ name: "Dashboard" });

Unable to render redux form select input choices dynamically

I am trying to populate a select menu in redux forms dynamically.
I've been using the debugging tools in chrome and can see that the 'departments' variable sees the array list
({departments.map(department => <option key={department} value={department}>{department}</option>)}
but the final choice list isn't populating. I'm guessing it has something to do with the renderSelectField function, but I'm not sure what I am overlooking?
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import isValidEmail from 'sane-email-validation';
class SimpleReduxForm extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.renderSelectField = this.renderSelectField.bind(this);
}
onSubmit = async (data) => {
try {
let response = await fetch('/api/getRecords', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-type': 'application/json'
}
});
let responseJson = await response.json();
//display success message to user
alert('Form successfully submitted')
return responseJson;
//reset form
} catch (error) {
alert(error);
}
}
renderInputField(field) {
return (
<div className="form-group">
<label htmlFor={field.input.name}>{field.label}</label>
<div className="field">
<input placeholder={field.label} {...field.input} className="form-control" type={field.input.type} />
</div>
</div>
)
}
renderSelectField(field) {
return (
<div className="form-group">
<label htmlFor={field.input.name}>{field.label}</label>
<div className="field">
<select {...field.input}
className="form-control"
defaultselection={field.defaultSelection}
><option>{field.defaultselection}</option></select>
</div>
</div>
)
}
render() {
const { handleSubmit, pristine, reset, submitting, invalid } = this.props;
//Options for select - this should be an AJAX call to a table to get options list
const departments = ["Dept 1", "Dept 2", "Dept 3"]
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
<Field
label="Username"
name="username"
component={this.renderInputField}
type="text"
/>
<Field
label="Email"
name="email"
component={this.renderInputField}
type="email"
/>
<Field
label="Age"
name="num_field"
component={this.renderInputField}
type="text"
/>
<Field
label="Department"
name="department"
defaultselection="Select..."
component={this.renderSelectField}>
{departments.map(department => <option key={department} value={department}>{department}</option>)}
</Field>
<div>
<button type="submit" className="btn btn-primary" disabled={pristine || submitting}>Submit</button>
<button type="button" className="btn btn-warning" disabled={pristine || submitting} onClick={reset}> Clear Values </button>
</div>
</form >
)
}
}
//Validate Errors Before Submission
const validate = (values) => {
//create errors object
const errors = {}
/*Example showing to check is a field contains data
* if no, submission == invalid*/
if (!values.username) {
errors.username = 'Required!'
}
/*check to see if email is provided and that submission is an actual email address*/
if (!values.email) {
errors.email = 'Required!'
} else if (!isValidEmail(values.email)) {
errors.email = 'Invalid Email!'
}
/* Example to show that the length of a field
* can be checked as part of validation */
if (values.num_field < 2) {
errors.num_field = "Must be at least 10 years old"
}
return errors
}
const mapStateToProps = state => ({
SimpleReduxForm: state.form.SimpleReduxForm
});
export default reduxForm({
validate,
form: 'SimpleReduxForm',
enableReinitialize: true,
keepDirtyOnReinitialize: true,
})(connect(mapStateToProps)(SimpleReduxForm));
I figured it out. Just in case anyone else runs into this issue. I needed to add {field.children} into the renderSelectField function. So the final function looks like:
renderSelectField(field) {
return (
<div className="form-group">
<label htmlFor={field.input.name}>{field.label}</label>
<select {...field.input}
className="form-control"
defaultselection={field.defaultSelection}
><option>{field.defaultselection}</option>{field.children}</select>
</div>
)
}

Formspree form alternative for React project/static site

I'm building a new portfolio site and I want it to have a contact form. The project is built with React and I had planned on using Formspree to add a contact form without a backend. Turns out Formspree no longer allows AJAX calls unless you're a paid subscriber.
Are there any alternatives for contact forms similar to Formspree? I had planned to do something like this but can't due to Formspree's limitations.
I know very little about backend programming and would prefer not to have to dive into Node/Express just for the sake of hooking up a contact form. Is this practical or would just adding a backend be easier at this point?
Hello you can use formcarry for this purpose.
Here is an example code for you:
import React from "react";
import axios from "axios"; // For making client request.
class Form extends React.Component {
constructor(props){
super(props);
this.state = {name: "", surname: "", email: "", message: ""};
}
handleForm = e => {
axios.post(
"https://formcarry.com/s/yourFormId",
this.state,
{headers: {"Accept": "application/json"}}
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
e.preventDefault();
}
handleFields = e => this.setState({ [e.target.name]: e.target.value });
render() {
return (
<form onSubmit={this.handleForm}>
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" onChange={this.handleFields} />
<label htmlFor="surname">Surname</label>
<input type="text" id="surname" name="surname" onChange={this.handleFields} />
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" onChange={this.handleFields} />
<label htmlFor="message">Your Message</label>
<textarea name="message" id="message" onChange={this.handleFields}></textarea>
<button type="submit">Send</button>
</form>
);
}
}
export default Form;
As of Nov 2019 Formspree supports AJAX forms on the free plan. They recently did a 3 part tutorial series on building forms with React using Formspree for the examples. See: https://formspree.io/blog/react-forms-1/
Here's a snippet of code from part 3 of the above series. It's a simple contact for that performs client-side validation with Formik.
import React, { useState } from "react";
import axios from "axios";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
const formSchema = Yup.object().shape({
email: Yup.string()
.email("Invalid email")
.required("Required"),
message: Yup.string().required("Required")
});
export default () => {
/* Server State Handling */
const [serverState, setServerState] = useState();
const handleServerResponse = (ok, msg) => {
setServerState({ok, msg});
};
const handleOnSubmit = (values, actions) => {
axios({
method: "POST",
url: "http://formspree.io/YOUR_FORM_ID",
data: values
})
.then(response => {
actions.setSubmitting(false);
actions.resetForm();
handleServerResponse(true, "Thanks!");
})
.catch(error => {
actions.setSubmitting(false);
handleServerResponse(false, error.response.data.error);
});
};
return (
<div>
<h1>Contact Us</h1>
<Formik
initialValues={{ email: "", message: "" }}
onSubmit={handleOnSubmit}
validationSchema={formSchema}
>
{({ isSubmitting }) => (
<Form id="fs-frm" noValidate>
<label htmlFor="email">Email:</label>
<Field id="email" type="email" name="email" />
<ErrorMessage name="email" className="errorMsg" component="p" />
<label htmlFor="message">Message:</label>
<Field id="message" name="message" component="textarea" />
<ErrorMessage name="message" className="errorMsg" component="p" />
<button type="submit" disabled={isSubmitting}>
Submit
</button>
{serverState && (
<p className={!serverState.ok ? "errorMsg" : ""}>
{serverState.msg}
</p>
)}
</Form>
)}
</Formik>
</div>
);
};
As an update in 2021 (not sure when it was introduced or changed) the standard react approach for formspree does not do any redirect and hides any AJAX details from you.
They have more info here - correct at tine of writing but worth checking as it updates quite regularly: https://help.formspree.io/hc/en-us/articles/360053108134-How-To-Build-a-Contact-Form-with-React
import React from 'react';
import { useForm, ValidationError } from '#formspree/react';
function ContactForm() {
const [state, handleSubmit] = useForm("contactForm");
if (state.succeeded) {
return <p>Thanks for joining!</p>;
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">
Email Address
</label>
<input
id="email"
type="email"
name="email"
/>
<ValidationError
prefix="Email"
field="email"
errors={state.errors}
/>
<textarea
id="message"
name="message"
/>
<ValidationError
prefix="Message"
field="message"
errors={state.errors}
/>
<button type="submit" disabled={state.submitting}>
Submit
</button>
</form>
);
}
export default ContactForm;
There are a few extra steps which are contained in the page above but this is the main contact from function

How to send input hidden in React JS?

I have this form, and I would like to send these values. I know we have to use setState() to store data but how does it work for input type="hidden"?
First question: How to store input hidden to setState ?
Second question: How to serialize data like form.serialize() ?
Third question: How to send these serialize values? Ajax or Axios, who is the better?
Here is the code:
handleSubmit(e) {
e.preventDefault();
/**
$.ajax({
url: "post.php",
type: "POST",
data: DATA,
success:function(data) {
}
});
**/
}
<form onSubmit={this.handleSubmit}>
<input type="hidden" name="action" value="login" />
<input type="email" name="email_user" placeholder="Email" />
<input type="password" name="password_user" placeholder="Mot de passe" />
<button type="submit">Login</button>
</form>
The answer is complex for all your questions.
First of all, it depends on the task: if you just want to send asynchonous request to server on form submit, you don't need to use Component state. Here is a link to the relevant section of the documentation. And use refs to access inputs data.
class FormComponent extends React.Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(e) {
e.preventDefault();
// Send your ajax query via jQuery or Axios (I prefer Axios)
axios.get('your_url', {
params: {
action: this.actionInput.value,
email: this.emailInput.value,
password: this.passwordInput.value,
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<form onSubmit={this.onSubmit}>
<input type="hidden" name="action" value="login"
ref={(input) => { this.actionInput = input }} />
<input type="email" name="email_user" placeholder="Email"
ref={(input) => { this.emailInput = input }}/>
<input type="password" name="password_user" placeholder="Mot de passe"
ref={(input) => { this.passwordInput = input }}/>
<button type="submit">Login</button>
</form>
);
}
}
All data can be stored on React's state, but if you still need to have inputs on your form you can do something like this:
const handleSubmit = e => {
e.preventDefault();
const inputs = Object.values(e.target)
.filter(c => typeof c.tagName === 'string' && c.tagName.toLowerCase() === 'input')
.reduce((acc, curr) => ({ ...acc, [curr.name]: curr.value }), {});
setFormVals({ ...formVals, ...inputs });
}
See the demo below:
const Demo = () => {
const [formValues] = React.useState({});
const handleSubmit = e => {
e.preventDefault();
const inputs = Object.values(e.target)
.filter(c => typeof c.tagName === 'string' && c.tagName.toLowerCase() === 'input')
.reduce((acc, curr) => ({ ...acc, [curr.name]: curr.value }), {});
console.log(inputs);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" value={formValues.name} />
<input name="email" placeholder="Email" value={formValues.email} />
<input name="hiddenInput" value="hiddenValue" type="hidden" />
<button type="submit">Submit</button>
</form>
);
}
ReactDOM.render(<Demo />, document.getElementById('demo'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="demo"></div>
If you know what the inputs that you need you can do something like this:
const Demo = () => {
const formRef = React.useRef(null);
const [formValues, setFormValues] = React.useState({});
const handleChange = e => {
setFormValues({
...formValues,
[e.target.name]: e.target.value,
});
}
const handleSubmit = e => {
e.preventDefault();
setFormValues({ ...formValues, hiddenInput: formRef.current.hiddenInput.value });
}
return (
<form onSubmit={handleSubmit} ref={formRef}>
<input name="name" placeholder="Name" value={formValues.name} onChange={handleChange} />
<input name="email" placeholder="Email" value={formValues.email} onChange={handleChange} />
<input name="hiddenInput" value="hiddenValue" type="hidden" />
<button type="submit">Submit</button>
<pre>{JSON.stringify(formValues, null, 2)}</pre>
</form>
);
}
ReactDOM.render(<Demo />, document.getElementById('demo'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="demo"></div>
Answering your questions:
Since you know how to use component's state you may set the value as : <input type='text' value={this.state.foo} /> or even via props passing <input type='hidden' value={this.props.foo} />
You don't need to serialise anything at all. Use your component's local state or even a state container like Redux or Flux in order to pick the appropriate data. Take a look at this fairly simple example:
var SuperForm = React.createClass({
getInitialState() {
return {
name: 'foo',
email: 'baz#example.com'
};
},
submit(e){
e.preventDefault();
console.log("send via AJAX", this.state)
},
change(e,key){
const newState = {};
newState[key] = e.currentTarget.value;
this.setState(newState)
},
render: function() {
return (
<div>
<label>Name</label>
<input
onChange={(e) => this.change(e,'name')}
type="text"
value={this.state.name} />
<label>Email</label>
<input
onChange={(e) => this.change(e,'email')}
type="text"
value={this.state.email} />
<button onClick={this.submit}>Submit</button>
</div>
);
}});
Demo
AJAX is a set of web development techniques while Axios is a JavaScript framework. You may use jQuery, Axios or even vanilla JavaScript.

Resources