State not being set by setState method - javascript

I am using React JS.
Here is my React class:
class SomeClass extends React.Component{
constructor(props){
super(props);
this.state = {
passAccount: {
email: "Email"
},
errorMessage: ''
};
}
submitRequest = (event) =>{
//this.state.passAccount.email === 'Email' ? this.setState({errorMessage:'Please enter a valid email'}) : axios.post(`http://localhost:5000/otp/generate-passcode/${this.state.passAccount.email.toString()}`, this.state.passAccount)
axios.post(`http://localhost:5000/generate/${String(this.state.passAccount.email)}`)
.then((response) => {
let result = response.data;
}).catch((error) =>{
this.setState({errorMessage: ''});
});
console.log(`submitRequest email: `, this.state.passAccount.email);
}
handleChange = (event) =>{
console.log(`input detected`);
let request = this.state.passAccount;
let requestValue = event.target.value;
this.setState({passAccount: requestValue});
}
render() {
return (
<Form onSubmit={this.handleSubmit}>
<Form.Group>
<Form.Label>Email Address</Form.Label>
<Form.Control type="text" value={this.state.email} onChange={this.handleChange} placeholder="Enter Email Address" style={{width: '25rem'}}/>
</Form.Group>
<Button type="submit" onClick={() => this.submitRequest()}>Get OTP</Button>
<Button type="submit">Sign In</Button>
</Form>
);
}
}
export default SomeClass;
In Chrome console, this is what I am getting:
input detected
submitRequest email: Email //here is what I want to fix
Form Submitted Successfully
My question is:
In the line where it says in the console:
submitRequest email: Email //here is what I want to fix, for some reason the setState method is not working what is the reason behind that ?
Is the error in the handleChange method or in the submitRequest method ? what is a possible fix ?
Thanks.

When you this.setState({passAccount: requestValue}); you are setting passAccount to current value edited in form. But passAccount is an object with email property.
So I would suggest to modify your code in this way:
handleChange = (event) =>{
console.log(`input detected`);
let request = Object.assign({}, this.state.passAccount); // copy this.state.passAccount into request
request.email = event.target.value; // change request email
this.setState({ passAccount: request }); // set passAccount
}

