Store validation in React and Redux - javascript

I have simple page, which identifies user, it consists of Name and Email.
class UserIdentification extends Component {
constructor(props) {
super(props);
this.state = {
Name: this.props.Name,
Email: this.props.Email,
emailErrors: ''
};
}
isValid(showErrors, validate, errorsName, value) {
let errors = [];
for (let name of validate) {
errors = errors.concat(validators[name](value));
}
if (showErrors) {
this.setState({
[errorsName]: errors.join(' ')
});
}
console.log(errors);
return !errors.length;
}
handleInputChange(name, event) {
this.setState({
[name]: event.target.value,
});
}
onInputBlur(showErrors, validate, errorsName, value, action) {
if (!this.isValid(showErrors, validate, errorsName, value))
return;
this.props.userIdentificationActions[action](value);
}
render() {
return (
<div className='row'>
<form className='col s12'>
<h5 className='flow-text'>Identification</h5>
<div className='row'>
<div className='input-field col s12'>
<i className='material-icons prefix'>account_circle</i>
<input
value={this.props.Name}
placeholder='Enter your name'
id='person_name'
onChange={this.handleInputChange.bind(this, 'Name')}
onBlur={this.onInputBlur.bind(this, false, [], '', this.state.Name, 'setName')}
type='text' />
<label className='active' htmlFor='person_name'>Name</label>
</div>
</div>
<div className='row'>
<div className='input-field col s12'>
<i className='material-icons prefix'>email</i>
<input
value={this.props.Email}
placeholder='e.g. myemail#example.com'
id='email'
type='email'
onChange={this.handleInputChange.bind(this, 'Email')}
onBlur={this.onInputBlur.bind(this, true, ['email'], 'emailErrors', this.state.Email, 'setEmail')}
className={this.state.emailErrors.length ? 'invalid' : 'valid'}
/>
<label className='active' data-error={this.state.emailErrors} htmlFor='email'>Email</label>
</div>
</div>
</form>
</div>
);
}
}
function mapStateToProps(state) {
return {
Name: state.userIentification.Name,
Email: state.userIdentification.Email,
}
}
function mapDispatchToProps(dispatch) {
return {
userIdentificationActions: bindActionCreators(userIdentificationActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(userIdentification)
Validation of single fields works perfect.
Here my actions:
export function setName(name) {
return {
type: actionTypes.SET_NAME,
payload: name
}
}
export function setEmail(email) {
return {
type: actionTypes.SET_EMAIL,
payload: email
}
}
and reducer:
const initialState = {
Name: '',
Email: ''
}
export default function identification(state = initialState, action) {
switch (action.type) {
case actionTypes.SET_NAME:
return { ...state, Name: action.payload };
case actionTypes.SET_EMAIL:
return { ...state, Email: action.payload };
default:
return state;
}
}
What I want, is to make validation of store, for example, when Name is not empty and Email is valid, then dispatch some needed action.
What should I do and where to implement this functionality?

Related

How to dispatch async action in Redux using Redux Thunk: TypeError: Cannot read property 'loading' of undefined

I have set everything in Redux side and I see every action in Redux Devtool. It works all perfect. The problem occurs when I want to dispatch action in React Component. In login component I want to dispatch action, wait its response then depending on response redirect it to a page or show errors. Here are my codes:
userActions.js
import axios from "axios";
import {
LOGIN_USER,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAILED,
} from "./types";
const loginUserRequest = () => {
return {
type: LOGIN_USER,
};
};
const loginUserSuccess = (user) => {
return {
type: LOGIN_USER_SUCCESS,
payload: user,
};
};
const loginUserFailed = (error) => {
return {
type: LOGIN_USER_FAILED,
payload: error,
};
};
export const loginUser = (dataSubmitted) => {
return (dispatch) => {
dispatch(loginUserRequest());
axios
.post("/api/users/login", dataSubmitted)
.then((response) => {
dispatch(loginUserSuccess(response.data));
})
.catch((err) => {
dispatch(loginUserFailed(err));
});
};
};
userReducer.js:
import {
LOGIN_USER,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAILED,
} from "../actions/types";
const initialState = {
loading: false,
user: "",
error: "",
};
export default function (state = initialState, action) {
switch (action.type) {
case LOGIN_USER:
return { ...state, loading: true };
case LOGIN_USER_SUCCESS:
return { ...state, loading: false, user: action.payload, error: "" };
case LOGIN_USER_FAILED:
return { ...state, loading: false, user: "", error: action.payload };
default:
return state;
}
}
The above codes works great and does the job. The problem is in following code where I am dispatching the async action. After I run the code I get this.props.userData as undefined.
Login.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { loginUser } from "../../actions/userActions";
export class Login extends Component {
state = {
email: "",
password: "",
errors: [],
};
displayErrors = (errors) =>
errors.map((error, i) => (
<div className="alert alert-danger" key={i}>
{error}
</div>
));
handleSubmit = (e) => {
e.preventDefault();
var { email, password } = this.state;
var errors = [];
if (email === "") {
errors.push("Email is required");
}
if (password === "") {
errors.push("Password is required");
}
this.setState({ errors: errors });
//Problem occurs here
this.props.dispatch(loginUser({ email, password }));
if (response.payload.success) {
sessionStorage.setItem("jwt", response.payload.token);
sessionStorage.setItem("userId", response.payload._id);
this.props.history.push("/");
} else {
errors.push("Username and/or Password is not correct");
this.setState({ errors: errors });
}
};
render() {
return (
<form className="form-signin" onSubmit={this.handleSubmit}>
<h1 className="h3 mb-3 font-weight-normal">Sign in</h1>
{this.state.errors.length > 0 && this.displayErrors(this.state.errors)}
<label for="inputEmail" className="sr-only">
Email address
</label>
<input
type="email"
id="inputEmail"
className="form-control"
placeholder="Email address"
value={this.state.email}
onChange={(e) => {
this.setState({ email: e.target.value });
}}
required
autoFocus
/>
<label for="inputPassword" className="sr-only">
Password
</label>
<input
type="password"
id="inputPassword"
className="form-control"
placeholder="Password"
value={this.state.password}
onChange={(e) => {
this.setState({ password: e.target.value });
}}
required
/>
<div className="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me" /> Remember me
</label>
</div>
<button
className="btn btn-lg btn-primary btn-block"
type="submit"
onClick={this.handleSubmit}
>
Sign in
</button>
<Link to="/register">Sign Up</Link>
</form>
);
}
}
function mapStateToProps(state) {
return {
userData: state.user,
};
}
export default connect(mapStateToProps)(Login);
You need to install connected-react-router to manipulate with a history inside redux:
import { push } from 'connected-react-router';
export const loginUser = (dataSubmitted) => {
return (dispatch) => {
dispatch(loginUserRequest());
axios
.post("/api/users/login", dataSubmitted)
.then((response) => {
dispatch(loginUserSuccess(response.data));
if (response.payload.success) {
sessionStorage.setItem("jwt", response.payload.token);
sessionStorage.setItem("userId", response.payload._id);
push("/");
} else {
errors.push("Username and/or Password is not correct");
}
})
.catch((err) => {
dispatch(loginUserFailed(err));
});
};
};

Prop does not change from true to false in react, redux, node app

As the title says, I have a check in RegistrationForm for checking if user email exists in the database. The prop is userExists. Now, if it's true, that means User email exists, so the user should be shown a message that says- "User already exists." Now, if it's false, he should be successfully registered and redirected to login.
But right now, I cannot even register with any email. In the network tab, it returns a 200 and gives
the response:
{"message":"User with this email does not exist"}
Can someone show me the proper way to do it? I mean the check, and the message from the backend, through redux, and back to the component.
My code is jibberish.
RegistrationForm Component
import React, { Component } from "react";
import { registerUser, checkValidUser } from "../../actions/userActions";
import { connect } from "react-redux";
import validator from "validator";
import { Link } from "react-router-dom";
import { toastError } from "../../../utils/toastify";
class RegistrationForm extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
email: "",
password: "",
};
}
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value,
});
};
handleSubmit = async (event) => {
event.preventDefault();
const { username, email, password } = this.state;
const registrationData = {
username: this.state.username,
email: this.state.email,
password: this.state.password,
};
if (!username || !email || !password) {
return toastError("Credentials should not be empty");
}
if (username.length < 6) {
return toastError("Username should be greater than 6 characters.");
}
if (!validator.isEmail(email)) {
return toastError("Invalid email.");
}
if (password.length < 6) {
return toastError("Password must contain 6 characters.");
}
await this.props.dispatch(checkUserExists(email));
const userExists = this.props.userExists;
if (!userExists) {
this.props.dispatch(
registerUser(registrationData, () => {
this.props.history.push("/login");
})
);
} else {
toastError("User with this email already exisits"); // I'm not sure how to show the message if the user email already exists. I want to show the message from backend, but currently I'm just doing it manually
}
};
render() {
const isRegistrationInProgress = this.props.isRegistrationInProgress;
return (
<div>
<div className="field">
<p className="control has-icons-left has-icons-right">
<input
onChange={this.handleChange}
name="username"
value={this.state.username}
className="input"
type="text"
placeholder="Username"
/>
<span className="icon is-small is-left">
<i className="fas fa-user"></i>
</span>
</p>
</div>
<div className="field">
<p className="control has-icons-left has-icons-right">
<input
onChange={this.handleChange}
name="email"
value={this.state.email}
className="input"
type="email"
placeholder="Email"
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
</p>
</div>
<div className="field">
<p className="control has-icons-left">
<input
onChange={this.handleChange}
name="password"
value={this.state.password}
className="input"
type="password"
placeholder="Password"
/>
<span className="icon is-small is-left">
<i className="fas fa-lock"></i>
</span>
</p>
</div>
<div className="field">
<div className="control">
{isRegistrationInProgress ? (
<button className="button is-success is-loading">Sign Up</button>
) : (
<button onClick={this.handleSubmit} className="button is-success">
Sign up console.log("registrationData", registrationData)
</button>
)}
<Link to="/login">
<p className="has-text-danger">
Already have an account? Sign In
</p>
</Link>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isRegistrationInProgress: state.registration.isRegistrationInProgress,
userExists: state.registration.userExists,
};
};
export default connect(mapStateToProps)(RegistrationForm);
checkUserExists action
export const checkUserExists = (email) => {
return async (dispatch) => {
try {
const res = await axios.get(`${baseUrl}/users/checkUserExists/${email}`)
console.log("res=>", res)
if (res.data.message = "User with this email already exists") {
dispatch({
type: "CHECK_USER_EXISTS_SUCCESS",
})
}
} catch (err) {
console.log("error=>", err)
}
}
}
checkUserExists controller function
checkUserExists: async (req, res, next) => {
const { email } = req.params
try {
const user = await User.findOne({ email })
if (user) {
return res.status(200).json({ message: "User with this email already exists" })
} else {
return res.json({ message: "User with this email does not exist" })
}
} catch (error) {
return next(error)
}
}
registerUser action
export const registerUser = (registrationData, redirect) => {
return async (dispatch) => {
dispatch({ type: "REGISTRATION_STARTS" })
try {
const res = await axios.post(
`${baseUrl}/users/register`,
registrationData
)
dispatch({
type: "REGISTRATION_SUCCESS",
data: { user: res.data.user },
})
toastSuccess("Successfully registered")
redirect()
} catch (err) {
dispatch({
type: "REGISTRATION_ERROR",
data: { error: err },
})
}
}
}
registration reducer
const initialState = () => ({
isRegistrationInProgress: false,
isRegistered: false,
registrationError: null,
user: {},
userExists: false,
error: null,
});
const registration = (state = initialState, action) => {
switch (action.type) {
case "REGISTRATION_STARTS":
return {
...state,
isRegistrationInProgress: true,
registrationError: null,
};
case "REGISTRATION_SUCCESS":
return {
...state,
isRegistrationInProgress: false,
registrationError: null,
isRegistered: true,
user: action.data,
};
case "REGISTRATION_ERROR":
return {
...state,
isRegistrationInProgress: false,
registrationError: action.data.error,
isRegistered: false,
user: {},
};
case "CHECK_USER_EXISTS_SUCCESS":
return {
...state,
userExists: true,
error: null
};
default:
return state;
}
};
export default registration;

