REACT-REDUX-EXPRESS, unable to update user fields, 404 ERROR - javascript

I am trying to update user information (name, email, password) but getting two errors.
If I try to enter & update name or Email (DATA DOES NOT GET UPDATED) I get 404 error :
Request:
Response:
But, if I try to enter and update Password it gets UPDATED (as I have to enter new password while logging in again) but it shows these error's immediately after submit button:
I have tried updating user data by putting data directly through thunder client & it's getting updated:
Here is my source code:
FRONTEND
ProfileScreen.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import ErrorMessage from "../../components/ErrorMessage/ErrorMessage";
import Message from "../../components/Message/Message";
import Loader from "../../components/Loader/Loader";
import {
getUserDetails,
updateUserProfile,
} from "../../redux/actions/userActions";
import "./ProfileScreen.scss";
import { USER_UPDATE_PROFILE_RESET } from "../../redux/constants/userConstants";
const ProfileScreen = ({ location, history }) => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [message, setMessage] = useState(null);
const regex =
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[#$!%*#?&])[A-Za-z\d#$!%*#?&]{8,}$/;
const dispatch = useDispatch();
const userDetails = useSelector((state) => state.userDetails);
const { loading, error, user } = userDetails;
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
const userUpdateProfile = useSelector((state) => state.userUpdateProfile);
const { success } = userUpdateProfile;
useEffect(() => {
if (!userInfo) {
history.push("/login");
} else {
if (!user.name || !user || success) {
dispatch({ type: USER_UPDATE_PROFILE_RESET });
dispatch(getUserDetails("profile"));
} else {
setName(user.name);
setEmail(user.email);
}
}
}, [history, userInfo, dispatch, user, success]);
const passwordHandler = (e) => {
e.preventDefault();
!regex.test(password)
? setMessage(
"Password must contain atleast 8 characters & one alphabet, number & special character"
)
: password !== confirmPassword
? setMessage("Passwords do not match!")
: dispatch(updateUserProfile({ id: user._id, password }));
};
const enameHandler = (e) => {
e.preventDefault();
dispatch(updateUserProfile({ id: user._id, name, email, password }));
};
return (
<>
<div className="profile-container">
<div className="profile">
{message && <ErrorMessage error={message} />}
{error && <ErrorMessage error={error} />}
{success && <Message success={"Profile Updated"} />}
<div className="profile-form">
<h2>User Profile</h2>
{loading ? (
<Loader />
) : (
<div>
<form onSubmit={enameHandler}>
<div className="profile-form-items">
<h3> Update Name or Email</h3>
<input
className="profile-input"
type="name"
placeholder="New Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
className="profile-input"
type="email"
placeholder="New Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit" value="submit">
Update
</button>
</div>
</form>
<form onSubmit={passwordHandler}>
<div className="profile-form-items">
<h3>Update Password</h3>
<input
className="profile-input"
type="password"
placeholder="New Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<input
className="profile-input"
type="password"
placeholder="Confirm New Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<button type="submit" value="submit">
Update
</button>
</div>
</form>
</div>
)}
</div>
</div>
</>
);
};
export default ProfileScreen;
userAction.js
import axios from "axios";
import {
USER_DETAILS_FAIL,
USER_DETAILS_REQUEST,
USER_DETAILS_SUCCESS,
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGOUT,
USER_REGISTER_FAIL,
USER_REGISTER_REQUEST,
USER_REGISTER_SUCCESS,
USER_UPDATE_PROFILE_FAIL,
USER_UPDATE_PROFILE_REQUEST,
USER_UPDATE_PROFILE_SUCCESS,
} from "../constants/userConstants";
export const login = (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-Type": "application/json",
},
};
const { data } = await axios.post(
"/api/users/login",
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const logout = () => (dispatch) => {
localStorage.removeItem("userInfo");
dispatch({
type: USER_LOGOUT,
});
};
export const register = (name, email, password) => async (dispatch) => {
try {
dispatch({
type: USER_REGISTER_REQUEST,
});
const config = {
headers: {
"Content-Type": "application/json",
},
};
const { data } = await axios.post(
"/api/users",
{ name, email, password },
config
);
dispatch({
type: USER_REGISTER_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_REGISTER_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const getUserDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: USER_DETAILS_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
dispatch({
type: USER_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: USER_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const updateUserProfile = (user) => async (dispatch, getState) => {
try {
dispatch({
type: USER_UPDATE_PROFILE_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${userInfo.token}`,
},
};
console.log("UPDATE Action called");
const { data } = await axios.put(`/api/users/profile`, user, config);
dispatch({
type: USER_UPDATE_PROFILE_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: USER_UPDATE_PROFILE_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
userReducer.js
import {
USER_UPDATE_PROFILE_FAIL,
USER_UPDATE_PROFILE_REQUEST,
USER_UPDATE_PROFILE_SUCCESS,
} from "../constants/userConstants";
export const userUpdateProfileReducer = (state = {}, action) => {
switch (action.type) {
case USER_UPDATE_PROFILE_REQUEST:
return { loading: true };
case USER_UPDATE_PROFILE_SUCCESS:
return { loading: false, success: true, userInfo: action.payload };
case USER_UPDATE_PROFILE_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
store.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
// reducers
import {
userLoginReducer,
userRegisterReducer,
userDetailsReducer,
userUpdateProfileReducer,
} from "./reducers/userReducers";
const reducer = combineReducers({
userLogin: userLoginReducer,
userRegister: userRegisterReducer,
userDetails: userDetailsReducer,
userUpdateProfile: userUpdateProfileReducer,
});
const userInfoFromStorage = localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
BACKEND
userRoutes.js
const express = require("express");
const {
authUser,
getUserProfile,
registerUser,
updateUserProfile,
} = require("../controllers/userController");
const protect = require("../middleware/authMiddleware");
const router = express.Router();
router.route("/").post(registerUser);
router.post("/login", authUser);
router
.route("/profile")
.get(protect, getUserProfile)
.put(protect, updateUserProfile);
module.exports = router;
userController.js
// #description: Update user profile
// #route: PUT /api/users/profile
// #access: Private
exports.updateUserProfile = async (req, res, next) => {
try {
const user = await User.findById(req.user._id);
if (user) {
user.name = req.body.name || user.name;
user.email = req.body.email || user.email;
if (req.body.password) {
user.password = req.body.password;
}
const updatedUser = await user.save();
res.json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
isAdmin: updatedUser.isAdmin,
token: generateToken(updatedUser._id),
});
}
} catch (error) {
error = new Error("User not found");
error.status = 404;
next(error);
}
};
authMiddleware.js
const jwt = require("jsonwebtoken");
const User = require("../models/userModel");
const protect = async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
try {
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} catch (error) {
error = new Error("Not Authorized!!");
error.status = 401;
next(error);
}
}
if (!token) {
const error = new Error("Not Authorized!!, No Token!!");
error.status = 401;
next(error);
}
};
module.exports = protect;

The error is (most probably) in
const user = await User.findById(req.user._id);
check if you are getting the "req.user._id" correctly. And also check your authMiddlwere if there is any error in getting req.user._id .

Related

Why does the background color only change after a page refresh?

When a user logs in, the background of their liked photos should change. Why does the background color only change when the page is reloaded and not after a successful login? There are too many requests happening at the same time and I don't know how to handle them properly. Maybe someone has any ideas.?
Login.js
import { useAppContext } from "../context/appContext";
function Login() {
const { handleClick } = useAppContext();
return (
<div>
<button onClick={() => handleClick()}>Log in</button>
</div>
);
}
export default Login;
Home.js
import { useAppContext } from "../context/appContext";
function Home() {
const [page, setPage] = useState(1);
const [query, setQuery] = useState("landscape");
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const { token, username, getUserProfile, getToken } =
useAppContext();
const clientId = process.env.REACT_APP_UNSPLASH_KEY;
useEffect(() => {
if (window.location.search.includes("code=") && !token) {
getToken();
}
if (token) {
getUserProfile();
}
}, [token]);
const fetchImages = () => {
setLoading(true);
let params = {
page: page,
query: query,
per_page: 30,
};
let headers = {};
if (username) {
headers = {
Authorization: `Bearer ${token}`,
};
} else {
params = { ...params, client_id: clientId };
}
axios
.get("https://api.unsplash.com/search/photos", { headers, params })
.then((response) => {
setImages([...images, ...response.data.results]);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
setLoading(false);
});
setPage(page + 1);
};
useEffect(() => {
fetchImages();
setQuery("");
}, []);
return (
<div>
<Login />
{loading && <Loader />}
<ImageList images={images} fetchImages={fetchImages} />
</div>
);
}
export default Home;
appContext.js
import React, { useReducer, useContext } from "react";
import reducer from "./reducer";
import axios from "axios";
import {
SET_TOKEN,
SET_USERNAME,
LOGOUT_USER,
} from "./actions";
const token = localStorage.getItem("token");
const username = localStorage.getItem("username");
const initialState = {
token: token,
username: username,
};
const client_id = process.env.REACT_APP_UNSPLASH_KEY;
const redirect_uri = "http://localhost:3000/";
const api_auth_uri = "https://unsplash.com/oauth/authorize";
const api_token_uri = "https://unsplash.com/oauth/token";
const response_type = "code";
const scope = [
"public",
"read_user",
"write_user",
"read_photos",
"write_photos",
"write_likes",
"write_followers",
"read_collections",
"write_collections",
];
const client_secret = process.env.REACT_APP_UNSPLASH_SECRET;
const grant_type = "authorization_code";
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleClick = async () => {
window.location.href = `${api_auth_uri}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope.join(
"+"
)}`;
};
const getToken = async () => {
const coder = window.location.search.split("code=")[1];
try {
const { data } = await axios.post(
`${api_token_uri}?client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirect_uri}&code=${coder}&grant_type=${grant_type}`
);
const { access_token } = data;
localStorage.setItem("token", access_token);
dispatch({
type: SET_TOKEN,
payload: { access_token },
});
const newUrl = window.location.href.split("?")[0];
history.replaceState({}, document.title, newUrl);
} catch (error) {
logoutUser();
}
};
const getUserProfile = async () => {
try {
const { data } = await axios.get(`https://api.unsplash.com/me`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
});
const { username } = data;
localStorage.setItem("username", username);
dispatch({
type: SET_USERNAME,
payload: { username },
});
} catch (error) {
console.log(error)
}
};
const logoutUser = () => {
dispatch({ type: LOGOUT_USER });
localStorage.removeItem("token");
};
return (
<AppContext.Provider
value={{
...state,
handleClick,
getToken,
getUserProfile,
logoutUser,
}}
>
{children}
</AppContext.Provider>
);
};
const useAppContext = () => useContext(AppContext);
export { AppProvider, initialState, useAppContext };
ImageList.js
import React, { useState } from "react";
import "../styles/ImageList.scss";
function ImageList({ images }) {
return (
<div className="result">
{images?.map((image, index) => (
<div
style={{
backgroundColor: image.liked_by_user ? "red" : "",
}}
key={image.id}
>
<div key={image.id}>
<img src={image.urls.small} alt={image.alt_description} />
</div>
</div>
))}
</div>
);
}
export default ImageList;

Turning JavaScript example into Typescript and failing

Trying to learn both TypeScript and JavaScript. Having trouble with translating this AuthContext code from this tutorial into typescript.
This is the original code
import { createContext, useState, useEffect } from "react";
import jwt_decode from "jwt-decode";
import { useHistory } from "react-router-dom";
const AuthContext = createContext();
export default AuthContext;
export const AuthProvider = ({ children }) => {
const [authTokens, setAuthTokens] = useState(() =>
localStorage.getItem("authTokens")
? JSON.parse(localStorage.getItem("authTokens"))
: null
);
const [user, setUser] = useState(() =>
localStorage.getItem("authTokens")
? jwt_decode(localStorage.getItem("authTokens"))
: null
);
const [loading, setLoading] = useState(true);
const history = useHistory();
const loginUser = async (username, password) => {
const response = await fetch("http://127.0.0.1:8000/api/token/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username,
password
})
});
const data = await response.json();
if (response.status === 200) {
setAuthTokens(data);
setUser(jwt_decode(data.access));
localStorage.setItem("authTokens", JSON.stringify(data));
history.push("/");
} else {
alert("Something went wrong!");
}
};
const registerUser = async (username, password, password2) => {
const response = await fetch("http://127.0.0.1:8000/api/register/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username,
password,
password2
})
});
if (response.status === 201) {
history.push("/login");
} else {
alert("Something went wrong!");
}
};
const logoutUser = () => {
setAuthTokens(null);
setUser(null);
localStorage.removeItem("authTokens");
history.push("/");
};
const contextData = {
user,
setUser,
authTokens,
setAuthTokens,
registerUser,
loginUser,
logoutUser
};
useEffect(() => {
if (authTokens) {
setUser(jwt_decode(authTokens.access));
}
setLoading(false);
}, [authTokens, loading]);
return (
<AuthContext.Provider value={contextData}>
{loading ? null : children}
</AuthContext.Provider>
);
};
This is my attempt
import { createContext, useState, useEffect } from "react";
import jwt_decode from "jwt-decode";
import { redirect } from "react-router-dom";
const AuthContext = createContext(null);
export function AuthProvider(children:any) {
const [user, setUser] = useState(() =>
localStorage.getItem("authTokens")
? jwt_decode(localStorage.getItem("authTokens") || '{}') : null
);
const [authTokens, setAuthTokens] = useState(() =>
localStorage.getItem("authTokens")
? JSON.parse(localStorage.getItem("authTokens") || '{}')
: null
);
const [loading, setLoading] = useState(true);
async function loginUser(username: string, password: string) {
const response = await fetch("http://127.0.0.1:8000/api/token", {
method: "POST",
headers: {
"Content-Type" : "application/json"
},
body: JSON.stringify({
username,
password
})
});
const data = await response.json();
if (response.status === 200) {
setAuthTokens(data);
setUser(jwt_decode(data.access));
localStorage.setItem("authTokens", JSON.stringify(data));
redirect("/");
} else {
alert("Something went wrong on login!");
}
}
async function registerUser(username: string, password: string, password2: string) {
const response = await fetch("http://127.0.0.1:8000/api/register", {
method: "POST",
headers: {
"Content-Type" : "application/json"
},
body: JSON.stringify({
username,
password,
password2
})
});
if (response.status === 201) {
redirect("/login");
} else {
alert("Something went wrong!");
}
}
async function logoutUser() {
setAuthTokens(null);
setUser(null);
localStorage.removeItem("authTokens");
redirect("/");
};
useEffect(() => {
if (authTokens) {
setUser(jwt_decode(authTokens.access));
}
setLoading(false);
}, [authTokens, loading]);
const contextData: contextData = {
user: user,
setUser: setUser,
authTokens: authTokens,
setAuthTokens: setAuthTokens,
registerUser: registerUser,
loginUser: loginUser,
logoutUser:
};
return (
<AuthContext.Provider value={}>
{loading ? null: children}
</AuthContext.Provider>
)
}
export default AuthContext;
I'm stuck with the
const contextData = {
user,
setUser,
authTokens,
setAuthTokens,
registerUser,
loginUser,
logoutUser
};
and value in
return (
<AuthContext.Provider value={contextData}>
{loading ? null : children}
</AuthContext.Provider>
);
I think that the constant should actually be an interface definition with a const that pulls all of that together and takes it's place? I've failed to get it to work though.
I'm also not sure what to do about value, since it always seems to yell at me about typing it even though it's defined as in the TypeScript types for createContext, so I though that meant it's essentially any?
I could just be vastly misunderstanding everything and stuck fooling myself that I'm kind of understanding any of this. Any guidance would be appreciated.
You are right in that you need a type definition for the contextData. Something like this:
interface AuthToken {
name: string
exp: number
}
interface ContextData {
user: string | null
setUser: (value: string) => void,
authTokens: AuthToken | null, // AuthToken[] | null if multiple
setAuthTokens: (authToken: string) => void,
registerUser: (username: string, password: string, password2: string) => void,
loginUser: (username: string, password: string) => void,
logoutUser: () => void
};

