How to implement Redux Slicer - reactjs

Hello I am working on a Registration app, I was using useState hook to set the user information
and this way it works fine
import React, { useState } from 'react';
import axios from 'axios';
const Registration = (props) => {
const [user, setUser] = useState({
email: '',
password: '',
password_confirmation: '',
});
const { email, password, password_confirmation } = user;
const handlChange = (event) => {
setUser({ ...user, [event.target.name]: event.target.value });
};
const handleSubmit = (event) => {
axios
.post(
'http://localhost:3001/registrations',
{
user: {
email: email,
password: password,
password_confirmation: password_confirmation,
},
},
{ withCredentials: true },
)
.then((response) => {
if (response.data.status === 'created') {
props.handleSuccessfulAuth(response.data);
}
})
.catch((error) => {
console.log('registration error', error);
});
event.preventDefault();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
placeholder="Email"
value={email}
onChange={handlChange}
required
/>
<input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={handlChange}
required
/>
<input
type="password"
name="password_confirmation"
placeholder="Confirm Password"
value={password_confirmation}
onChange={handlChange}
required
/>
<button tupe="submit">Register</button>
</form>
</div>
);
};
export default Registration;
now the issue is that I have to use Redux to manage the state ,so created a slicer
here is my slicer ,
the problem i have is that i don't know how to connect redux to my registration component,
for example I am assuming that the information provided in the form is the payload so i will be passing it to my reducer as the action.payload and i think i am setting the state with the information from the payload.
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
email: '',
password: '',
password_confirmation: '',
};
const registrationSlice = createSlice({
name: 'registration',
initialState,
reducers: {
setUsers: (state, action) => {
const { email, password, password_confirmation } = action.payload;
state = {
email,
password,
password_confirmation,
};
},
},
});
export const { setUsers } = registrationSlice.actions;
export default registrationSlice.reducer;
the problem is how do i connect this to my component, I read in the Redux Toolkit website I have to use dispatch and useSelector,
so that is what i think i am doing here but is not working,
I am stuck at this point and don't know how to solve it. Can someone please help me understand how to fix it and make it work.
import React, { useState } from 'react';
import axios from 'axios';
import { useDispatch, useSelector } from 'react-redux';
import { setUsers } from '../../features/user/registrationSlice';
const Registration = (props) => {
const dispatch = useDispatch();
const user = useSelector((state) => state.user);
// const { email, password, password_confirmation } = user;
const handlChange = (event) => {
dispatch(setUsers({ [event.target.name]: event.target.value }));
};
const handleSubmit = (event) => {
axios
.post(
'http://localhost:3001/registrations',
{
user: {
email: email,
password: password,
password_confirmation: password_confirmation,
},
},
{ withCredentials: true },
)
.then((response) => {
if (response.data.status === 'created') {
props.handleSuccessfulAuth(response.data);
}
})
.catch((error) => {
console.log('registration error', error);
});
event.preventDefault();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
placeholder="Email"
value={email}
onChange={handlChange}
required
/>
<input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={handlChange}
required
/>
<input
type="password"
name="password_confirmation"
placeholder="Confirm Password"
value={password_confirmation}
onChange={handlChange}
required
/>
<button tupe="submit">Register</button>
</form>
</div>
);
};
export default Registration;

Issue
The issue here is that your reducer case is expecting a "full set" of state properties to update all at once but the UI is dispatching actions for each sub-state individually.
Solution
I suggest updating the reducer, or rather, add a new reducer to handle setting individual state properties. This leaves the possibility to set the entire state if you want.
const registrationSlice = createSlice({
name: 'registration',
initialState,
reducers: {
setUsers: (state, action) => {
const { email, password, password_confirmation } = action.payload;
state {
email,
password,
password_confirmation,
};
},
setUserProperty: (state, action) => {
const { name, value } = action.payload;
state[name] = value;
},
},
});
And dispatch the new setUserProperty action.
const handlChange = (event) => {
dispatch(setUserProperty({
name: event.target.name,
value: event.target.value,
}));
};
Since you are using Redux-toolkit you may want to eventually consider moving all the asynchronous axios POST request logic into a thunk via the createAsyncThunk. The thunk will have access to the store to get the user values and make the asynchronous calls. This will allow you to further decouple the Registration component from auth/registration logic and your redux state.