Set Input Value based on React State after componentDidMount?

I'm using Axios to perform a GET request and then store the information into the {information} property, and then I store all of that in my state. How can I use setState to set the state of the input fields after the compoenntDidMount()?? I've tried a .then() after the this.props.getItemCredential, but it doesn't work because it's not a standard fetch request?
I've also tried to set the state of the input value like: {this.state.profile.credential.itemArray[0].company} and it returns undefined even though I see it in my redux chrome extension as properly being in the state.
I think the issue is that it's returning undefined cause it cannot change the value of the input until the getItemCredential returns, but then how do I wait for it to finish and then adjust the: this.state.(whatever) for each one?
What my component looks like:
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
import TextFieldGroup from '../../common/TextFieldGroup';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getItemCredential, getCurrentProfile } from '../../../actions/profileActions';
import Spinner from '../../common/Spinner';
class EditInfo extends Component {
constructor(props) {
super(props);
this.state = {
company: '',
title: '',
location: '',
errors: {},
disabled: false
}
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
componentDidMount() {
this.props.getItemCredential(this.props.match.params.id)
this.props.getCurrentProfile()
}
onChange = (e) => {
this.setState({[e.target.name]: e.target.value});
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({errors: nextProps.errors});
}
}
onSubmit = (e) => {
e.preventDefault();
const itemData = {
company: this.state.company,
title: this.state.title,
location: this.state.location,
}
}
render() {
const { errors } = this.state;
const { profile, loading } = this.props.profile;
const { credential } = this.props.profile;
let editContent;
if (profile === null || loading) {
editContent = <Spinner />
} else {
editContent = (
<React.Fragment>
<div>
<form onSubmit={this.onSubmit}>
<TextFieldGroup
placeholder={this.state.company}
name="company"
value={this.state.company}
onChange={this.onChange}
error={errors.company}
required
/>
<TextFieldGroup
placeholder="* Job Title"
name="title"
value={this.state.title}
onChange={this.onChange}
error={errors.title}
required
/>
<TextFieldGroup
placeholder="Location"
name="location"
value={this.state.location}
onChange={this.onChange}
error={errors.location}
required
/>
<div class="col-xl-4 col-lg-4 col-md-4 col-sm-12 text-center my-auto">
<input type="submit" value="Submit" class="button text-center m-auto add-credentials-button mt-4" />
</div>
</form>
</div>
);
}
return (
<div>
{editContent}
</div>
)
}
}
EditInfo.propTypes = {
profile: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
getItemCredential: PropTypes.func.isRequired,
getCurrentProfile: PropTypes.func.isRequired,
credential: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
profile: state.profile,
credential: state.credential,
errors: state.errors
});
export default connect(mapStateToProps, { addExperience, getExperienceCredential, getCurrentProfile })(withRouter(EditExperience));
Here is what my state markup looks like:
profile: {
credential: {
itemArray: [
0: {
title: 'Some entry',
company: 'Some entry',
Location: 'some entry'
}
]
}
}
Here is the axios request I'm making:
export const getItemCredential = (id) => dispatch => {
dispatch(setProfileLoading());
axios.get(`/profile/item-credential/${id}`)
.then(res =>
dispatch({
type: GET_ITEM_CREDENTIAL,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
Here is what my reducer looks like:
import { GET_PROFILE, PROFILE_LOADING, GET_ITEM_CREDENTIAL } from '../actions/types';
const initialState = {
profile: null,
credential: null,
loading: false
}
export default function(state = initialState, action) {
switch(action.type) {
case PROFILE_LOADING:
return {
...state,
loading: true
}
case GET_PROFILE:
return {
...state,
profile: action.payload,
loading: false
}
case GET_ITEM_CREDENTIAL:
return {
...state,
credential: action.payload
}
default:
return state;
}
}
You can do a .then if you change getItemCredential so it returns a Promise:
export const getItemCredential = (id) => dispatch => {
dispatch(setProfileLoading());
return axios.get(`/profile/item-credential/${id}`)
.then(res => {
dispatch({
type: GET_ITEM_CREDENTIAL,
payload: res.data
});
return res.data;
})
.catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
});
throw err;
});
}
Notice the added return in getItemCredential. Also, notice that res.data is returned from the Promise so that it can be accessed by your component that awaits it. I also re-threw the caught error so that the Promise continues in the rejected state instead of resolving to undefined.

