Formik Form only updates state on second click - reactjs

I am trying to make a checkout form using Formik, however when I click the submit button and log the data to my console I don't see any changes until the second click.
onSubmit={(values) => {
addData(values);
console.log(data);
}}>
const addData = (billing) => {
setData((currentData) => {
const shipping = {
first_name: billing.first_name,
last_name: billing.last_name,
address_1: billing.address_1,
address_2: billing.address_2,
city: billing.city,
state: billing.state,
postcode: billing.postcode,
country: billing.country,
};
return {billing, shipping, ...currentData};
});
};
const [data, setData] = useState({
payment_method: 'bacs',
payment_method_title: 'Direct Bank Transfer',
set_paid: true,
line_items: [
{
product_id: 93,
quantity: 2,
},
{
product_id: 22,
variation_id: 23,
quantity: 1,
},
],
shipping_lines: [
{
method_id: 'flat_rate',
method_title: 'Flat Rate',
total: '10',
},
],
});
Additionally, when I go to change the data (my form doesn't reset on submission) I still see the old data and it doesn't update.

I just have just passed it and I could resolved it!
It seems like it renders just on next click and then updates.
In order to fix that, please try React useEffect Hook.THis is my example for React Native
import React, { useState, useEffect } from 'react';
import { View, TextInput, Button } from 'react-native';
import { Formik } from 'formik';
export const FormikPub = (props) => {
const [formikPub, setFormikPub] = useState(
{foo: '', bar:''}
)
useEffect(() => {
console.log(`After setFormikPub(): ${JSON.stringify(formikPub)}`)
}, [formikPub]);
return (
<Formik
initialValues={{ foo: '', bar: '' }}
onSubmit={(values) => {
setFormikPub(values)
}}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<TextInput
name='foo'
placeholder="Foo"
onChangeText={handleChange('foo')}
onBlur={handleBlur('foo')}
value={values.foo}
/>
<TextInput
name='bar'
placeholder="Bar"
onChangeText={handleChange('bar')}
onBlur={handleBlur('bar')}
value={values.bar}
/>
<Button color="#004686" onPress={handleSubmit} title="Publish Formik" />
</View>
)}
</Formik>
);
}
It may able to you press the button and it will run only when you change the text input.

Related

UseEffect not re-rendering on use state object property change

I have a component that fetches data from an API and displays it in a select box. When the select box is changed, it updates a property inside the state. When this is done, I want to run a different useEffect which fetches more values from the API based on the selected item.
// State for dropdown options
const [categories, setCategories] = useState<DropdownItemProps[]>([]);
// State for form
const [category] = useState<CategoryInstance>({
id: "",
number: 1,
sponsor: "",
logo: "",
permit: "",
bumpSpot: 128,
raceEventId: "670d34d5-134b-4408-91bd-691f8c4b62d2",
categoryId: "",
configurationId: "",
category: null,
configuration: null,
raceEvent: null,
ladder: null,
entryList: [],
rounds: [],
});
// UseEffect 1, fetch dropdown items on load
useEffect(() => {
console.log("Use effect 1");
let c: DropdownItemProps[] = [];
categoryStore.loadCategories().then(() => {
categoryStore.categories.forEach((x) =>
c.push({
key: x.id,
value: x.id,
text: x.name,
})
);
setCategories(c);
});
}, [categoryStore]);
// UseEffect 2, fetch more items
useEffect(() => {
console.log("Use effect 2");
console.log(`ID: ${category.categoryId}`);
}, [category.categoryId]);
From my understanding, when the category.categoryId changes value, it should cause the UseEffect to print out the information. But this isn't happening.
<SelectInput
name='categoryId'
placeholder='Category'
label='Category'
options={categories}
search
/>
Select Input
import { useField } from "formik";
import { DropdownItemProps, Form, Label, Select } from "semantic-ui-react";
interface Props {
placeholder: string;
name: string;
type?: string;
label?: string;
options: DropdownItemProps[];
search: boolean;
}
export default function SelectInput(props: Props) {
const [field, meta, helpers] = useField(props.name);
return (
<Form.Field error={meta.touched && !!meta.error}>
<label>{props.label}</label>
<Select
clearable
options={props.options}
value={field.value || null}
onChange={(event, data) => helpers.setValue(data.value)}
onBlur={() => helpers.setTouched(true)}
placeholder={props.placeholder}
search={props.search}
/>
{meta.touched && meta.error ? (
<Label basic color='red'>
{meta.error}
</Label>
) : null}
</Form.Field>
);
}
Any help would great,
Thanks.