Related

Multiple args using createAsyncThunk

I try to rewrite authentication tutorial from javascript into typescript. I encounter a problem as for createAsyncThunk arguments.
store.ts
import { configureStore } from "#reduxjs/toolkit";
import userReducer from './features/user';
export const store = configureStore({
reducer: {
user: userReducer,
},
devTools: true
})
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
user.ts
It seams there are some issue with arguments, any idea how to manage it using typescript?
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import {RootState} from "../store"
import { API_URL } from "../config/index";
export interface UserState {
isAuthenticated: boolean,
user: null,
loading: boolean,
registered: boolean
}
const initialState: UserState = {
isAuthenticated: false,
user: null,
loading: false,
registered: false
}
export const register = createAsyncThunk('/api/user/create/', async ({name, email, password}, thunkAPI) => {
const body = JSON.stringify({name, email, password})
try {
const res = await fetch(`${API_URL}/api/user/create/`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body
})
// const data: UserState[] = await res.json();
const data = await res.json();
if (res.status === 201) {
return data;
} else {
return thunkAPI.rejectWithValue(data);
}
} catch(err: any) {
return thunkAPI.rejectWithValue(err.response.data);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
resetRegistered(state) {
state.registered = false;
},
},
extraReducers: builder => {
builder
.addCase(register.pending, state => {
state.loading = true;
})
.addCase(register.fulfilled, state => {
state.loading = false;
state.registered = true;
})
.addCase(register.rejected, state => {
state.loading = false;
})
}
})
export const selectUser = (state: RootState) => state.user;
export const { resetRegistered } = userSlice.actions
export default userSlice.reducer
Register.tsx (here I get error when dispatch register() -> Expected 0 arguments, but got 1)
import { useState } from "react";
import Layout from '../components/Layout';
import { Navigate } from "react-router-dom";
import { MDBInput} from "mdb-react-ui-kit";;
import { useAppDispatch, useAppSelector } from '../hooks'
import { register, selectUser } from '../features/user';
const initialState = {
name: "",
email: "",
password: "",
confirmPassword: "",
}
const RegisterPage = () => {
const [formData, setFormData] = useState(initialState);
const {name, email, password, confirmPassword} = formData;
const [showRegister, setShowRegister] = useState(false);
const {registered, loading} = useAppSelector(selectUser);
const dispatch = useAppDispatch();
const handleChange = (e: any) => {
setFormData({ ...formData, [e.target.name]: e.target.value})
};
const onSubmit = (e: any) => {
e.preventDefault();
dispatch(register({name, email, password})); //Expected 0 arguments, but got 1
}
if (registered) {
return <Navigate to='/login'/>
}
return (
<Layout>
<form onSubmit={onSubmit} className="mt-5">
<div className="form-group">
<label htmlFor="name" className="form-label">
<MDBInput
type="text"
name="name"
value={name}
onChange={handleChange}
label="Name"
className="form-control form-control-lg"
/>
</label>
<label htmlFor="email" className="form-label">
<MDBInput
type="email"
name="email"
value={name}
onChange={handleChange}
label="Email"
className="form-control form-control-lg"
/>
</label>
<label htmlFor="password" className="form-label">
<MDBInput
type="password"
name="password"
value={name}
onChange={handleChange}
label="Password"
className="form-control form-control-lg"
/>
</label>
</div>
<button
className="btn btn-outline-light btn-lg px-5"
>
Register
</button>
</form>
</Layout>
)
}
export default RegisterPage;

error messages under the relevant input fields automatically from Django Rest API by react hooks