You have declared your state variable passAccount as an object which contains an email property. If you want to update this email property then in your handleChange function, you need to update the state like this:
this.setState({ passAccount: { email: requestValue });

Related

React JS (Form Update Not Working) - How to set state immediately after getting data from API using mapDispatchToProps?

I am very new to react and working on some basics where I came up in the situation - I want to set state immediately after API call.
Scenario:
2 Forms:
1st form => accepts id and calls api to get data of single user
2nd form => updates data
PROBLEM: I want to set state when I get data after clicking submit button on 1st Form
import React, { Component, useEffect } from 'react'
import { connect } from 'react-redux';
import { getSingleUser } from '../redux/user/userActions';
export class UsersContainerUpdate extends Component {
constructor(props) {
super(props);
console.log(props.propFirstName);
this.state = {
id: '',
// first_name: props.propFirstName === '' ? '' : props.propFirstName,
first_name: props.propFirstName,
last_name: props.propLastName === '' ? '' : props.propLastName,
phone: props.propPhone === '' ? '' : props.propPhone,
email: props.propEmail === '' ? '' : props.propEmail,
address: props.propAddress === '' ? '' : props.propAddress,
city: props.propCity === '' ? '' : props.propCity,
state: props.propState === '' ? '' : props.propState,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
handleChange = (field, event) => {
this.setState({ [field]: event.target.value });
}
handleSubmit(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
id
} = this.state;
const postData = {
id: id
};
// console.log(this.state);
// console.log(postData);
this.props.getSingleUserData(id);
// if (this.props.getSingleUserData(id)) {
// this.setState({
// ...this.state,
// first_name: this.props.propFirstName
// });
// }
}
handleUpdate(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
first_name,
last_name,
phone,
email,
address,
city,
state
} = this.state;
const postData = {
first_name: first_name,
last_name: last_name,
phone: phone,
email: email,
address: address,
city: city,
state: state
};
console.log(this.state);
console.log("POSTDATA:", postData);
// alert('hi');
// this.props.updateUserData(id,postData);
}
render() {
return (
<div>
<h1>Update User By ID</h1>
<form onSubmit={this.handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={this.state.id}
onChange={(event, newValue) => this.handleChange('id', event)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
<h1>Update User</h1>
<form onSubmit={this.handleUpdate}>
<div>
<label>First Name:</label>
<input
type="text"
value={this.state.first_name || this.props.propFirstName}
onChange={(event, newValue) => this.handleChange('first_name', event)}
/>
</div>
<div>
<label>Last Name:</label>
<input
type="text"
value={this.state.last_name || this.props.propLastName}
onChange={(event, newValue) => this.handleChange('last_name', event)} />
</div>
<div>
<label>Phone:</label>
<input
type="text"
value={this.state.phone || this.props.propPhone}
onChange={(event, newValue) => this.handleChange('phone', event)} />
</div>
<div>
<label>Email:</label>
<input
type="text"
value={this.state.email || this.props.propEmail}
onChange={(event, newValue) => this.handleChange('email', event)} />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
Notice Message : {this.props.propFirstName}
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state.user);
return {
propFirstName: state.user.first_name,
propLastName: state.user.last_name,
propPhone: state.user.phone,
propEmail: state.user.email,
propAddress: state.user.address,
propCity: state.user.city,
propState: state.user.state
}
}
const mapDispatchToProps = dispatch => {
return {
getSingleUserData: id => dispatch(getSingleUser(id)),
// updateUserData: (id,postData) => dispatch(updateUser(id,postData))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UsersContainerUpdate)
The console outputs are
The console output of line:81 is the current state which is currently empty. I want to set it there.
Thanks in advance. Cheers!!
If your requirement is just to state after API call inside this.props.getSingleUserData(id),
Approach 1: (Unclean)
Add one more argument to getSingleUserData(id, setState) and pass it this.setState as an argument and inside getSingleUserData you can set the state using the function reference passed
Approach 2:
You can return a promise from getSingleUserData and do setState once it is resolves
Suggestion:
Divide your big component into individual components (like one for getting user ID and one for User data updation). The more we identify and split our project into meanigfull individual components we get more clean codes. Also when you choose to move towards functional components you can reduce lot of boiler plates with hooks.
Problem
state.user is used to set the initial value of your component's state. Changes to those props do not change your state after the component is created. They do change the values in your inputs because the initial value was an empty string '' so you default to showing the value from props. This is very misleading since those inputs don't reflect the current state.
I bet I could delete at least half of this code but that's besides the point. But take a moment to think about why props.propState === '' ? '' : props.propState is always exactly the same as just props.propState.
Solution
I have two key recommendations for how I would rewrite this:
Select user by id
Separate into multiple components
Store only the modifications in the state
Create a selector function selectUserById that selects a user from your Redux state by the id. I don't think it makes sense to store the current user properties as top-level properties of state.user like you have them right now. It seems like you also have a property state.user.users which is an array of all loaded users so I would use that.
const selectUserById = (state, id) => state.user.users.find(user => user.id === id);
Or better yet, store an object of users keyed by id.
const selectUserById = (state, id) => state.user.users[id];
With this approach we either have a complete user object or we have undefined. It's easy to check for undefined and not show the "Update User" form at all until we have real data. This makes more sense than using empty strings as the default.
We can access the complete user object from Redux. I would not duplicate that object in state. Instead I would use the state only for the properties that you have changed. You would start out with the state as an empty object and add properties to it as you modify their inputs. You can always combine the two together using object spreading.
const merged = {...existing, ...changes}
Can you implement these suggestions using class components and connect? Yes. But why add the extra hurdle? Some of the things in your code like this.handleChange.bind(this) are relics of the past when we had to do that because there wasn't a better way. But now we have better ways so you should use them.
Code
Interactive Demo on CodeSandbox
import "./App.css";
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "../store";
import { getSingleUser, updateUser } from "../store/slice";
const selectUserById = (state, id) => state.user.users[id];
const UserIdForm = ({ submitId }) => {
const [id, setId] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
submitId(id);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={id}
onChange={(event) => setId(event.target.value)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UpdateUserForm = ({ id }) => {
const [changes, setChanges] = useState > {};
const existing = useSelector((state) => selectUserById(state, id));
const dispatch = useDispatch();
// respond to changes in id by clearing the changes state and requesting the user
useEffect(() => {
dispatch(getSingleUser(id));
setChanges({});
}, [dispatch, setChanges, id]);
if (!existing) {
return <div>Loading User...</div>;
}
const merged = { ...existing, ...changes };
const handleChange = (property, event) => {
// in function components you have to copy the whole state
setChanges((prevChanges) => ({
...prevChanges,
[property]: event.target.value
}));
};
const handleUpdate = (event) => {
event.preventDefault();
const postData = { ...merged, id };
console.log("POSTDATA:", postData);
dispatch(updateUser(postData));
};
const renderInput = (property, label) => {
return (
<div>
<label>
{label}
<input
type="text"
value={merged[property]} // shows the current value or the updated value
onChange={(event) => handleChange(property, event)}
/>
</label>
</div>
);
};
return (
<form onSubmit={handleUpdate}>
{renderInput("first_name", "First Name:")}
{renderInput("last_name", "Last Name:")}
{renderInput("phone", "Phone:")}
{renderInput("email", "Email:")}
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UsersContainerUpdate = () => {
// this is the id that was last submitted.
const [id, setId] = useState();
return (
<div>
<div>
<h1>Update User By ID</h1>
<UserIdForm submitId={setId} />
</div>
{!!id && ( // only load when there is an actual id
<div>
<h1>Update User</h1>
<UpdateUserForm id={id} />
</div>
)}
</div>
);
};
export default UsersContainerUpdate;

Form validation at server side. How to show validation message in form? (React / Formik / Antd)

For example form with only email field:
const RegistrationForm = (props) => {
const { values, touched, errors, handleChange, handleBlur, handleSubmit, status } = props;
const emailFieldHelp = () => {
const { touched, errors, status, setStatus } = props;
console.log('status: ', status);
if (touched.email && errors.email) {
return errors.email;
}
if (status && !status.isUserAdded) {
setStatus({"isUserAdded": false});
return "User already exist";
}
return null;
};
return (
<Form onFinish={handleSubmit}>
<Form.Item
help={emailFieldHelp()}
validateStatus={setFieldValidateStatus('email')}
label="E-mail"
name="email"
hasFeedback={touched.email && values.email !== ''}
>
<Input
placeholder="Email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
)
};
const RegFormView = withFormik({
validationSchema,
mapPropsToValues: () => ({
email: ''
}),
handleSubmit: async (values, { setErrors, setSubmitting, setStatus }) => {
await userService.createUser('/signup/', values)
.then(response => {
setStatus('');
const status = (response.isAdded)
? {isUserAdded: true}
: {isUserAdded: false};
setStatus(status);
setSubmitting(false);
}, (error) => {
setErrors(error);
});
},
})(RegistrationForm);
When I send form and validate it on sever side, I return 'isAdded' = false or true. Than I set status to {isUserAdded: true} or false if user not added. and I absolutely have no idea how to show this message in form under email field and keep form working. Now I can show message but than I cant send form second time becouse of status already set to {isUserAdded: true}. Do I need somehow to change status when user change email field? but I can't do it becouse of the formik.
here onChange={handleChange} I can pass only handleChange and can't run my function or I can run my function like this onChange={myFunc()} but than its seems impossible to pass handleChange to Formik. (I dont need call it like this handleChange(e) its not work! i need somehow pass it to Formik) I'm total stuck. If you have knowledge in react plz help.
And if you know some examples how to show server side validation messages in react form, links to this examples might be helpful. ty.

How to clear input after form submit (React)

I have a search input I'd like to clear after the value is submitted and the search is performed with the value. In similar questions, it was suggested to set the state of the input value to '', but I think that's what I tried and it didn't do anything.
I only have a parent and child component in my app. The parent component has a method for searching jokes (searchJokes), and it is passed down as a prop with a different name to the child component in the component instance with onFormSubmit={this.searchJokes}. In the child component, when the user enters something into the search input, its event.target.value is passed with onChange={e => props.onInputChange(e.target.value)} corresponding to the onSearchChange method in the parent prop, and the value is used to update the state of searchTerm.
I added searchTerm: '' to the end of the searchJokes method, which fetches a search according to the search term, as you can see in the parent component code below.
Parent component:
class App extends Component {
constructor() {
super();
this.state = {
searchTerm: '',
jokes: [],
isFetchingJokes: false,
isSearch: false
};
this.onSearchChange = this.onSearchChange.bind(this);
this.randomizeJokes = this.randomizeJokes.bind(this);
this.searchJokes = this.searchJokes.bind(this);
}
randomizeJokes() {
this.setState({
isFetchingJokes: true,
isSearch: false
});
fetch(
'https://icanhazdadjoke.com/',
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let joke = json.joke;
this.setState({
joke,
isFetchingJokes: false
});
});
}
searchJokes(limit = 15) {
// If nothing entered, user gets "Please fill out this field" message due to "required" attribute on input element
if (this.state.searchTerm !== '') {
this.setState({
isFetchingJokes: true,
isSearch: true
});
fetch(
`https://icanhazdadjoke.com/search?term=${
this.state.searchTerm
}&limit=${limit}`,
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let jokes = json.results;
this.setState({
jokes,
isFetchingJokes: false,
searchTerm: '' // <-- DOESN'T CLEAR INPUT
});
});
}
}
onSearchChange(value) {
this.setState({ searchTerm: value });
}
jokeRender() {
return (
<div>
{this.state.isSearch ?
<ul>{this.state.jokes.map(item => <li key={item.id}>{item.joke}</li>)}
</ul> : <p className="random-joke">{this.state.joke}</p>}
</div>
);
}
render() {
return (
<div>
<h1>Dad Jokes</h1>
<RetrievalForm
onFormSubmit={this.searchJokes}
onInputChange={this.onSearchChange}
isSearching={this.state.isFetchingJokes}
onRandomize={this.randomizeJokes}
/>
{this.state.isFetchingJokes ? <p className="searching-message">Searching for jokes...</p> : this.jokeRender()}
</div>
);
};
}
Child component:
const RetrievalForm = props => {
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Enter search term..."
onChange={e => props.onInputChange(e.target.value)}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>Search</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Any help would be greatly appreciated.
You need to pass your searchTerm down to the RetrievalForm and in that input set value={searchTerm} so that it's value will be bound to that state.
Basically, you need to store the input value in the component's state. When onSubmit is called, we should revert that value to an empty string.
Example with some React Hooks goodness:
import React, { Component, useState } from 'react';
const RetrievalForm = props => {
const [searchTerm, setSearchTerm] = useState('');
const onChange = e => {
const { value } = e.target;
props.onInputChange(value);
setSearchTerm(value)
}
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
setSearchTerm('');
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
value={searchTerm}
placeholder="Enter search term..."
onChange={onChange}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>
Search
</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Example here: https://stackblitz.com/edit/react-db5ire

Why does my localStorage.setItem not seem to be doing anything?

I have tried to simplify the code down to what is relevant.
class AnnotatedLayout extends React.Component {
state = {
user: '',
enabled: false,
};
render() {
const { user, enabled } = this.state;
const contentStatus = enabled ? 'Disable' : 'Enable';
const textStatus = enabled ? 'enabled' : 'disabled';
return (
...
<Form onSubmit={this.handleSubmit}>
<FormLayout>
<TextField
value={user}
onChange={this.handleChange('user')}
label="Shop Name"
type="user"
helpText={
<span>
Log in with your store username.
</span>
}
/>
<Stack distribution="trailing">
<Button primary submit>
Submit
</Button>
</Stack>
</FormLayout>
</Form>
...
);
}
handleSubmit = () => {
this.setState({
user: this.state.user
});
localStorage.setItem('user', JSON.stringify(this.state.user));
console.log('submission', this.state);
console.log(this.state.user);
};
handleChange = field => {
return value => this.setState({ [field]: value });
};
}
export default AnnotatedLayout;
Essentially, I have a form component to my webpage that, on submitting, is executing this.handleSubmit, and that function is at the bottom.
What my code SHOULD be doing is saving that submitted string to the localStorage with the key 'user', but evidently (you can see below console.log output) that's not happening.
Any idea what's going on?
My website is hosted locally, tunneled to a URL, and used as the base URL for a shopify embedded app, just to give all relevant context.
UPDATE
handleSubmit = () => {
this.setState({
user: this.state.user
},
() => localStorage.setItem('user', "SMH"),
console.log(localStorage.getItem('user'))
);
console.log('submission', this.state);
};
Check this out, after submitting my text form now this is what I get
is localStorage like local or something, to the point where it doesnt save anything outside of a function??
It seems like you handleChange returns a method, which you need to call again to set the user value.
Instead of
<TextField
value={user}
onChange={this.handleChange('user')}
...
Try
<TextField
value={user}
onChange={e => this.handleChange('user')(e)}
...
The value in handleChange should accept e event value, which is the user value to set.
this.setState(
{
user: this.state.user
},
() => localStorage.setItem('user', JSON.stringify(this.state.user))
);

How to reset input form on submit (ReactJS) [duplicate]

This question already has answers here:
React - clearing an input value after form submit
(9 answers)
Closed 4 years ago.
I am having some difficulty getting my input form to reset on submit. I would like for the input field to reset to a blank value when the form is submitted successfully, but for the time being I am also fine with it just resetting onSubmit in general, neither of which I have been able to figure out so far. Specifically, what I tried was:
class SubscribeForm extends React.Component {
constructor(props, ...args) {
super(props, ...args)
this.state = {
status: null,
msg: null,
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
this.setState({ value: '' });
}
With the form layout:
<form onSubmit={this.handleSubmit} action={action} method="post" id="alert-form" noValidate>
<input
ref={node => (this.input = node)}
type="email"
defaultValue=""
name="EMAIL"
required={true}
placeholder={messages.inputPlaceholder}
/>
<button
disabled={this.state.status === "sending" || this.state.status === "success"}
onClick={this.onSubmit}
type="submit"
className="btn-group"
id="AlertButton"
>
<p>Sign Up</p>
</button>
</form>
However, calling {this.handleSumbit} in the onSubmit prop of the Form does not appear to have any result when the button is actually clicked. Is there anything obvious i'm missing here or is it a more complex problem?
Provided below is the full relevant code snippet:
class SubscribeForm extends React.Component {
constructor(props, ...args) {
super(props, ...args)
this.state = {
status: null,
msg: null,
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
this.setState({ value: '' });
}
onSubmit = e => {
e.preventDefault()
if (!this.input.value || this.input.value.length < 5 || this.input.value.indexOf("#") === -1) {
this.setState({
status: "error"
})
return
}
const url = getAjaxUrl(this.props.action) + `&EMAIL=${encodeURIComponent(this.input.value)}`;
this.setState(
{
status: "sending",
msg: null
}, () => jsonp(url, {
param: "c"
}, (err, data) => {
if (err) {
this.setState({
status: 'error',
msg: err
})
} else if (data.result !== 'success') {
this.setState({
status: 'error',
msg: data.msg,
})
} else {
this.input.defaultValue = "";
this.setState({
status: 'success',
msg: data.msg,
inputPlaceholder: "E-mail"
})
}
})
)
}
render() {
const { action, messages, className, style, styles } = this.props
const { status, msg } = this.state
return (
<div className={className} style={style}>
<form onSubmit={this.handleSubmit} action={action} method="post" id="alert-form" noValidate>
<input
ref={node => (this.input = node)}
type="email"
defaultValue=""
name="EMAIL"
required={true}
placeholder={messages.inputPlaceholder}
/>
<button
disabled={this.state.status === "sending" || this.state.status === "success"}
onClick={this.onSubmit}
type="submit"
className="btn-group"
id="AlertButton"
>
<p>Sign Up</p>
</button>
</form>
</div>
)
}
}
Try this
handleSubmit(event) {
event.preventDefault();
this.input.value = '';
this.setState({ value: '' });
}
https://codepen.io/va0000ll/pen/zRXNwY
You could make SubscribeForm a controlled component. https://reactjs.org/docs/forms.html
This means adding an onChange that updates this.state.value and setting the value of the input field to this.state.value.
If you do this your setState({value: ''}) would cause a render of the form with the value set back to empty string.
It creates a two way binding. When the input field is updated, so is the component state. When the component state is updated, so is the input field.
Since you are using onSubmit, you can get the input field from the event. Something like event.target.elements.EMAIL.value = ''. I would prefer this over using the ref that you have there.

Categories