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;
Related
Im using react js with redux/toolkit to create user authentication.
I created a form with Formik. When the I submit the form it doesn't redirect me to the dashboard aand I'm unable to identify the issue.
I created a default authentication service under src/services/AuthService.js, the service method works coresponded useAuth fuction.
AuthService.js
export async function apiSignIn (data) {
return ApiService.fetchData({
url: '/v1/auth/login',
method: 'post',
data
})
}
And then I created a store with to Slices:
1- sessionsSlice
2- userSlice:
The logic I implemented in : src/store/auth/sessionSlice.js
import { createSlice } from '#reduxjs/toolkit'
export const sessionSlice = createSlice({
name: 'auth/session',
initialState: {
token: '',
signedIn: false,
},
reducers: {
onSignInSuccess: (state, action) => {
state.signedIn = true
state.token = action.payload
},
onSignOutSuccess: (state) => {
state.signedIn = false
state.token = ''
},
setToken: (state, action) => {
state.token = action.payload
}
},
})
export const { onSignInSuccess, onSignOutSuccess, setToken } = sessionSlice.actions
export default sessionSlice.reducer
The logic I implemented in : src/store/auth/userSlice .js
import { createSlice } from '#reduxjs/toolkit'
export const initialState = {
avatar: '',
userName: '',
email: '',
authority: []
}
export const userSlice = createSlice({
name: 'auth/user',
initialState,
reducers: {
setUser: (_, action) => action.payload,
userLoggedOut: () => initialState,
},
})
export const { setUser } = userSlice.actions
export default userSlice.reducer
The signIn logic implementation in the useAuth looks this way:
import { useSelector, useDispatch } from 'react-redux'
import { setUser, initialState } from 'store/auth/userSlice'
import { apiSignIn, apiSignOut, apiSignUp } from 'services/AuthService'
import { onSignInSuccess, onSignOutSuccess } from 'store/auth/sessionSlice'
import { REDIRECT_URL_KEY } from 'constants/app.constant'
import { useNavigate } from 'react-router-dom'
import useQuery from './useQuery'
function useAuth() {
const dispatch = useDispatch()
const navigate = useNavigate()
const query = useQuery()
const { token, signedIn } = useSelector((state) => state.auth.session)
const signIn = async (values) => {
try {
const resp = await apiSignIn(values)
if (resp.data) {
const { token } = resp.data
dispatch(onSignInSuccess(token))
if(resp.data) {
dispatch(setUser(resp.data || {
avatar: '',
userName: resp.data.userName,
authority: [resp.data.role],
email: resp.data.email
}))
}
const redirectUrl = query.get(REDIRECT_URL_KEY)
navigate(redirectUrl ? redirectUrl : '/dashboard')
return {
status: 'success',
message: ''
}
}
} catch (errors) {
return {
status: 'failed',
message: errors?.response?.data?.message || errors.toString()
}
}
}
const signUp = async (values) => {
try {
const resp = await apiSignUp(values)
if (resp.data) {
const { token } = resp.data
dispatch(onSignInSuccess(token))
if(resp.data.user) {
dispatch(setUser(resp.data.user || {
avatar: '',
userName: 'Anonymous',
authority: ['USER'],
email: ''
}))
}
const redirectUrl = query.get(REDIRECT_URL_KEY)
navigate(redirectUrl ? redirectUrl : '/dashboard')
return {
status: 'success',
message: ''
}
}
} catch (errors) {
return {
status: 'failed',
message: errors?.response?.data?.message || errors.toString()
}
}
}
const handleSignOut = () => {
dispatch(onSignOutSuccess())
dispatch(setUser(initialState))
navigate('/dashboard')
}
const signOut = async () => {
await apiSignOut()
handleSignOut()
}
return {
authenticated: token && signedIn,
signIn,
signUp,
signOut
}
}
export default useAuth
And finally, My form looks this way:
import React from 'react'
import { Input, Button, Checkbox, FormItem, FormContainer, Alert } from 'components/ui'
import { PasswordInput, ActionLink } from 'components/shared'
import useTimeOutMessage from 'utils/hooks/useTimeOutMessage'
import { Field, Form, Formik } from 'formik'
import * as Yup from 'yup'
import useAuth from 'utils/hooks/useAuth'
const validationSchema = Yup.object().shape({
userName: Yup.string().required('Please enter your username'),
password: Yup.string().required('Please enter your password'),
rememberMe: Yup.bool()
})
const SignInForm = props => {
const {
disableSubmit = false,
className,
} = props
const [message, setMessage] = useTimeOutMessage()
const { signIn } = useAuth()
const onSignIn = async (values, setSubmitting) => {
const { userName, password } = values
setSubmitting(true)
const result = await signIn({ userName, password })
if (result.status === 'failed') {
setMessage(result.message)
}
setSubmitting(false)
}
return (
<div className={className}>
{message && <Alert className="mb-4" type="danger" showIcon>{message}</Alert>}
<Formik
initialValues={{
userName: '',
password: '',
rememberMe: true
}}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
if(!disableSubmit) {
onSignIn(values, setSubmitting)
} else {
setSubmitting(false)
}
}}
>
{({touched, errors, isSubmitting}) => (
<Form>
<FormContainer>
<FormItem
label="User Name"
invalid={errors.userName && touched.userName}
errorMessage={errors.userName}
>
<Field
type="text"
autoComplete="off"
name="userName"
placeholder="User Name"
component={Input}
/>
</FormItem>
<FormItem
label="Password"
invalid={errors.password && touched.password}
errorMessage={errors.password}
>
<Field
autoComplete="off"
name="password"
placeholder="Password"
component={PasswordInput}
/>
</FormItem>
<div className="flex justify-between mb-6">
<Field className="mb-0" name="rememberMe" component={Checkbox} children="Remember Me" />
</div>
<Button block loading={isSubmitting} variant="solid" type="submit">
{ isSubmitting ? 'Signing in...' : 'Sign In' }
</Button>
</FormContainer>
</Form>
)}
</Formik>
</div>
)
}
export default SignInForm
i want to implement update user password form using react-router-dom v6 but this code is not working..
please please.. put your suggestion or explain me about my mistakes on this code.
userReducer.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_RESET,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const profileReducer = (state = {}, action) => {
switch (action.type) {
case UPDATE_PASSWORD_REQUEST:
return {
...state,
loading: true,
};
case UPDATE_PASSWORD_SUCCESS:
return {
...state,
loading: false,
isUpdated: action.payload,
};
case UPDATE_PASSWORD_FAIL:
return {
...state,
loading: false,
error: action.payload,
};
case UPDATE_PASSWORD_RESET:
return {
...state,
isUpdated: false,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
userAction.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const updatePassword = (passwords) => async (dispatch) => {
try {
dispatch({ type: UPDATE_PASSWORD_REQUEST });
const config = { headers: { "Content-Type": "application/json" } };
const { data } = await axios.put(
`/api/v1/password/update`,
passwords,
config
);
dispatch({ type: UPDATE_PASSWORD_SUCCESS, payload: data.success });
} catch (error) {
dispatch({
type: UPDATE_PASSWORD_FAIL,
payload: error.response.data.message,
});
}
};
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
store.js
import {createStore,combineReducers,applyMiddleware} from 'redux';
import thunk from "redux-thunk";
import {composeWithDevTools} from "redux-devtools-extension";
import { profileReducer } from './Reducers/userReducer';
const reducer = combineReducers({
profile:profileReducer,
})
let initialState = {};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
UpdatePassword.js
import React, { Fragment, useState, useEffect } from "react";
import "./UpdatePassword.css";
import Loader from "../Loader/Loader";
import { useDispatch, useSelector } from "react-redux";
import { clearErrors, updatePassword } from "../../Actions/userAction";
import { UPDATE_PASSWORD_RESET } from "../../Constants/userConstant";
import {useNavigate} from 'react-router-dom'
const UpdatePassword = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { error, isUpdated, loading } = useSelector((state) => state.profile);
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const updatePasswordSubmit = (e) => {
e.preventDefault();
const myForm = new FormData();
myForm.set("oldPassword", oldPassword);
myForm.set("newPassword", newPassword);
myForm.set("confirmPassword", confirmPassword);
dispatch(updatePassword(myForm));
};
useEffect(() => {
if (error) {
alert(error);
dispatch(clearErrors());
}
if (isUpdated) {
alert("Profile Updated Successfully");
navigate("/account");
dispatch({
type: UPDATE_PASSWORD_RESET,
});
}
}, [dispatch, error, isUpdated]);
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
{/* <MetaData title="Change Password" /> */}
<div className="updatePasswordContainer">
<div className="updatePasswordBox">
<h2 className="updatePasswordHeading">Update Profile</h2>
<form
className="updatePasswordForm"
onSubmit={updatePasswordSubmit}
>
<div className="loginPassword">
<input
type="password"
placeholder="Old Password"
required
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="New Password"
required
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="Confirm Password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
<input
type="submit"
value="Change"
className="updatePasswordBtn"
/>
</form>
</div>
</div>
</Fragment>
)}
</Fragment>
);
};
export default UpdatePassword;
i want to make a form where user update user password.but due to any mistake this form is not working...
Could someone please let me know why the state isn't being updated from the reducer? The useEffect(()=>{}) isn't being triggered when the state is being returned from the reducer. I have validated the correct information is being passed to the return, but nothing can be seen from the LoginScreen.
Context Script
import React, { createContext, useReducer } from "react";
import userReducer from "./UserReducer";
export const UserContext = createContext();
const initialState = {
userData: [],
isLoggedIn: false,
isAdmin: false,
isEmployee: false,
errorMessage: [{ success: false, statusCode: 0, error: null }],
};
const UserContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(userReducer, initialState);
const registerUser = (user) =>
dispatch({ type: "REGISTER_USER", payload: user });
const loginUser = (user) => dispatch({ type: "LOGIN_USER", payload: user });
const deleteUser = (user) => dispatch({ type: "DELETE_USER", payload: user });
const updateUser = (user) => dispatch({ type: "UPDATE_USER", payload: user });
const contextValues = {
...state,
registerUser,
loginUser,
deleteUser,
updateUser,
};
return (
<UserContext.Provider value={contextValues}>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
Reducer Script
import axios from "axios";
axios.defaults.withCredentials = true;
const userReducer = (state = {}, action) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
switch (action.type) {
case "REGISTER_USER":
break;
case "LOGIN_USER":
console.log(state);
const email = action.payload.email;
const password = action.payload.password;
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
// localStorage.setItem("authToken", response.data.authToken);
state.userData = response.data.user;
state.isLoggedIn = true;
if (response.data.user.role === 9) {
state.isAdmin = true;
state.isEmployee = true;
} else {
state.isAdmin = false;
state.isEmployee = false;
}
}
})
.catch((error) => {
state.errorMessage = {
success: error.response.data.success,
statusCode: error.response.status,
message: error.response.data.error,
};
});
return {
...state,
userData: [state.userData],
isLoggedIn: state.isLoggedIn,
isAdmin: state.isAdmin,
isEmployee: state.isEmployee,
errorMessage: [state.errorMessage],
};
default:
return state;
}
};
export default userReducer;
Login Form
import { useState, useEffect, useContext } from "react";
import { Link } from "react-router-dom";
import {
Button,
Form,
Grid,
Message,
Segment,
Image,
Container,
} from "semantic-ui-react";
//Custom Imports
import "./LoginScreen.css";
import Logo from "../../../img/logo.png";
//Context
import { UserContext } from "../../context/UserContext";
const LoginScreen = ({ history }) => {
const { userData, loginUser, isLoggedIn, errorMessage, clearErrorMessage } =
useContext(UserContext);
const [user, setUser] = useState({ email: "", password: "" });
const [error, setError] = useState("");
useEffect(() => {
console.log(errorMessage);
if (localStorage.getItem("authToken")) {
history.push("/dashboard");
}
}, [history]);
useEffect(() => {
if (isLoggedIn) {
console.log(userData);
console.log("User is Logged in");
// history.push("/");
}
if (!errorMessage.success && errorMessage.error != null) {
console.log(errorMessage);
setError(errorMessage.message);
setTimeout(() => {
setError("");
}, 5000);
}
}, [userData, errorMessage, isLoggedIn]);
return (
<Container className="login-container">
<Grid
textAlign="center"
style={{ height: "100vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Image src={Logo} className="login-logo" />
<Form size="large" onSubmit={() => loginUser(user)}>
<Segment stacked>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="Email Address"
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="Password"
value={user.password}
type="password"
onChange={(e) => setUser({ ...user, password: e.target.value })}
/>
{error && <span>{error}</span>}
<Button color="blue" fluid size="large" type="submit">
Login
</Button>
</Segment>
</Form>
<Message>
Don't have an account? <Link to="/register">Sign Up</Link>
</Message>
</Grid.Column>
</Grid>
</Container>
);
};
export default LoginScreen;
Refactor your login function like this
const loginUser({ email, password }) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
dispatch({ type: 'LOGIN_SUCCESS', payload: response.data });
}
})
.catch((error) => {
dispatch({ type: 'LOGIN_FAILED', payload: error });
});
}
and then your reducer
...
switch(action.type) {
...
case 'LOGIN_SUCCESS':
// return here a new object
// do not mutate the state (state.something = something) is not allowed
...
case 'LOGIN_FAILED':
// handle error
}
Prerequisite Reducer Concepts
Redux and useReducer use reducer like (previousState, action) => newState.
The reducer should be a 'pure' function as in this document. The promises, api calls should not be use inside reducers.
The problem:
Because you call api/promise inside the reducer. The reducer function returns the value before the promise finish. So when the promise finishes, nothing happen.
// A will be return before B, C are going to call
case "LOGIN_USER":
promiseFn()
.then(/* B */ ...)
.catch(/* C */ ...)
// A
return {
...
}
Solution:
Separate the non-pure calls from the reducer. And put them in the other code blocks (like inside hooks, event handlers...).
This is my code and I try to set email of user to state and set it with other data:
import React, {Component} from "react";
import PropTypes from "prop-types";
import * as actionCreators from '../../actions/authAction';
import {loadUser, setPass , register} from "../../actions/authAction";
import {connect} from "react-redux";
import { bindActionCreators } from "redux";
import {clearError} from "../../actions/errorAction";
import {toast} from "react-toastify";
import store from "../../store";
class RegisterFinal extends Component {
componentDidMount() {
store.dispatch(loadUser());
}
componentDidUpdate(nextProps) {
if (nextProps.user !== this.props.user) {
this.setState({ email: this.props.user});
}
}
state = {
userName: "",
password: "",
passwordConfirm: "",
email: "",
msg: null
}
static propTypes = {
isAuthenticated: PropTypes.bool,
setPass: PropTypes.bool,
register: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
user : PropTypes.object.isRequired,
};
onSubmit = (e) => {
e.preventDefault();
const {password, userName, email} = this.state
const setPass = {
password, userName, email
}
this.props.setPass(setPass);
const {passwordConfirm} = e.target;
const errors = {};
if (password.value !== passwordConfirm.value) {
errors.passwordMismatch = "Entered passwords do not match.";
console.log(errors)
}
}
onChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
render() {
return (
<div className={"container"}>
<div className={"row row-of-final-register justify-content-center"}>
<div className={"mt-5 register-teacher-inputs-box final-register-teacher-inputs-box"}>
<div className={"final-register-wrapper"}>
<form className={"mt-5"} onSubmit={this.onSubmit}>
<div className={"row"}>
<div className={"col-12"}>
<label
htmlFor={"userName"} className={"text-right username-label"}>
<span>*</span>
</label>
<input type="text" className="form-control w-100" placeholder={"Username"}
name={"userName"}
autoComplete="true"
value={this.userName}
onChange={this.onChange}
onFocus={(e) => e.target.placeholder = ""}
/>
</div>
</div>
<div className={"row"}>
<div className={"col-12 col-lg-6 mt-3"}>
<label
htmlFor={"password"} className={" text-right"}>
<span>*</span>
</label>
<input type="password" className="form-control " placeholder={"Password"}
name={"password"}
value={this.password}
onChange={this.onChange}
onFocus={(e) => e.target.placeholder = ""}
/>
</div>
</div>
<div className={"row mt-3 pt-2"}>
<div className={"col-12 final-register-wrapper final-register-btn"}>
<button type={"submit"} className={"final-register-btn"}>Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
)
}
}
function mapStateToProps (state , ownProperties) {
console.log(state.auth.user)
return {
setPass: state.auth.setPass,
isAuthenticated: state.auth.isAuthenticated,
error: state.error,
auth: state.auth,
user : state.auth.user,
}
};
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps
)(RegisterFinal);
When run I have this data in my email state :
But when I try to access email get Error that cannot read property ‘email’ of undefined, I just change email : this.props.user to email : this.props.user.email in componentDidUpdate. I actually need to set redux props to react state. I'm new in redux.
Here share my project : my project
Here is a working example that will async load a user, set the initial value of a textbox with it's value and submit changes:
const { Provider, connect } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const initialState = {
user: null,
loading: true, //initially set loading to true
};
//helper for async
const later = (value) =>
new Promise((resolve) =>
setTimeout(() => resolve(value), 2000)
);
//action types
const CHANGE_USER = 'CHANGE_USER';
const CHANGED_USER = 'CHANGED_USER';
const LOAD_USER = 'LOAD_USER';
const LOADED_USER = 'LOADED_USER';
//action creators
const loadUser = () => ({
type: LOAD_USER,
});
const loadedUser = (user) => ({
type: LOADED_USER,
payload: user,
});
const changeUser = () => ({
type: CHANGE_USER,
});
const changedUser = (user) => ({
type: CHANGED_USER,
payload: user,
});
// action thunks
const loadUserThunk = () => (dispatch) => {
dispatch(loadUser());
return later({
email: 'original-email',
}).then((user) => dispatch(loadedUser(user)));
};
const changeUserThunk = (email) => (dispatch) => {
dispatch(changeUser());
return later({
email,
}).then((user) => dispatch(changedUser(user)));
};
const reducer = (state, { type, payload }) => {
if (type === LOAD_USER || type === CHANGE_USER) {
return { ...state, loading: true };
}
if (type === LOADED_USER || type === CHANGED_USER) {
return {
...state,
user: payload,
loading: false,
};
}
return state;
};
//selectors
const selectUser = (state) => state.user;
const selectLoading = (state) => state.loading;
const selectUserEmail = createSelector(
[selectUser],
//want to use user?.email but SO babel is too old
(user) => user && user.email
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
({ getState, dispatch }) =>
(next) =>
(
action //simple thunk implementation
) =>
typeof action === 'function'
? action(dispatch, getState)
: next(action)
)
)
);
class App extends React.PureComponent {
state = {
email: '',
initial: true, //only initially set from redux
};
componentDidUpdate() {
if (!this.props.loading && this.state.initial) {
this.setState({
email: this.props.email,
initial: false,
});
}
}
//arrow function so you don't need to bind for "this" context
onSubmit = (e) => {
e.preventDefault();
this.props.dispatch(changeUserThunk(this.state.email));
//reset initial
this.setState({ initial: true });
};
emailChanged = (e) =>
this.setState({ email: e.target.value });
componentDidMount() {
this.props.dispatch(loadUserThunk());
}
render() {
return (
<form onSubmit={this.onSubmit}>
<input
type="text"
onChange={this.emailChanged}
value={this.state.email}
// do not edit when loading
disabled={this.props.loading}
/>
<input type="submit" />
{this.props.loading && 'loading...'}
</form>
);
}
}
//do not use store in your components, connect will provide
// dispatch on this.props when mapDispatchToProps is
// undefined or you can pass an object as mapDispatchToProps
const ConnectedApp = connect((state) => ({
email: selectUserEmail(state), //select state.user.email as props.email
loading: selectLoading(state),
}))(App);
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
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.