i want to display every error messages from Django Rest Api automatically in React frontend. i wanted to test my concept with the signup authentication function and after fixing it i would like to use the concept in every components in fetching data from or into django API.
here is my App.js to register a user just for test:
import React, { useState } from "react";
export default function Signup() {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password1, setPassword1] = useState("");
const [password2, setPassword2] = useState("");
const [user, setUser] = useState("");
function handleEmail(evt) {
setEmail(evt.target.value);
}
function handleUsername(evt) {
setUsername(evt.target.value);
}
function handlePassword1(evt) {
setPassword1(evt.target.value);
}
function handlePassword2(evt) {
setPassword2(evt.target.value);
}
function handle_signup(evt) {
evt.preventDefault();
fetch("http://127.0.0.1:8000/api/v1/rest-auth/registration/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, email, password1, password2 }),
})
.then((res) => res.json())
.then((json) => {
localStorage.setItem("token", json.key);
console.log(json);
setUser(json.username);
})
.catch((err) => {
if(err.res){
console.log(err.res.username)
console.log(err.res.email);
console.log(err.res.password1);
console.log(err.res.password2);
}else if(err.res){
console.log(err.res)
}else{
console.log('Error',err.message)
}
console.log(err.config);
});
}
return (
<form onSubmit={(evt) => handle_signup(evt, setUser())}>
<label htmlFor="register-username">Username:</label>
<input
type="text"
value={username}
onChange={handleUsername}
name="register-username"
id="register-username"
/>
<label htmlFor="register-email">Email:</label>
<input
type="text"
value={email}
onChange={handleEmail}
name="register-username"
id="register-username"
/>
<label htmlFor="register-password1">Password1:</label>
<input
type="password1"
value={password1}
onChange={handlePassword1}
name="register-password1"
id="register-password1"
/>
<label htmlFor="register-password2">password2:</label>
<input
type="password2"
value={password2}
onChange={handlePassword2}
name="register-password2"
id="register-password2"
/>
<input type="submit" value="Register" />
</form>
);
}
in UseEffect i have tried to show every error message under relevant input boxes which are username, email, password1, password2, i tried to do it by React-hook-form but it will be like inserting error messages from frontend. but i want to show actual error messages from backend. in development tools, when i try upper codes by putting wrong infos in input boxes, it would only show POST: 400 (bad request)
how can i show such error messages under every input boxes like Username exist or email address is invalid, or password must be at least 8 which are typical in Django Rest API's typical error messages ?
FYI: this code can register any user if the input boxes are correctly filled up.
The code below is from my article React Token-Based Authentication to Django REST API Backend. It is using react-bootstrap. It is a simple example with username and password, but you can easily extend it.
If there is a known error (axios docs about handling errors) I check if it has a message for username or password. If yes, then I set an error message for FormControl.Feedback. If you don't want to use react-bootstrap you can just make a small red text inside div and make it visible only if the error message is set (not empty).
// frontend/src/components/SignupReducer.js
// import needed actions
import {
CREATE_USER_ERROR,
CREATE_USER_SUBMITTED,
CREATE_USER_SUCCESS
} from "./SignupTypes";
// define the initial state of the signup store
const initialState = {
usernameError: "",
passwordError: "",
isSubimtted: false
};
// define how action will change the state of the store
export const signupReducer = (state = initialState, action) => {
switch (action.type) {
case CREATE_USER_SUBMITTED:
return {
usernameError: "",
passwordError: "",
isSubimtted: true
};
case CREATE_USER_ERROR:
const errorState = {
usernameError: "",
passwordError: "",
isSubimtted: false
};
if (action.errorData.hasOwnProperty("username")) {
errorState.usernameError = action.errorData["username"];
}
if (action.errorData.hasOwnProperty("password")) {
errorState.passwordError = action.errorData["password"];
}
return errorState;
case CREATE_USER_SUCCESS:
return {
usernameError: "",
passwordError: "",
isSubimtted: false
};
default:
return state;
}
}
// frontend/src/components/signup/SignupActions.js
import axios from "axios";
import { toast } from "react-toastify";
import { isEmpty } from "../../utils/Utils";
import {
CREATE_USER_ERROR,
CREATE_USER_SUBMITTED,
CREATE_USER_SUCCESS
} from "./SignupTypes";
export const signupNewUser = userData => dispatch => {
dispatch({ type: CREATE_USER_SUBMITTED }); // set submitted state
axios
.post("/api/v1/users/", userData)
.then(response => {
toast.success(
"Account for " +
userData.username +
" created successfully. Please login."
);
dispatch({ type: CREATE_USER_SUCCESS });
})
.catch(error => {
if (error.resposne) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
toast.error(JSON.stringify(error.response.data));
dispatch({
type: CREATE_USER_ERROR,
errorData: error.response.data
});
} else if (error.message) {
// the error message is available,
// let's display it on error toast
toast.error(JSON.stringify(error.message));
} else {
// strange error, just show it
toast.error(JSON.stringify(error));
}
});
};
// frontend/src/components/signup/Signup.js file
import React, { Component } from "react";
import { withRouter } from "react-router-dom"; // new import
import { connect } from "react-redux"; // new import
import PropTypes from "prop-types"; // new import
import { Link } from "react-router-dom";
import {
Container,
Button,
Row,
Col,
Form,
FormControl
} from "react-bootstrap";
import { signupNewUser } from "./SignupActions"; // new import
class Signup extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: ""
};
}
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
// update function to call the action
onSignupClick = () => {
const userData = {
username: this.state.username,
password: this.state.password
};
this.props.signupNewUser(userData); // <-- signup new user request
};
render() {
return (
<Container>
<Row>
<Col md="4">
<h1>Sign up</h1>
<Form>
<Form.Group controlId="usernameId">
<Form.Label>User name</Form.Label>
<Form.Control
isInvalid={this.props.createUser.usernameError}
type="text"
name="username"
placeholder="Enter user name"
value={this.state.username}
onChange={this.onChange}
/>
<FormControl.Feedback type="invalid">
{this.props.createUser.usernameError}
</FormControl.Feedback>
</Form.Group>
<Form.Group controlId="passwordId">
<Form.Label>Your password</Form.Label>
<Form.Control
isInvalid={this.props.createUser.passwordError}
type="password"
name="password"
placeholder="Enter password"
value={this.password}
onChange={this.onChange}
/>
<Form.Control.Feedback type="invalid">
{this.props.createUser.passwordError}
</Form.Control.Feedback>
</Form.Group>
</Form>
<Button color="primary" onClick={this.onSignupClick}>
Sign up
</Button>
<p className="mt-2">
Already have account? <Link to="/login">Login</Link>
</p>
</Col>
</Row>
</Container>
);
}
}
// connect action and reducer
// replace
// export default Signup;
// with code below:
Signup.propTypes = {
signupNewUser: PropTypes.func.isRequired,
createUser: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
createUser: state.createUser
});
export default connect(mapStateToProps, {
signupNewUser
})(withRouter(Signup));