dispatch method wont update state

Here with a dispatch() problem, i'm trying to create a post using dispatch() but when i submit the post i get a PATCH 404 not found. Now that error comes from updatePost action which i'm guessing its throwing the error due to no data being present after trying to create a post using createPost action. When i console.log(postData) data is present in the form.js file when submitted but currentId is not and thats the goal. Everything was working well until i decided to add middleware, but even when i submit a createPost action i still get 204 status in the network tab. I even console.log(req.headers) in my middleware - auth.js and shows my access token from bearer. I'd like to think in my form.js file maybe is something with useSelector or useState. so i did one last try and console.log(setPostData) and got an error which says, 'state updates from the useState()'.
Thanks for anyone who helps.
Form.js
import FileBase from 'react-file-base64';
import { TextField, Button, Typography, Paper } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import useStyles from './style';
import { createPost, updatePost } from '../../actions/posts';
const Form = ({ currentId, setCurrentId }) => {
const classes = useStyles();
const [postData, setPostData] = useState({ title:'', message: '', tags: '', selectedFile: '' });
const post = useSelector((state) => (currentId ? state.posts.find((message) => message._id === currentId) : null));
const dispatch = useDispatch();
const user = JSON.parse(localStorage.getItem('profile'));
useEffect(() => {
if (post) setPostData(post);
}, [post]);
const clear = () => {
setCurrentId(0);
setPostData({ title: '', message: '', tags: '', selectedFile: '' });
};
const handleSubmit = async (e) => {
e.preventDefault();
if (currentId === 0 ){
dispatch(createPost({ ...postData, name: user?.result?.name }));
}else{
dispatch(updatePost(currentId, { ...postData, name: user?.result?.name }));
}
clear();
console.log(currentId);
};
if(!user?.result?.name){
return(
<Paper className={classes.paper}>
<Typography variant='h6' align='center'>
Please Sign In to create / interact with posts.
</Typography>
</Paper>
)
}
return(
<Paper className={classes.paper}>
<form autoComplete='off' noValidate className={classes.form} onSubmit={handleSubmit}>
<Typography variant="h6">{currentId ? 'edit' : 'create'} a Post</Typography>
<TextField className={classes.input} name="title" variant="outlined" label="Title" fullWidth value={postData.title} onChange={(e) => setPostData({ ...postData, title: e.target.value})} />
<TextField className={classes.input} name="message" variant="outlined" label="Message" fullWidth multiline minRows={4} value={postData.message} onChange={(e) => setPostData({ ...postData, message: e.target.value })} />
<TextField className={classes.input} name="tags" variant="outlined" label="Tags (coma separated)" fullWidth value={postData.tags} onChange={(e) => setPostData({ ...postData, tags: e.target.value.split(',') })} />
<div className={classes.fileInput}> <FileBase className={classes.file} type='file' multiple={false} onDone={({base64}) => setPostData({ ...postData, selectedFile: base64})} /> </div>
<Button className={classes.buttonSubmit} variant="contained" color="primary" size="large" type="submit" fullWidth >Submit</Button>
<Button className={classes.clearSubmit} variant="contained" color="secondary" size="small" onClick={clear} fullWidth >Clear</Button>
</form>
</Paper>
);
}
export default Form;
reducers - posts.js
const actions = (posts = [], action) => {
switch (action.type) {
case FETCH_ALL:
return action.payload;
case CREATE:
return [...posts, action.payload];
case UPDATE:
return posts.map((post) => (post._id === action.payload._id ? action.payload : post));
case LIKE:
return posts.map((post) => (post._id === action.payload._id ? action.payload : post));
case DELETE:
return posts.filter((post) => post._id !== action.payload);
default:
return posts;
}
};
export default actions;
Actions - posts.js
import * as api from '../api/index';
export const getPosts = () => async (dispatch) => {
try {
const { data } = await api.fetchPosts();
dispatch({ type: FETCH_ALL, payload: data })
} catch (error) {
console.log(error);
}
};
export const createPost = (newPost) => async (dispatch) => {
try {
const { data } = await api.createPost(newPost);
dispatch({ type: CREATE, payload: data })
} catch (error) {
console.log(error.message);
}
};
export const updatePost = (id, post) => async (dispatch) => {
try{
const { data } = await api.updatedPost(id, post);
dispatch({ type: UPDATE, payload: data });
} catch (error){
console.log(error.message);
}
};
export const likePost = (id) => async (dispatch) => {
const user = JSON.parse(localStorage.getItem('profile'));
try {
const { data } = await api.likePost(id, user?.token);
dispatch({ type: LIKE, payload: data });
}catch (error) {
console.log(error);
}
};
export const deletePost = (id) => async (dispatch) => {
try{
await api.deletePost(id);
dispatch({ type: DELETE, payload: id });
}catch (error) {
console.log(error);
}
};
api - index.js
const API = axios.create({ baseURL: 'http://localhost:5000' });
API.interceptors.request.use((req) => {
if(localStorage.getItem('profile')){
req.headers.authorization = `Bearer ${JSON.parse(localStorage.getItem('profile')).token}`;
}
return req;
})
export const fetchPosts = () => API.get('/posts');
export const createPost = (newPost) => API.post('/posts', newPost);
export const updatedPost = (id, post) => API.patch(`/posts/${id}`, post);
export const likePost = (id) => API.patch(`/posts/${id}/likePost`);
export const deletePost = (id) => API.delete(`/posts/${id}`);
export const signin = (form) => API.post('/user/signin', form);
export const RegisterNow = (formData) => API.post('/user/RegisterNow', formData);
middleware - auth.js
const secret = 'test';
const auth = async (req, res, next) => {
try {
const token = req.headers.authorization.split(" ")[1];
const isCustomAuth = token.length < 500;
let decodedData;
if (token && isCustomAuth) {
decodedData = jwt.verify(token, secret);
req.userId = decodedData?.id;
} else {
decodedData = jwt.decode(token);
req.userId = decodedData?.sub;
}
next();
} catch (error) {
console.log(error);
}
};
export default auth;

