Cannot read property 'data' of undefined when user successfully register - reactjs

I keep getting an error everytime a user signs up successfully,
Unhandled Rejection (TypeError): Cannot read property 'data' of
undefined
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
why is it executing this catch method if their is no error when a user signs up successfully ?
console.log(err.response) shows when a user is registering with the same username or email.
What could i be doing wrong ?
authActions.js
export const registerUser = (userData) => dispatch => {
Axios
.post('/users/register', userData)
.then( res => {
const token = res.data.token;
console.log(token);
// pass the token in session
sessionStorage.setItem("jwtToken", token);
// set the auth token
setAuthToken(token);
// decode the auth token
const decoded = jwt_decode(token);
// pass the decoded token
dispatch(setCurrentUser(decoded))
this.props.history.push("/dashboard")
}).catch( (err) => {
console.log(err.response)
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
})
};
reducer
import {SET_CURRENT_USER } from '../actions/types';
import isEmpty from '../actions/utils/isEmpty';
const initialState = {
isAuthenticated: false
}
export default (state = initialState, action) => {
switch (action.type) {
case SET_CURRENT_USER:
return{
...state,
isAuthenticated: !isEmpty(action.payload),
user:action.payload
}
default:
return state;
}
}
console.log(res)
{
"data": {
"message": "user created",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDEsImlhdCI6MTU1OTI2ODcxNX0.t7xK5VVdOmj2BbExBmOdUrZHwYuyEJgjvUTQq1Mw5qY",
"auth": true
},
"status": 200,
"statusText": "OK",
"headers": {
"content-type": "application/json; charset=utf-8",
"content-length": "165"
},
"config": {
"transformRequest": {},
"transformResponse": {},
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"headers": {
"Accept": "application/json",
"Content-Type": "application/json"
},
"method": "post",
"baseURL": "http://localhost:3000",
"withCredentials": true,
"url": "http://localhost:3000/users/register",
"data": "{\"username\":\"billyssss999\",\"email\":\"j0hnnnysssraddddddin#yahoo.com\",\"password\":\"janeddmdddba\"}"
},
"request": {}

So it seems to be working after i remove this
this.props.history.push("/dashboard")
and redirects to the dashboard already by this existing code.
Signup
....
componentDidMount() {
// console.log(this.props.auth);
if (this.props.auth.isAuthenticated) {
this.props.history.push("/dashboard");
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.auth.isAuthenticated) {
this.props.history.push("/dashboard");
}
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
handleChange = (e) => {
e.preventDefault();
const {formData} = this.state;
this.setState({
formData: {
...formData,
[e.target.name]: e.target.value
}
});
}
handleSubmit = (e) => {
e.preventDefault();
const {formData} = this.state;
const {username, email, password, passwordConf} = formData;
this.setState({
username: this.state.username,
password: this.state.password,
passwordConf: this.state.passwordConf,
email: this.state.email
});
const creds = {
username,
email,
password
}
console.log(creds);
if (password === passwordConf) {
this.props.registerUser(creds, this.props.history);
} else {
this.setState({passErr: "Passwords Don't Match"})
}
}

Related

how to catch login error authorization 401 in React, Reducer

I'm trying to handle the login of a react app using reducer.
Here is the reducer fuction =>
export const AuthReducer = (initialState, action) => {
switch (action.type) {
case "REQ_LOGIN":
return {
...initialState,
loading: true
};
case "REQ_REGISTER":
return {
...initialState,
loading: true
};
case "LOGIN_OK":
return {
id: action.payload.user,
token: action.payload.token,
rol: action.payload.rol,
loading: false
};
case "LOGIN_FAIL":
return {
...initialState,
loading: false,
errorMessage: action.error
};
case "LOGOUT":
return {
id: undefined,
token: undefined,
loading: false
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
and this is the fuction I user to dispatch the login =>
const requestOptions = (sendData) => {
return {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sendData)
}
};
const fetchUserToDatabase = async (urlPetition, reqData) => {
try {
const res = await fetch(`${BASE_URL}/${urlPetition}`, requestOptions(reqData));
const data = await res.json();
return data;
} catch (error) {
console.error(error)
}
};
export const loginUser = async (dispatch, loginData) => {
localStorage.removeItem('currentUser');
try {
dispatch({ type: "REQ_LOGIN" });
const data = await fetchUserToDatabase("users/login", loginData)
if (data.data) {
dispatch({ type: "LOGIN_OK", payload: data.data });
localStorage.setItem('currentUser', JSON.stringify(data.data));
}
} catch (error) {
dispatch({ type: 'LOGIN_FAIL', error: error });
}
};
Then whenever I need to login I call loginUser(dispatch, loginForm), for example.
The problem is that the 401 unauthorized error is not being handled by the try, catch but is being sent in the response as res.status. so I literally don't know how to catch it outside the reducer and the actions function.
Error auth 401

I got Type-errors while converting my Slice.js file to ts (redux tool kit)

I just created my sample Redux tool kit with js files. now I am trying to convert them to typescript. some of the error is now fixed. but I have no idea how to fix the two errors below.
Property 'name' does not exist on type 'void'. <<<< also for email, password... and token
A computed property name must be of type 'string', 'number', 'symbol', or 'any' <<<< for signupUser.fulfilled, signupUser.pending... so on
Please refer to my comments within the code below and please help me to how to solve the problems
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import axios from "axios";
export interface UserState {
username: string;
email: string;
isFetching: boolean;
isSuccess: boolean;
isError: boolean;
errorMessage: string;
}
interface check {
name: string;
email: string;
password: string;
}
const initialState: UserState = {
username: "",
email: "",
isFetching: false,
isSuccess: false,
isError: false,
errorMessage: "",
}
export const signupUser = createAsyncThunk(
'users/signupUser',
//
//
// async ({ name, email, password} ... <<< This is the problem 1.
//
async ({ name, email, password }, thunkAPI) => {
try {
const response = await fetch(
'https://mock-user-auth-server.herokuapp.com/api/v1/users',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name,
email,
password,
}),
},
);
const data = await response.json();
console.log('data', data);
if (response.status === 200) {
localStorage.setItem('token', data.token);
return { ...data, username: name, email };
}
return thunkAPI.rejectWithValue(data);
} catch (e) {
console.log('Error', e.response.data);
return thunkAPI.rejectWithValue(e.response.data);
}
},
);
export const loginUser = createAsyncThunk(
'users/login',
async ({ email, password }, thunkAPI) => {
try {
const response = await fetch(
'https://mock-user-auth-server.herokuapp.com/api/v1/auth',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
}),
},
);
const data = await response.json();
console.log('response', data);
if (response.status === 200) {
localStorage.setItem('token', data.token);
return data;
}
return thunkAPI.rejectWithValue(data);
} catch (e) {
console.log('Error', e.response.data);
thunkAPI.rejectWithValue(e.response.data);
}
},
);
export const fetchUserBytoken = createAsyncThunk(
'users/fetchUserByToken',
async ({ token }, thunkAPI) => {
try {
const response = await fetch(
'https://mock-user-auth-server.herokuapp.com/api/v1/users',
{
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: token,
'Content-Type': 'application/json',
},
},
);
/* test case for Axios
const response = await axios.get(
'https://mock-user-auth-server.herokuapp.com/api/v1/users',
{
headers: {
Accept: 'application/json',
Authorization: token,
'Content-Type': 'application/json',
},
},
);
*/
const data = await response.json();
console.log('data', data, response.status);
if (response.status === 200) {
return { ...data };
}
return thunkAPI.rejectWithValue(data);
} catch (e) {
console.log('Error', e.response.data);
return thunkAPI.rejectWithValue(e.response.data);
}
},
);
export const userSlice = createSlice({
name: 'user',
initialState: {
username: '',
email: '',
isFetching: false,
isSuccess: false,
isError: false,
errorMessage: '',
},
reducers: {
clearState: state => {
state.isError = false;
state.isSuccess = false;
state.isFetching = false;
return state;
},
},
////
//// [signupUser.fulfilled] ... <<<< this is the problem 2.
////
////
extraReducers: {
[signupUser.fulfilled]: (state, { payload }) => {
console.log('payload', payload);
state.isFetching = false;
state.isSuccess = true;
state.email = payload.user.email;
state.username = payload.user.name;
},
[signupUser.pending]: state => {
state.isFetching = true;
},
[signupUser.rejected]: (state, { payload }) => {
state.isFetching = false;
state.isError = true;
state.errorMessage = payload.message;
},
[loginUser.fulfilled]: (state, { payload }) => {
state.email = payload.email;
state.username = payload.name;
state.isFetching = false;
state.isSuccess = true;
return state;
},
[loginUser.rejected]: (state, { payload }) => {
console.log('payload', payload);
state.isFetching = false;
state.isError = true;
state.errorMessage = payload.message;
},
[loginUser.pending]: state => {
state.isFetching = true;
},
[fetchUserBytoken.pending]: state => {
state.isFetching = true;
},
[fetchUserBytoken.fulfilled]: (state, { payload }) => {
state.isFetching = false;
state.isSuccess = true;
state.email = payload.email;
state.username = payload.name;
},
[fetchUserBytoken.rejected]: state => {
console.log('fetchUserBytoken');
state.isFetching = false;
state.isError = true;
},
},
});
export const { clearState } = userSlice.actions;
export const userSelector = (state: { user: UserState}) => state.user;
You'll need to define a type for the argument you put in there. If you only want to type the first argument and are fine with the defaults for thunkAPI, you can use
export const signupUser = createAsyncThunk(
'users/signupUser',
async ({ name, email, password }: ArgumentType, thunkAPI) => {
Otherwise, you'll have to declare generic arguments as described in https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk
const fetchUserById = createAsyncThunk<
// Return type of the payload creator
MyData,
// First argument to the payload creator
number,
{
dispatch: AppDispatch
state: State
extra: {
jwt: string
}
}
>('users/fetchById', async (userId, thunkApi) => {
For problem #2 please see that we discourage the object map notation for extraReducers as it is less typesafe in general and does not work in TypeScript. Please use the builder notation as described in https://redux-toolkit.js.org/api/createSlice#the-extrareducers-builder-callback-notation

how to jest test an async action with axios in react?

I have an action-generator register.js:
import { REGISTER_SUCCESS, REGISTER_FAIL } from "./types";
import axios from "axios";
export const register = (formData) => async (dispatch) => {
const { name, email, password } = formData;
const configRegister = {
method: "post",
url: "/api/users",
headers: { "Content-Type": "application/json" },
data: { name, email, password },
};
try {
const res = await axios(configRegister);
const token = res.data.token;
dispatch({
type: REGISTER_SUCCESS,
payload: {
token,
isAuthenticated: true,
loading: false,
},
});
} catch (err) {
console.error(err);
dispatch({
type: REGISTER_FAIL,
payload: {
token: null,
isAuthenticated: false,
loading: true,
},
});
}
};
the endpoint /api/users returns {token:'a_token_string'} on being successful.
How should i test this action-generator using jest ?
I tried doing this, register.test.js :-
import {
REGISTER_SUCCESS,
} from "./types";
import thunk from "redux-thunk";
import configureMockStore from "redux-mock-store";
import axios from "axios";
jest.mock("axios");
/** mock-store */
const createMockStore = configureMockStore([thunk]);
const defaultState = [];
const store = createMockStore(defaultState);
/** reset mock */
afterEach(() => jest.resetAllMocks());
test("should register a user ", async () => {
axios.post.mockImplementation(() => {
return Promise.resolve({
status: 200,
body: {
token: "testToken",
},
});
});
const res = await axios.post("/api/users");
console.log(res.body);
const testUser = {
name: "testName",
email: "test#email.com",
password: "testPassword",
};
await store.dispatch(register(testUser)).then(() => {
expect(store.getActions()[0]).toEqual({
type: REGISTER_SUCCESS,
payload: {
token: "testToken",
isAuthenticated: true,
loading: false,
},
});
});
});
You're quite close to get it done. The thing is you're mocking axios.post while your implementation is using directly from axios object. As long as you mock axios object then it would work as it should. Here is proposed changes, please check inline comments for things you should also change:
test("should register a user ", async () => {
// Mock `axios` object directly
axios.mockImplementation(() => {
return Promise.resolve({
status: 200,
// should also change from `body` to `data` as well
data: {
token: "testToken",
},
});
});
// it will no longer work since the mock is changed
// const res = await axios.post("/api/users");
// console.log(res.body);
const testUser = {
name: "testName",
email: "test#email.com",
password: "testPassword",
};
await store.dispatch(register(testUser)).then(() => {
expect(store.getActions()[0]).toEqual({
type: REGISTER_SUCCESS,
payload: {
token: "testToken",
isAuthenticated: true,
loading: false,
},
});
});
});

useSelector doesn't load the state in time

I'm working on a project of mine using React and Redux.
I'm trying to retrieve the token field from my auth state from my store (which contains an authentication key that I intend to send over to my server side) using useSelectorbut the thing is, that it doesn't load in time. How do I fix this?
I'll add below parts of my code that are associated with this issue:
LikeButton.js: (here is the part where I'm trying to retrieve token)
...
const LikeButton = ({ postId }) => {
const classes = useStyles();
const [isLiked, setIsLiked] = useState(false);
const isMount = useIsMount();
const dispatch = useDispatch();
const { token } = useSelector((state) => state.auth);
const { likedPostsIds } = useSelector((state) => state.likes);
useEffect(() => {
if (token) dispatch(fetchLikedPosts(token));
}, [token]);
...
likeActions.js:
...
export const fetchLikedPosts = ({ token }) => (dispatch) => {
fetch("http://localhost:8080/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: fetchLikedPostsQuery(),
})
.then((res) => res.json())
...
authActions.js
...
export const signIn = ({ password, email }) => (dispatch) => {
dispatch({
type: IS_AUTH_LOADING,
});
fetch("http://localhost:8080/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: signInQuery(password, email),
})
.then((res) => res.json())
.then((resData) => {
if (resData.errors) {
dispatch(showNotification("User authentication has failed!", ERROR));
dispatch({
type: AUTH_ERROR,
});
} else {
setLocalStorageAuth(resData.data.signIn.token, expiryDate);
dispatch({
type: SIGN_IN,
payload: resData.data.signIn,
});
}
})
.catch((err) => {
dispatch(
showNotification(
"User authentication has failed. Please try again later.",
ERROR
)
);
dispatch({
type: AUTH_ERROR,
});
emptyLocalStorage();
});
};
...
auth.js:
...
const initialState = {
token: localStorage.getItem("token"),
isAuth: localStorage.getItem("isAuth"),
expiryDate: localStorage.getItem("expiryDate"),
isLoading: false,
};
export default function authReducer(state = initialState, action) {
switch (action.type) {
case REGISTER_LOADING:
case IS_AUTH_LOADING:
return {
...state,
isLoading: true,
};
case SIGN_IN:
return {
token: action.payload.token,
isAuth: true,
isLoading: false,
};
case AUTH_ERROR:
case LOGIN_FAIL:
case REGISTER_FAIL:
case LOGOUT_SUCCESS:
return {
token: null,
isAuth: false,
isLoading: false,
};
case CREATE_USER:
return {
...state,
isLoading: false,
};
default:
return state;
}
}
...
The fetchLikedPosts function expects an object with a token key as an argument. the effect currently passes just the token as an argument. Try
useEffect(() => {
// note the change from token to {token}
if (token) dispatch(fetchLikedPosts({token}));
}, [token]);

react & redux with hooks: Actions must be plain objects. Use custom middleware for async actions

i tried looking for similar answers to help solve my problem but i couldn't find anything using react redux hooks. This code was from a tutorial and originally written using the Context api. I wanted to trying using it with react-redux-hooks, but i got stuck. Basically i'm trying to register a user with a name, email and password, then pass these three as an object to the express server which will validated it and give me back a jwt token. Then come back to the client side and send the token to the reducer, which adds the token to localstorage and sets the state to isAuthenticated. The error i get is on the dispatch.
Dispatch
const onSubmit = e => {
e.preventDefault();
if (name === "" || email === "" || password === "") {
dispatch(setAlert("Please enter all fields", "danger"));
} else if (password !== password2) {
dispatch(setAlert("Passwords do not match", "danger"));
} else {
dispatch(register({ name, email, password })); // Error is here
}
setTimeout(() => {
dispatch(removeAlert());
}, 5000);
};
Action
export const register = async formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
try {
const res = await axios.post("/api/users", formData, config);
return {
type: "REGISTER_SUCCESS",
payload: res.data
};
} catch (err) {
return {
type: "REGISTER_FAIL",
payload: err.response.data.msg
};
}
};
Reducer
const authReducer = (
state = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null,
error: null
},
action
) => {
switch (action.type) {
case "REGISTER_SUCCESS":
console.log("register success");
localStorage.setItem("token", action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false
};
case "REGISTER_FAIL":
console.log("register failed");
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
error: action.payload
};
default:
return state;
}
};
Store
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|| compose;
const store = createStore(
allReducers,
composeEnhancers(applyMiddleware(thunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Express server
router.post(
"/",
[
check("name", "Please a name")
.not()
.isEmpty(),
check("email", "Please include a valid email").isEmail(),
check(
"password",
"Please enter a password with 6 or more characters"
).isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { name, email, password } = req.body;
try {
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({
msg: "User already exists"
});
}
user = new User({
name,
email,
password
});
// hash passsword
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{
expiresIn: 360000
},
(err, token) => {
if (err) throw err;
res.json({
token
});
}
);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
I believe this question has answers to the issue your experiencing here: how to async/await redux-thunk actions?
Using this example, it may look something like this (wasn't able to test it):
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
const request = axios.post("/api/users", formData, config);
return dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
request.then(onSuccess, onError);
};
};
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
return async dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
try {
const success = await axios.post("/api/users", formData, config);
return onSuccess(success);
} catch (error) {
return onError(error);
}
}
};

Resources