UseEffect not fired when redux props change caused by async api call with redux-thunk

I have a functional login page connected with redux, I'm firing an async event onSubmit that will trigger the emailLogin action, I am using useEffect to detect the change of the isLoading prop to see whether login finished or not. If login success, the redux store should have the user object, if failed, the user should remain null.
The question is, I know that the login is success, which should triggered the change of isLoading, the parameter that decide whether the useEffect, however, the useEffect is not fired. Also, the console.log('done'); after the line await emailLogin(authData); is never fired. Ssomething is wrong.
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { emailLogin } from '../actions/index';
function Login({ user, isLoading, emailLogin }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const history = useHistory();
useEffect(() => {
console.log('useEffect fired', user, isLoading); //<-----This does not fire after login success
if (user) {
history.push('/protected_home');
}
}, [isLoading]);
const submitEmailLoginForm = async (e) => {
e.preventDefault();
const authData = { email, password };
await emailLogin(authData);
console.log('done'); // <------- This is never fired
};
return (
<div>
<h2>Login</h2>
<Link to="/">back</Link>
<form onSubmit={submitEmailLoginForm}>
<label>
email:
<input
type="text"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
password:
<input
type="text"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
const mapStateToProps = (state) => ({
user: state.user,
isLoading: state.isLoading
});
const mapDispatch = {
emailLogin: emailLogin
};
export default connect(mapStateToProps, mapDispatch)(Login);
My action file:
import axios from 'axios';
export const authActions = {
EMAIL_LOGIN_START: '##EMAIL_LOGIN_START',
EMAIL_LOGIN_SUCCESS: '##EMAIL_LOGIN_SUCCESS'
};
export const emailLogin = ({ email, password }) => async (dispatch) => {
dispatch({ type: authActions.EMAIL_LOGIN_START });
try {
const response = await axios.post('http://localhost:5001/api/auth', {
email: email,
password: password
});
dispatch({
type: authActions.EMAIL_LOGIN_SUCCESS,
payload: {
user: { ...response.data }
}
});
} catch (error) {
console.log('Should dispatch api error', error.response);
}
};
My Reducer:
import { authActions } from '../actions/index';
const initialState = {
user: null,
isLoading: false
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case authActions.EMAIL_LOGIN_START:
return { ...state, isLoading: true };
case authActions.EMAIL_LOGIN_SUCCESS:
console.log('Reducer check => Login is success'); //<-----this line is printed
return { ...state, user: action.payload.user, isLoading: false };
default:
return state;
}
};
export default userReducer;
In the reducer, I see that the success action is actually triggered by checking the console.log(). Also in the redux dev tool, I can actually see that the login is success and the isLoading prop has changed :
This solve my problem
const mapStateToProps = (state) => ({
user: state.userReducer.user,
isLoading: state.userReducer.isLoading
});

In React-Redux app, trying to pre-fill the default value in Edit Component with current api calls value

In my reactredux app, There is a peculiar situaton where I am currently trying to pre-fill my input field in Edit component but the thing is ,Its getting filled but not with current api calls but with last api calls that happens inside componentDidMount().I tried to clear the object too but all in vain. Kindly suggest
ProfileEdit.js component
import React, { Component } from 'react';
import '../App.css';
import {connect} from 'react-redux';
import {profileFetchDetail} from '../actions/profile';
import { withRouter } from 'react-router-dom';
class ProfileEdit extends Component {
constructor(props){
super(props);
this.state = {
firstName: '',
lastName: '',
emailId: '',
}
}
componentDidMount(){
const id = this.props.match.params.id;
this.props.profileFetchDetail(id);
this.setState({
firstName: this.props.profile.firstName,
lastName: this.props.profile.lastName,
emailId: this.props.profile.emailId
})
}
render() {
const {firstName,lastName,emailId} = this.state;
console.log(this.props.profile);
return (
<form name="profileCreate" className="profile-form">
<div className="form-control">
<label htmlFor="firstName">First Name</label><br/>
<input type="text" id="firstName" defaultValue={firstName}
name="firstName" placeholder="First Name"
/>
</div>
<div className="form-control">
<label htmlFor="LastName">Last Name</label><br/>
<input type="text" id="LastName" defaultValue={lastName}
name="lastName" placeholder="Last Name"
/>
</div>
<div className="form-control">
<label htmlFor="email">Email</label><br/>
<input type="email" id="email" defaultValue={emailId}
/>
</div>
<div className="form-action">
<input type="submit" value="Click here" />
</div>
</form>
)
}
}
const mapStateToProps = state => ({
profile: state.profile.profile
})
export default connect(mapStateToProps, {profileFetchDetail})(withRouter(ProfileEdit));
Action creators, here profileFetchDetail() is of our interest
import api from '../api';
// profile create
export const profileAdd = (formData, history) => async dispatch => {
console.log(formData);
const config = {
headers : { 'Content-Type': 'application/json' }
}
try {
await api.post('/api/profile/create', formData, config);
dispatch({ type: 'CREATE_PROFILE', payload: formData });
history.push('/list');
} catch (error) {
console.log(error);
}
}
// profile get all list
export const profileFetch = () => async dispatch => {
try {
const res = await api.get('/api/profile/list');
dispatch({ type: 'GET_PROFILE', payload: res.data });
} catch (error) {
console.log(error);
}
}
// profile get single list item corresponding to id
export const profileFetchDetail = (id) => async dispatch => {
dispatch({ type: 'CLEAR_PROFILE' });
try {
const res = await api.get(`/api/profile/${id}`);
dispatch({ type: 'GET_PROFILE_SINGLE', payload: res.data });
} catch (error) {
console.log(error);
}
}
// profile delete
export const profileDelete = (id) => async dispatch => {
dispatch({ type: 'CLEAR_PROFILE' });
try {
const res = await api.delete(`/api/profile/${id}/delete`);
dispatch({ type: 'DELETE_PROFILE', payload: res.data });
dispatch(profileFetch());
} catch (error) {
console.log(error);
}
}
ProfileReducers
const initialState = {
profiles:[],
profile:{}
};
export default (state = initialState, action) => {
switch (action.type) {
case 'CREATE_PROFILE':
return {...state, profiles: [...state.profiles, action.payload]};
case 'GET_PROFILE':
return {...state, profiles: action.payload};
case 'GET_PROFILE_SINGLE':
return {...state, profile: action.payload};
case 'CLEAR_PROFILE':
return {...state, profile: {}};
case 'DELETE_PROFILE':
return {...state, profiles: state.profiles.filter( item => item._id !== action.payload) };
default:
return state;
}
};
First time it loads perfectly on clicking edit button then the issue happens on clicking any other edit button.Pasting the example of 2 api calls inside componentDidMount().
In the attached image, the last api request in sequence displayed is the currently made request.Api made detail
Note: Till now I am not trying to edit it just prefilling data,where issue happening.

Set Redux State in handleSubmit method of Formik React form

I want to set a variable in the redux store that is gotten via Ajax after submitting a Formik react form
import React from 'react';
import {connect} from 'react-redux';
import { withFormik, Form, Field } from 'formik';
const LoginForm = ({
values
}) => {
return (
<div>
<Form className={styles.LoginForm}>
<Field className={input_classes} type="text" name="username" placeholder="username" />
<Field className={input_classes} type="password" name="password" placeholder="password" />
<button>Submit</button>
</Form>
</div>
)
}
const mapDispatchToProps = dispatch => {
return {
setData: (data) => dispatch({type: actionTypes.ADD_DATA, data: data})
}
}
const loginForm = connect(null, mapDispatchToProps)(LoginForm);
const FormikLoginForm = withFormik({
mapPropsToValues({ username, password }) {
return {
username: username || '',
password: password || '',
}
},
handleSubmit(values, props) {
//make AJAX call and set data in redux store
}
})(loginForm);
export default FormikLoginForm;
I cannot find a way to set any data to the store in the handleSubmit method; that is, how do I get access to the setData method that is returned mapDispatchToProps?
Order of HOC's matter, especially in this case (access to prop in another HOC) - redux connect must be outer to withFormik.
const FormikLoginForm = withFormik({
mapPropsToValues({ username, password }) {
return {
username: username || '',
password: password || '',
}
},
handleSubmit(values, { props } ) {
//make AJAX call and set data in redux store
props.setData( result )
}
})(LoginForm);
export default connect(null, mapDispatchToProps)(FormikLoginForm);

Resources