Update component when the store changes

I'm trying to update a component based on store updates, the objective of that is, when I click on a table item, I want to update the form buttons to edit form, and Edit the table item.
My source code:
I have an action which updates currentUser. currentUser is the user I want to update
src/actions/user.js
export const updateCurrentUserSuccess = (currentUser) => {
return {
type: UPDATE_CURRENT_USER,
currentUser
}
}
export const updateCurrentUser = (id) => {
return (dispatch) => {
return axios.get(`${apiUrl}/users/${id}`)
.then(response => {
console.log(response.data.data)
dispatch(updateCurrentUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
my currentUserReducer:
src/reducers/currentUserReducer.js
import { UPDATE_CURRENT_USER } from '../constants/ActionTypes';
const initialState = {
currentUser: [],
}
export default function userReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_CURRENT_USER:
return action.currentUser;
default:
return state;
}
}
now my components:
my NewUser form:
src/components/NewUser.js
import React, { Component } from 'react';
import { Store } from '../store'
class NewUser extends Component {
state = {
id: '',
name: '',
cpfcnpj: '',
isEdit: false
};
componentDidMount(){
this.handleUserChange()
}
handleInputChange = e => {
this.handleUserChange();
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
if (!this.state.isEdit) {
if (this.state.name.trim() && this.state.cpfcnpj.trim()) {
this.props.onAddUser(this.state);
this.handleReset();
}
} else {
if (this.state.name.trim() && this.state.cpfcnpj.trim() && this.state.id !== '') {
this.props.onEdit(this.state);
this.handleReset();
}
}
};
handleReset = () => {
Store.getState().currentUser = []
this.setState({
id: '',
name: '',
cpfcnpj: '',
isEdit: false
});
};
handleUserChange() {
console.log('store', Store.getState().currentUser._id);
if (Store.getState().currentUser._id !== undefined) {
this.setState({
id: Store.getState().currentUser._id,
name: Store.getState().currentUser.name,
cpfcnpj: Store.getState().currentUser.cpfcnpj,
isEdit: true
});
}
}
render() {
return (
<div>
<form className="form-inline" onSubmit={this.handleSubmit}>
<div className="form-group margin-right">
<input
type="text"
placeholder="Name"
className="form-control"
name="name"
onChange={this.handleInputChange}
value={this.state.name}
/>
</div>
<div className="form-group margin-right">
<input
type="text"
placeholder="CPF/CNPJ"
className="form-control"
name="cpfcnpj"
onChange={this.handleInputChange}
value={this.state.cpfcnpj}>
</input>
</div>
<div className="form-group">
<button type="submit" className={this.state.isEdit ? "btn btn-success margin-right hidden" : "btn btn-success margin-right"}>
<span className="glyphicon glyphicon-plus" aria-hidden="true"></span>
Adicionar
</button>
<button type="submit" className={this.state.isEdit ? "btn btn-primary margin-right" : "btn btn-primary margin-right hidden"}>
<span className="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
Salvar
</button>
<button type="button" className="btn btn-default margin-right" onClick={this.handleReset}>
<span className="glyphicon glyphicon-erase" aria-hidden="true"></span>
Limpar
</button>
</div>
</form>
</div>
);
}
}
export default NewUser;
my component User item:
***src/components/User.js***
import React from 'react';
export default ({ user: { name, cpfcnpj, _id }, onDelete, onEditUser }) => {
return (
<tr>
<th scope="row">{name}</th>
<td>{cpfcnpj}</td>
<td>
<button className="btn btn-warning btn-xs margin-right" type="button" onClick={() => onEditUser(_id)}>
<span className="glyphicon glyphicon-edit" aria-hidden="true"> </span>
Editar
</button>
<button className="btn btn-danger btn-xs margin-right" type="button" onClick={() => onDelete(_id)}>
<span className="glyphicon glyphicon-trash" aria-hidden="true"> </span>
Excluir
</button>
</td>
</tr>
);
};
now my smart components:
src/containers/UserList.js
import React from 'react';
import { connect } from 'react-redux';
import User from '../components/User';
import { deleteUser, updateCurrentUser } from '../actions/user';
import NewUser from '../components/NewUser';
function UserList({ users, onDelete, onEditUser }) {
if (!users.length) {
return (
<div className="margin-top">
No Users
</div>
)
}
return (
<div className="margin-top">
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Nome</th>
<th scope="col">CPF/CNPJ</th>
</tr>
</thead>
<tbody>
{users.map(user => {
return (
<User user={user} onDelete={onDelete} onEditUser={onEditUser} key={user._id} />
);
})}
</tbody>
</table>
</div>
);
}
const mapStateToProps = state => {
return {
users: state.users
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteUser(id));
},
onEditUser: (id) => {
dispatch(updateCurrentUser(id))
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList, NewUser);
src/containers/CreateUser.js
import { connect } from 'react-redux';
import { createUser, updateUser } from '../actions/user';
import NewUser from '../components/NewUser';
const mapDispatchToProps = dispatch => {
return {
onAddUser: user => {
dispatch(createUser(user));
},
onEdit: (id, name, cpfcnpj) => {
dispatch(updateUser(id, name, cpfcnpj))
}
};
};
export default connect(
null,
mapDispatchToProps
)(NewUser);
src/App.js
import React, { Component } from 'react';
import CreateUser from './containers/CreateUser';
import UserList from './containers/UserList';
import './css/main.css'
class App extends Component {
render() {
return (
<div className="container">
<h1 className="styles-app">Usuários</h1>
<div className="row styles-app">
<div className="col-md-12">
<CreateUser />
</div>
<div className="col-md-12">
<UserList />
</div>
</div>
</div>
);
}
}
export default App;
Here is something you might try. Connect your NewUser.js to the store.
import { connect } from 'react-redux;
export default connect(mapStateToProps)(NewUser);
Then map your currentUser state to props.
const mapStateToProps = state => {
return {
currentUser: state.currentUser
};
};
In your currentUserReducer
initialState = {
//Assuming these are the only values in response
id: '',
name: '',
cpfcnpj: '',
isEdit: false
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_CURRENT_USER:
return {
...state,
id: action.currentUser.id,
name: action.currentUser.name,
cpfcnpj: action.currentUser.cpfcnpj,
isEdit: true
};
default:
return state;
}
}
You should have access to the current user object now in props.
Then in your input value field
value={this.props.currentUser.name}
value={this.props.currentUser.cpfcnpj}
You may also need to do a check to see if these values have been updated. Also, not sure if the placeholder text might interfere.
Hope this gets you closer to the solution.
Edit
In the case of clearing props, you might just add another action to do so.
In your actions for currentUser:
export const clearUserData = () => {
return {
type: CLEAR_USER_DATA,
}
}
And in your reducer:
export default function userReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_CURRENT_USER:
return {
...state,
id: action.currentUser.id,
name: action.currentUser.name,
cpfcnpj: action.currentUser.cpfcnpj,
isEdit: true
};
case CLEAR_USER_DATA:
return {
...state,
id: '',
name: '',
cpfcnpj: '',
isEdit: false
};
default:
return state;
}
}
Add the clearUserData action to execute after you submit your edits and it should reset your reducer. You might even be able to just do
return {
...state,
initialState
};