createAsyncThunk function returns userId undefined data attribute

I'm trying to fetch appointment data from my rails-api backend using the createAsyncThunk function. When I
console.log('appointmentDate', appointmentDate);
console.log('doctorId', doctorId);
console.log('userId', userId);
After clicking the submit button to create a new appointment; I get:
appointmentDate => 2021-07-30 which is fine
doctorId => 2 which is also fine.
userId => undefined which is not what I'm expecting. I expected a number just like doctorId
I have tested the backend with postman and everything is fine. And since I can't fetch the correct data. I can't create an appointment when I submit the form
This is how I'm destructuring the user state before adding it to the postAppointment action creator for dispatch
const { data: userData } = useSelector((state) => state.user);
const { userId } = userData;
since `user_Id` is part of the `user` state in my `store`. I can destructure it as above and add it to my `dispatch`.
And this is how I'm dispatching it inside the NewAppointment component
dispatch(postAppointments({ userId, doctorId, appointmentDate }))
.then(() => {
setSuccessful(true);
alert.show('Appointment created', {
type: 'success',
timeout: 2000,
});
setLoading(false);
})
.catch((error) => {
console.log(error.message);
setSuccessful(false);
});
I don't know what I'm missing with the user destructuring and dispatching the action creator?
Here are the other codes
src/redux/appointmentsSlice
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import API from '../api/api';
export const postAppointments = createAsyncThunk(
'appointments/postAppointments',
async (
{
userId, appointmentDate, doctorId,
},
) => {
console.log('appointmentDate', appointmentDate);
console.log('doctorId', doctorId);
console.log('userId', userId);
const response = await fetch(`${API}/users/${userId}/appointments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
appointmentDate,
doctorId,
userId,
}),
});
const data = await response.json();
console.log('appointmentsData', data);
if (!response.ok) throw new Error(data.failure);
localStorage.setItem('token', data.jwt);
console.log('localstorageData', data);
return data;
},
);
export const appointmentsSlice = createSlice({
name: 'appointments',
initialState: {
loading: false,
error: null,
data: [],
},
extraReducers: {
[postAppointments.pending]: (state) => {
state.loading = true;
},
[postAppointments.rejected]: (state, action) => {
state.loading = false;
state.error = action.error.message;
},
[postAppointments.fulfilled]: (state, action) => {
state.loading = false;
state.data = action.payload;
},
},
});
export default appointmentsSlice.reducer;
src/components/NewAppointment
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { postAppointments } from '../redux/appointmentsSlice';
import { getDoctors } from '../redux/doctorsSlice';
const NewAppointment = () => {
const [appointmentDate, setAppointmentDate] = useState('');
const [doctorId, setDoctorId] = useState('');
const [successful, setSuccessful] = useState(false);
const [loading, setLoading] = useState(false);
const { data: userData } = useSelector((state) => state.user);
const { userId } = userData;
console.log('userData', userData);
const dispatch = useDispatch();
const { data, error } = useSelector((state) => state.doctors);
console.log('data', data);
useEffect(() => {
if (data === null && userData) {
dispatch(getDoctors())
.then(() => {
loading(false);
})
.catch(() => {
// setError('Unable to get doctors list');
});
}
}, [data, dispatch]);
const onChangeDoctorId = (e) => {
const doctorId = e.target.value;
setDoctorId(doctorId);
console.log('doctorUnchange', doctorId);
};
const onChangeAppointmentDate = (e) => {
const appointmentDate = e.target.value;
setAppointmentDate(appointmentDate);
console.log('apptntmentonchange', appointmentDate);
};
const handleBooking = (e) => {
e.preventDefault();
setSuccessful(false);
// eslint-disable-next-line no-underscore-dangle
dispatch(postAppointments({ userId, doctorId, appointmentDate }))
.then(() => {
setSuccessful(true);
alert.show('Appointment created', {
type: 'success',
timeout: 2000,
});
setLoading(false);
})
.catch((error) => {
console.log(error.message);
setSuccessful(false);
});
};
console.log('data now', data);
const options = data && (
data.map((doctor) => (
<option
key={doctor.id}
value={doctor.id}
>
{doctor.name}
</option>
))
);
if (!userData) {
return <Redirect to="/login" />;
}
if (successful) {
return <Redirect to="/appointments" />;
}
return (
<div className="col-md-12">
<div className="card card-container">
<form onSubmit={handleBooking}>
{ !successful && (
<div>
<div className="form-group create">
<label htmlFor="appointmentDate" className="control-label">
Appointment Date
<input
type="date"
className="form-control"
name="appointmentDate"
id="appointmentDate"
required
value={appointmentDate}
onChange={onChangeAppointmentDate}
/>
</label>
</div>
<div className="form-group create">
<label htmlFor="doctorId">
Select from list:
<select className="form-control" id="doctorId" onChange={onChangeDoctorId} value={doctorId}>
{loading ? <option>Loading..</option> : options }
</select>
</label>
</div>
<div className="form-group create">
<button className="btn btn-primary btn-block" disabled={loading} type="submit">
{loading && (
<span className="spinner-border spinner-border-sm" />
)}
<span>Book</span>
</button>
</div>
</div>
)}
{error && (
<div className="form-group">
<div className={successful ? 'alert alert-success' : 'alert alert-danger'} role="alert">
{error}
</div>
</div>
)}
</form>
</div>
</div>
);
};
export default NewAppointment;
And the API url returns undefined for the userId variable as shown below
POST https://agile-escarpment-87534.herokuapp.com/api/v1/users/undefined/appointments 404 (Not Found)
This is my first project using createAsyncThunk and still trying to understand how it works. I have also checked similar posts, but none solved my issue.
Any support or constructive criticism is welcome.
you will need the token from here
const { user_id, jwt } = userData;
add the extracted token from the userData to postAppointments
dispatch(postAppointments({ user_id, doctor_id, appointment_date, jwt }))
.then(() => {
setSuccessful(true);
alert.show('Appointment created', {
type: 'success',
timeout: 2000,
});
setLoading(false);
})
.catch((error) => {
console.log(error.message);
setSuccessful(false);
});
finally add the token to the fetch request
export const postAppointments = createAsyncThunk(
'appointments/postAppointments',
async (
{
user_id, appointment_date, doctor_id, jwt
},
) => {
console.log('appointmentDate', appointment_date);
console.log('doctor_id', doctor_id);
console.log('user_id', user_id);
const response = await fetch(`${API}/appointments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
// this your missing header
Authorization: `Bearer ${jwt}`
},
body: JSON.stringify({
appointment_date,
doctor_id,
user_id,
}),
});
const data = await response.json();
console.log('appointmentsData', data);
if (!response.ok) throw new Error(data.failure);
localStorage.setItem('token', data.jwt);
console.log('localstorageData', data);
return data;
},
);
This is just a guess.. because I don't have actual code.
UPDATE:
Another way to write that fetch:
fetch(`${API}/appointments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${jwt}`
},
body: JSON.stringify({
appointment_date,
doctor_id,
user_id,
}),
}).then(response => {
response.json();
}).then(data => {
console.log('Success:', data);
localStorage.setItem('token', data.jwt);
}).catch((error) => {
// request just fail
throw new Error('Error:', error);
});
inspired by: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