Unable to submit a multi-select checkbox input using formik in react

My form using formik does not currently return a value for the multi-select checkbox input component. I know I need to wrap the check box function component in the formik Field for formik to recognise and grab the component data. However, when ever I do so, It throws an error and the page goes blank.
How best can I integrate this component with formik so I can successfully submit the form.
Checkbox multi-select input compoenet
import React, { useState } from "react";
import { MultiSelect } from "react-multi-select-component";
const options = [
{ label: 'Sunday', value: 'sunday' },
{ label: 'Monday', value: 'monday'},
{ label: 'Tuesday', value: 'tuesday'},
{ label: 'Wednessday', value: 'wednessday'},
{ label: 'Thursday', value: 'thursday'},
{ label: 'Friday', value: 'friday'},
{ label: 'Saturday', value: 'saturday'},
{ label: "Week", value: "week", disabled: true },
];
const SelectFields = ({name}) => {
const [selected, setSelected] = useState([]);
return (
<div>
{/* <pre>{JSON.stringify(selected)}</pre> */}
<MultiSelect
options={options}
value={selected}
onChange={setSelected}
labelledBy="Select"
name={name}
/>
</div>
);
};
export default SelectFields;
Parent component where I'm using formik
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const NewRates = () => {
// code here were removed...
const initialValues = {
rateName: '',
price: '',
availableForPurchase: '',
availableType: '',
accessLevel: false,
validationType: '',
durationOfRate: '',
startTime: '',
endTime: '',
startTimeDate: '',
endTimeDate: '',
};
const validationSchema = Yup.object().shape({
});
const handleRates = (formValue) => {
console.log('formValue', formValue)
};
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleRates}
>
<Form>
{!successful && (
<FormWrapper>
// codes here were removed.
<>
<h6>Purchase Availability</h6>
<FormGroup>
<label htmlFor="AvailabilityForPurchase">
Select Days as required
<SelectFields name='availableForPurchase'/>
<ErrorMessage
name='availableForPurchase'
component='div'
className='alert alert-danger'
/>
</label>
</FormGroup>
....other codes
I have checked other similar solutions. But none come close to solving my issue.
You need to create a reusable version of multi select, which is bound to formik. Create a new component/file called MultiSelectField and useField to route the formik state into the MultiSelect component:
import { useField } from "formik"
import { MultiSelect } from "react-multi-select-component";
export const MultiSelectField = ({name, ...otherProps}) => {
const [field, meta, helpers] = useField({name})
return <MultiSelect value={field.value} onChange={(items) => helpers.setValue(items)} {...otherProps}/>
}
Now create your specific dropdown wrapper around this:
import React, { useState } from "react";
import { MultiSelectField } from "./MultiSelectField";
const options = [
{ label: 'Sunday', value: 'sunday' },
{ label: 'Monday', value: 'monday'},
{ label: 'Tuesday', value: 'tuesday'},
{ label: 'Wednessday', value: 'wednessday'},
{ label: 'Thursday', value: 'thursday'},
{ label: 'Friday', value: 'friday'},
{ label: 'Saturday', value: 'saturday'},
{ label: "Week", value: "week", disabled: true },
];
const SelectFields = ({name}) => {
return (
<MultiSelectField
options={options}
labelledBy="Select"
name={name}
/>
);
};
export default SelectFields;
And now use this as you already do <SelectFields name='availableForPurchase'/>

How to update a state entry in react and display it's contents in input field before updating?

I'm creating a shopping cart form that can be used to add/update/delete user info. I've used react-hook-form for form submission and validation. My initial state is empty array. When user is added, objects are appended in the state array like -
state = [
{ name: 'abc', age: '23' },
{ name: 'katy', age: '12' },
];
How can update the value in state if a div row has an edit button and it displays it in an existing input box and when i click update(another button), it updates the corresponding value.
Note- name can be same hence i can't use a state.find().
One approach
const UpdateComponent = ({ id, user, setState }) => {
const [userData, setUserData] = React.useState({
id: 0,
name: "",
age: 0
});
React.useEffect(() => {
setUserData({ id: id, name: user.name, age: user.age });
}, [user, id]);
const onChange = (e) => {
setUserData((currentData) => ({
...currentData,
[e.target.name]: e.target.value
}));
};
const onSubmit = () => {
setState((currentState) =>
currentState.map((u) => (u.id === id ? userData : u))
);
};
return (
<>
<input
onChange={onChange}
name="name"
value={userData.name}
placeholder="Name"
/>
<input
onChange={onChange}
name="age"
value={userData.age}
placeholder="Age"
/>
<button onClick={onSubmit} type="button">
Update
</button>
</>
);
};
const List = () => {
const [state, setState] = React.useState([]);
React.useEffect(() => {
setState(
[
{ name: "abc", age: "23" },
{ name: "katy", age: "12" }
].map((u, i) => ({ ...u, id: i }))
);
}, []);
React.useEffect(() => {
// debug
console.log(state);
}, [state]);
return (
<div>
{state.map((user) => (
<UpdateComponent
key={user.id}
id={user.id}
user={user}
setState={setState}
/>
))}
</div>
);
};
Take a look https://codesandbox.io/s/fragrant-surf-p5cxh?file=/src/App.js
You could use the UUID package to generate the IDs:
React.useEffect(() => {
// Generates IDs when loading the data as example
// but ideally IDs are created on user creation
setState(
[
{ name: "abc", age: "23" },
{ name: "katy", age: "12" }
].map((u) => ({ ...u, id: uuidv4() }))
);
}, []);
Sandbox: https://codesandbox.io/s/unruffled-hypatia-tjzcx
But that is not very different from the initial approach with the map index id(on the component mount), that is not ideal because we are generating IDs when the component mounts, but at least they don't keep changing on each render
My initial state is empty array. When user is added, objects are appended in the state array like
For your case you could just have an ID that increments each time a user is added, or use the uuid when its added, so your data already comes with the ID

React Native login form

I am trying to create Login page in React native using functional component. But it is not working. As soon as enter any text throwing error. value is not changing.
import React from "react";
import { View, Button, Text } from "react-native";
import Inputs from "../../utils/Form/Input";
const LoginForm = () => {
const [formData, setForm] = React.useState({
email: {
value: "",
valid: false,
type: "textinput",
rules: {
isRequired: true,
isEmail: true
}
},
password: {
value: "",
valid: false,
type: "textinput",
rules: {
isRequired: true,
minLength: true
}
}
});
const handleChange = () => {
setForm({ ...formData });
console.log(formData.email);
};
return (
<View>
<Text>Login</Text>
<Inputs
placeholder="Enter email address"
placeholdercolor="red"
autoCapitalize={"none"}
keyboardType={"email-address"}
onChangeText={value => handleChange("email", value)}
value={formData.email.value}
type={formData.email.type}
/>
<Inputs
placeholder="Password"
placeholdercolor="red"
autoCapitalize={"none"}
type={formData.password.type}
value={formData.password.value}
onChangeText={value => setForm("password", value)}
/>
</View>
);
};
export default LoginForm;
Util file
import React from "react";
import { View, Button, TextInput, Picker, StyleSheet } from "react-native";
const Inputs = props => {
let template = null;
switch (props.type) {
case "textinput":
template = (
<TextInput {...props} style={[styles.input, props.overrideStyle]} />
);
break;
default:
return template;
}
return template;
};
const styles = StyleSheet.create({
input: {
width: "100%",
borderBottomWidth: 2,
borderBottomColor: "blue",
fontSize: 16,
padding: 5,
marginTop: 10
}
});
export default Inputs;
You are missing parameters in handleChange function. It should be
const handleChange = (key, value) => {
let data = formData;
data[key].value = value;
setForm(data);
console.log(formData.email);
};
your handleChange change function is not proper and producing error, change your handle change method to this function
const handleChange = (val, data) => {
if (val === 'email') {
setForm({
...formData,
email: {
...formData.email,
value: data,
},
});
} else {
setForm({
...formData,
password: {
...formData.password,
value: data,
},
});
}
};
and change your onChangeText prop of password input to
onChangeText={value => handleChange('password', value)}

React Native Store value in redux

i wanted to store the Email in the redux store and i am unable to do so here is my sign in component and redux store any help would be appreciated i am using react-navigation
My Dispatch Method is invoked on the initial load as well as on every key stroke for email input i want that to invoke only on hit of continue button
I need a way to store the email in the store and retrieve it in some other screen later
SignUp.js
import React, { Component } from 'react';
import {
StyleSheet,
View,
KeyboardAvoidingView,
Keyboard,
TouchableWithoutFeedback,
Alert
} from 'react-native';
import { SocialIcon } from 'react-native-elements';
import PropTypes from 'prop-types';
import { Header } from 'react-navigation';
import { connect } from 'react-redux';
import {
Container, Footer, FooterContainer, DefaultInput, Typography
} from '../components/common';
import { validate } from '../config';
import * as actionTypes from '../store/actions';
const styles = StyleSheet.create({
container: {
flex: 1
},
input: {
width: '80%',
height: 40
}
});
class SignUp extends Component {
state = {
controls: {
email: {
value: '',
valid: false,
validationRules: {
isEmail: true
},
touched: false
},
password: {
value: '',
valid: false,
validationRules: {
minLength: 6
},
touched: false
},
confirmPassword: {
value: '',
valid: false,
validationRules: {
equalTo: 'password'
},
touched: false
}
}
};
updateInputState = (key, value) => {
let connectedValue = {};
const stateObject = this.state;
if (stateObject.controls[key].validationRules.equalTo) {
const equalControl = stateObject.controls[key].validationRules.equalTo;
const equalValue = stateObject.controls[equalControl].value;
connectedValue = {
...connectedValue,
equalTo: equalValue
};
}
if (key === 'password') {
connectedValue = {
...connectedValue,
equalTo: value
};
}
this.setState(prevState => ({
controls: {
...prevState.controls,
confirmPassword: {
...prevState.controls.confirmPassword,
valid:
key === 'password'
? validate(
prevState.controls.confirmPassword.value,
prevState.controls.confirmPassword.validationRules,
connectedValue
)
: prevState.controls.confirmPassword.valid
},
[key]: {
...prevState.controls[key],
value,
valid: validate(value, prevState.controls[key].validationRules, connectedValue),
touched: true
}
}
}));
};
render () {
const stateData = this.state;
const { navigation } = this.props;
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
keyboardVerticalOffset={Header.HEIGHT + 20}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<Container>
<Typography textType="loginLabelStyle" textLabel="Use any of your existing profiles" />
<View style={‌{
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<SocialIcon type="twitter" />
<SocialIcon type="facebook" />
<SocialIcon type="google" light onPress={this.signIn} />
</View>
<Typography textType="loginLabelStyle" textLabel="or create one on SimpliFid" />
<DefaultInput
placeholder="Your E-Mail Address"
style={styles.input}
value={stateData.controls.email.value}
onChangeText={val => this.updateInputState('email', val)}
valid={stateData.controls.email.valid}
touched={stateData.controls.email.touched}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
/>
<DefaultInput
placeholder="Password"
style={styles.input}
value={stateData.controls.password.value}
onChangeText={val => this.updateInputState('password', val)}
valid={stateData.controls.password.valid}
touched={stateData.controls.password.touched}
secureTextEntry
/>
<DefaultInput
placeholder="Confirm Password"
style={styles.input}
value={stateData.controls.confirmPassword.value}
onChangeText={val => this.updateInputState('confirmPassword', val)}
valid={stateData.controls.confirmPassword.valid}
touched={stateData.controls.confirmPassword.touched}
secureTextEntry
/>
<FooterContainer>
<Footer
leftButtonHandler={() => navigation.goBack()}
rightButtonHandler={this.props.onSignUp(stateData.controls.email.value, navigation)}
/* rightButtonHandler={() => navigation.navigate('ChatBot')} */
/>
</FooterContainer>
</Container>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
}
}
SignUp.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func.isRequired
}).isRequired
};
const mapDispatchToProps = dispatch => ({
onSignUp: (email, navigation) => {
Alert.alert(email);
dispatch({ type: actionTypes.SIGNUP, email });
navigation.navigate('ChatBot');
}
});
export default connect(
null,
mapDispatchToProps
)(SignUp);
Reducers.js
import * as actionTypes from './actions';
const initialState = {
email: '',
accountType: '',
name: '',
dob: '',
address: '',
ssn: '',
phoneNumber: '',
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SIGNUP:
return {
...state,
email: action.email,
};
default:
return state;
}
};
export default reducer;
You are calling the this.props.onSingUp methods on each render
Try wrapping the call in a handler method:
handleRightButton = () => {
this.props.onSignUp(this.state..controls.email.value, this.props.navigation);
}
// And on render
render() {
...
rightButtonHandler={this.handleRightButton}
...
}
The problem was that i was trying to access the store in a wrong way i was trying using this
import state from '../store/reducers';
const Email = state.email;
However the correct way and probably the only way to access the store is using mapStateToProps
const mapStateToProps = state => ({
email: state.email,
});
<Footer
leftButtonHandler={() => navigation.goBack()}
rightButtonHandler={(event) => {
event.preventDefault();
this.props.onSignUp(stateData.controls.email.value,navigation)
/>
Try adding the event.preventDefault() in the rightButtonHandler.

Resources