How to update state in react-redux?

I have made a submit form. I have made userReducer where user[] is array and each array element has firstname, lastname,emailid etc. When I click on submit button it shows array element [0] but when I click on clear button and again try to fill in the form and try to submit no user is added again i.e no state is updated. How to fix this problem ?
Form component (form.js) :
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as action from '../actions/actions';
import './form.css';
class Form extends Component {
constructor(props) {
super(props);
this.setFirstName = this.setFirstName.bind(this);
this.setLastName = this.setLastName.bind(this);
this.setEmailId = this.setEmailId.bind(this);
this.setIban = this.setIban.bind(this);
this.setBankName = this.setBankName.bind(this);
this.showUser = this.showUser.bind(this);
this.reset = this.reset.bind(this);
console.log(this.props);
}
setFirstName(event) {
this.props.dispatch(action.setFirstName(event.target.value));
}
setLastName(event) {
this.props.dispatch(action.setLastName(event.target.value));
}
setEmailId(event) {
this.props.dispatch(action.setEmailId(event.target.value));
}
setIban(event) {
this.props.dispatch(action.setIban(event.target.value));
}
setBankName(event) {
this.props.dispatch(action.setBankName(event.target.value));
}
showUser(){
const jsonobj = this.props;
alert(JSON.stringify(jsonobj));
}
reset(){
this.props.dispatch(action.setFirstName(''));
this.props.dispatch(action.setLastName(''));
this.props.dispatch(action.setEmailId(''));
this.props.dispatch(action.setIban(''));
this.props.dispatch(action.setBankName(''));
}
render(){
return(
<div>
<div id="center">
<form>
<div className="form-group">
<label htmlFor="firstname">First Name:</label>
<input type="firstname" className="form-control" id="firstname" value={this.props.firstname} onChange={this.setFirstName} required/>
</div>
<div className="form-group">
<label htmlFor="lastname">Last Name:</label>
<input type="lastname" className="form-control" id="lastname" value={this.props.lastname} onChange={this.setLastName} required/>
</div>
<div className="form-group">
<label htmlFor="email">Email address:</label>
<input type="email" className="form-control" id="email" value={this.props.emailid} onChange={this.setEmailId} required/>
</div>
<div className="form-group">
<label htmlFor="bankacc">IBAN:</label>
<div id="deletebank" className="items">
<input type="bankacc" className="form-control" id="bankacc" value={this.props.iban} onChange={this.setIban} required/>
<button type="button" className="btn btn-default btn-sm">
<span className="glyphicon glyphicon-trash"></span>
</button>
</div>
</div>
<div className="form-group">
<label htmlFor="bankname">Bank Name:</label>
<input type="bankname" className="form-control" id="bankname" value={this.props.bankname} onChange={this.setBankName} required/>
</div>
<div className="form-group">
<div id="buttons" className="items">
<button type="button" class="btn btn-warning" onClick={this.reset}>Clear Input</button>
<button type="button" className="btn btn-success" onClick={this.showUser}>Submit</button>
</div>
</div>
</form>
</div>
</div>
)}
}
const mapStateToProps = store => {
return {
firstname: store.user.firstname,
lastname: store.user.lastname,
emailid: store.user.emailid,
iban: store.user.iban,
bankname: store.user.bankname
}
}
export default connect(mapStateToProps)(Form);
reducer.js:
const userReducer = (state = {
user:[{
firstname:'',
lastname:'',
emailid:'',
bankaccounts:{
iban:'',
bankname:''
}
}]
}, action) => {
switch (action.type) {
case 'SET_FIRSTNAME':{
return {
...state,
user:{...state.user, firstname: action.payload}
}
}
case 'SET_LASTNAME':{
return {
...state,
user:{...state.user, lastname: action.payload}
}
}
case 'SET_EMAILID':{
return {
...state,
user:{...state.user, emailid: action.payload}
}
}
case 'SET_IBAN':{
return {
...state,
user:{...state.user, iban: action.payload}
}
}
case 'SET_BANKNAME':{
return {
...state,
user:{...state.user, bankname: action.payload}
}
}
default: return state;
}
}
export default userReducer;
Actions.js:
export const SET_FIRSTNAME = 'SET_FIRSTNAME';
export const SET_LASTNAME = 'SET_LASTNAME';
export const SET_EMAILID = 'SET_EMAILID';
export const SET_IBAN = 'SET_IBAN';
export const SET_BANKNAME = 'SET_BANKNAME';
export function setFirstName(firstname){
return {
type:SET_FIRSTNAME,
payload:firstname
}
}
export function setLastName(lastname){
return {
type:SET_LASTNAME,
payload:lastname
}
}
export function setEmailId(emailid){
return {
type:SET_EMAILID,
payload:emailid
}
}
export function setIban(iban){
return {
type:SET_IBAN,
payload:iban
}
}
export function setBankName(bankname){
return {
type:SET_BANKNAME,
payload:bankname
}
}
store.js:
import { createStore } from 'redux';
import userReducer from './reducers/reducers';
const store = createStore(userReducer);
store.subscribe(() => {
console.log('Store changed', store.getState());
})
export default store;
Screenshot:
There's a bunch of stuff to address here, but the primary issue is that your state is expecting user to be an array. I think it would be very wise to rename this users as to not get confused:
(I'm going to remove some keys to make this easier)
const initialState = {
users: [ // note "users" not "user"
{
firstname: '',
lastname: '',
},
],
};
Your reducer switch statements don't specify WHICH user it should be updating. If you want to just start with "adding" a new user it might look something like this:
const userReducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_USER": {
return {
...state,
users: [...state.users, action.payload],
};
}
default:
return state;
}
};
Here we add a new user to the users array in the form of action.payload which should contain all the keys you want on your user.
You can now have an action creator that's a bit more concise
const addUser = user => ({
type: 'ADD_USER',
payload: user,
})
And your form could be simplified a lot:
import * as actions from './actions'
class Form extends React.Component {
state = {
firstname: '',
lastname: '',
}
render() {
return (
<form onSubmit={() => {
this.props.dispatch(actions.addUser(this.state))
}}>
<input
value={this.state.firstname}
onChange={e => this.setState({ firstname: e.target.value })
/>
<input
value={this.state.lastname}
onChange={e => this.setState({ lastname: e.target.value })
/>
</form>
)
}
}
export default connect()(Form)

Categories