POST http://localhost:3000/api/v1/stories 401 (Unauthorized)

Hi I am new to redux and authentication. I am creating a react/redux app where a user can login and be able to add a story by submitting a form. I am able to login but when I get to the story creation page, I click submit and I get a POST http://localhost:3000/api/v1/stories 401 (Unauthorized) error.
I am logging in using an API that gives a token on login. I then save the username and token to sessionstorage. But how would I fix this error?
App.js
import './App.scss';
import Login from './components/Login';
import { Router, Switch, Route, NavLink } from 'react-router-dom';
import PrivateRoute from './utils/PrivateRoute';
import CreateStory from './components/CreateStory';
import history from './utils/history';
function App() {
return (
<div className="App">
<Router history={history}>
<Switch>
<Route exact path="/" component={Login} />
<PrivateRoute path="/user" component={CreateStory}/>
</Switch>
</Router>
</div>
);
}
export default App;
PrivateRoute.js
import { useSelector } from 'react-redux'
// handle the private routes
function PrivateRoute({ component: Component, ...rest }) {
const getToken = useSelector((state)=> state.loginReducer.token)
console.log(getToken)
return (
<Route
{...rest}
render={(props) => getToken ? <Component {...props} /> : <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
)
}
export default PrivateRoute;
CreateStory.js
import React, { useState } from 'react'
import { createStory } from '../redux/actions'
import { useDispatch } from "react-redux";
const CreateStory = () => {
const [summary, setSummary] = useState("");
const [description, setDescription] = useState("");
const [type, setType] = useState("");
const [complexity, setcomplexity] = useState("");
const usedispatch = useDispatch();
const userCreateStory = (summary, description, type, complexity) => usedispatch(createStory({
'summary': summary,
'description': description,
'type': type,
'complexity': complexity
}));
const handleSummaryChange = e => {
setSummary(e.target.value)
}
const handleDescriptionChange = e => {
setDescription(e.target.value)
}
const handleTypeChange = e => {
setType(e.target.value)
}
const handleComplexityChange = e => {
setcomplexity(e.target.value)
}
const handleSubmit = e => {
e.preventDefault();
userCreateStory('a','b','c','d')
// setTimeout(()=> history.push("/user"), 1000 );
}
return (
<div>
<form className='create-story-form'>
<label for="summary">Summary:</label>
<input name="summary" type='text' onChange={handleSummaryChange}/>
<label for="desc">Description:</label>
<textarea name="desc" type='text' onChange={handleDescriptionChange}/>
<label for="type">Type:</label>
<select name="type">
<option value="enhancement">Enchancement</option>
<option value="bugfix">Bugfix</option>
<option value="development">Development</option>
<option value="qa">QA</option>
</select>
<label for="complexity">Complexity:</label>
<select name="complexity">
<option value="Low">Low</option>
<option value="Mid">Mid</option>
<option value="High">High</option>
</select>
<label for="time">Estimated time for completion:</label>
<input name="time" type='text' />
<label for="cost">Cost:</label>
<input name="cost" type='number' />
<button onClick={handleSubmit}>Submit</button>
</form>
</div>
)
}
export default CreateStory;
Login.js
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { login, roleChange } from '../redux/actions' //OUR ACTIONS
import { useSelector } from 'react-redux'
import history from '../utils/history';
import { withRouter } from 'react-router-dom';
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const usedispatch = useDispatch();
const userLogin = (email, password) => usedispatch(login({'email': email, 'password': password }));
const switchToAdmin = () => usedispatch(roleChange('admin'));
const switchToUser = () => usedispatch(roleChange('user'));
const currentRole = useSelector((state)=> state.loginReducer.role)
const handleRoleChange = e => {
e.preventDefault();
if(currentRole === 'user')
switchToAdmin();
else if(currentRole === 'admin' )
switchToUser()
}
const handleEmailChange = e => {
setEmail(e.target.value)
}
const handlePasswordChange = e => {
setPassword(e.target.value)
}
const handleSubmit = e => {
e.preventDefault();
userLogin(email, password)
setTimeout(()=> history.push("/user"), 1000 );
}
const disabled = () => {
return email === "" || password === ""
}
return (
<div>
<form className='login-form'>
<input type='email' name='email' placeholder='Email' onChange={handleEmailChange}/>
<input type='password' name='password' placeholder='Password' onChange={handlePasswordChange}/>
<button type='submit' disabled={disabled()} onClick={handleSubmit}>Login</button>
</form>
<button onClick={handleRoleChange}>Switch to {currentRole === 'user' ? 'admin' : 'user'}</button>
</div>
)
}
export default withRouter(Login);
actionTypes.js
export const SET_LOGIN_STATE = "SET_LOGIN_STATE"
export const SET_ROLE_STATE = "SET_ROLE_STATE"
export const CREATE_STORY = "CREATE_STORY"
initialState.js:
import { getToken } from '../utils/Common'
export const initialState = {
isLoggedIn: false,
userId: '',
role: 'user',
token: getToken,
data: '',
};
reducers.js
import { initialState } from './initialState';
import * as t from './actionTypes';
export const loginReducer = (state = initialState, action) => {
switch (action.type) {
case t.SET_ROLE_STATE:
return {
...state,
role: action.payload,
};
case t.SET_LOGIN_STATE:
return {
...state,
...action.payload, // this is what we expect to get back from API call and login page input
isLoggedIn: true, // we set this as true on login
};
default:
return state;
}
};
export const storyReducer = (state = initialState, action) => {
switch (action.type) {
case t.CREATE_STORY:
return {
...state,
role: action.payload,
};
default:
return state;
}
}
actions.js:
import * as t from './actionTypes';
import { setUserSession } from '../utils/Common';
// this is what our action should look like which dispatches the "payload" to reducer
const setLoginState = (loginData) => {
return {
type: t.SET_LOGIN_STATE,
payload: loginData, //{ ...json, userId: email }
};
};
const setStoryState = (storyData) => {
return {
type: t.CREATE_STORY,
payload: storyData,
};
};
export const login = (loginInput) => { //our login action
const { email, password } = loginInput;
return (dispatch) => { // don't forget to use dispatch here!
return fetch('http://localhost:3000/api/v1/signin', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(loginInput),
})
.then((response) => response.json()) //json will be the response body
.then((json) => {
// if (json.msg === 'success') { // response success checking logic could differ
// console.log(json)
dispatch(setLoginState({ ...json, userId: email })); // our action is called here with object as parameter, this is our payload
//we appended json object to our state
// } else {
// alert('Login Failed', 'Email or Password is incorrect');
// }
setUserSession(json.token, json.lastName)
})
.catch((err) => {
alert('Login Failed', 'Some error occured, please retry');
console.log(err);
});
};
};
export const roleChange = role => {
return {
type: t.SET_ROLE_STATE,
payload: role
};
}
/**
* story input:
{
"summary": "string",
"description": "string",
"type": "string",
"complexity": "string"
}
*/
export const createStory = storyInput => {
const { summary, description, type, complexity } = storyInput;
return (dispatch) => { // don't forget to use dispatch here!
return fetch('http://localhost:3000/api/v1/stories', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(storyInput),
})
.then((response) => response.json()) //json will be the response body
.then((json) => {
// if (json.msg === 'success') { // response success checking logic could differ
console.log(json)
// dispatch(setStoryState({ // our action is called here with object as parameter, this is our payload
// summary: summary,
// description: description,
// type: type,
// complexity: complexity
// })); // our action is called here
// } else {
// alert('Login Failed', 'Email or Password is incorrect');
// }
})
.catch((err) => {
alert('Some error occured, please retry');
console.log(err);
});
};
}
Common.js
// return the user data from the session storage
export const getUser = () => {
const userStr = sessionStorage.getItem('user');
if (userStr) return JSON.parse(userStr);
else return null;
}
// return the token from the session storage
export const getToken = () => {
return sessionStorage.getItem('token') || null;
}
// remove the token and user from the session storage
export const removeUserSession = () => {
sessionStorage.removeItem('token');
sessionStorage.removeItem('user');
}
// set the token and user from the session storage
export const setUserSession = (token, user) => {
sessionStorage.setItem('token', token);
sessionStorage.setItem('user', JSON.stringify(user));
}
You'll have to pass the auth token from the sessionStorage to the header of API you are posting your story to :-
const token = sessionStorage.getItem('token'); //Add this line
return fetch('http://localhost:3000/api/v1/stories', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}` //Add this line
},
body: JSON.stringify(storyInput),
})

Categories