React Redux state re-render issues with object from mongodb - javascript

These are my codes for creating a new blog post:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { createBlog, reset } from "../features/blogSlice";
const Createblog = () => {
const dispatch = useDispatch();
const [title, setTitle] = useState("");
const [texts, setTexts] = useState("");
const history = useNavigate();
const { user, isError, isSuccess, message } = useSelector((state) => {
return state.auth;
});
useEffect(() => {
if (!user) {
history("/login");
}
dispatch(reset());
}, [user, isError, isSuccess, message, history, dispatch]);
const handleSubmit = async (e) => {
e.preventDefault();
const data = {
title,
texts,
};
dispatch(createBlog(data));
history("/");
};
return (
<>
<div className="container mt-3">
<div className="row">
<div className="col-3"></div>
<div className="col-6 bg-info">
<h1>Create A New Blog</h1>
</div>
<div className="col-3"></div>
</div>
<div className="row">
<div className="col-3"></div>
<div className="col-6">
<form className="mt-3">
<input
type="text"
name="title"
className="form-control my-2"
placeholder="Type your Title"
onChange={(e) => setTitle(e.target.value)}
/>
<textarea
name="texts"
className="form-control"
id="exampleFormControlTextarea1"
rows="10"
onChange={(e) => setTexts(e.target.value)}
></textarea>
<button className="btn btn-primary col-6 my-2" onClick={handleSubmit}>
Click to post
</button>
</form>
</div>
<div className="col-3"></div>
</div>
</div>
</>
);
};
export default Createblog;
This is my redux slice for creating the Blog
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import blogService from "./blogService";
const initialState = {
blogs: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
export const createBlog = createAsyncThunk("blogs/createBlog", async (blogData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await blogService.createBlog(blogData, token);
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString();
return thunkAPI.rejectWithValue(message);
}
});
export const getBlogs = createAsyncThunk("blogs/", async (_, thunkAPI) => {
try {
// const token = thunkAPI.getState().auth.user.token;
return await blogService.getBlogs();
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString();
return thunkAPI.rejectWithValue(message);
}
});
const blogSlice = createSlice({
name: "blogs",
initialState,
reducers: {
reset: (state) => {
return initialState;
},
},
extraReducers: (builder) => {
// Get All Blogs
builder
.addCase(getBlogs.pending, (state) => {
state.isLoading = true;
})
.addCase(getBlogs.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.blogs = action.payload;
})
.addCase(getBlogs.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
// create blog
.addCase(createBlog.pending, (state) => {
state.isLoading = true;
})
.addCase(createBlog.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.blogs.push(action.payload);
})
.addCase(createBlog.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
export const { reset } = blogSlice.actions;
export default blogSlice.reducer;
And, these are the redux blog service codes:
import Axios from "axios";
const api_Link = "http://localhost:8080/";
const createBlog = async (blogData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await Axios.post(api_Link + `blog`, blogData, config);
return response.data;
};
const getBlogs = async () => {
const response = await Axios.get(api_Link);
const data = response.data.data;
return data;
};
const blogService = {
createBlog,
getBlogs,
};
export default blogService;
And, this is how I view the blog posts:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { getBlogs, reset } from "../features/blogSlice";
const Blogpage = () => {
const dispatch = useDispatch();
const history = useNavigate();
const { blogs, isSuccess, isLoading, isError, message } = useSelector((state) => state.blogs);
useEffect(() => {
if (isError) {
console.log(message);
}
dispatch(getBlogs());
return () => {
dispatch(reset());
};
}, [history, isError, message, dispatch]);
return (
<>
<div className="container">
<div className="row">
<div className="col">
<h1 className="text-centr">Following are your blogs</h1>
</div>
</div>
{blogs.map((item, sl) => {
return (
<section className="bg-success text-light mt-4" key={sl}>
<div className="row">
<div className="col">
<h1 className="text-start">{item.title}</h1>
<h5 className="text-start bg-success text-light">Author: {item.creator.name}</h5>
<p className="text-start">{item.texts}</p>
</div>
</div>
</section>
);
})}
</div>
</>
);
};
export default Blogpage;
Normally I can view the blog posts. Creating blog, redirecting everything works just fine. The problem occurs only when I try to retrieve anything from the creator field. Please see below. Here is what I get from db
{
_id: new ObjectId("62b7a94583a09428b900a069"),
date: 2022-06-26T00:33:09.838Z,
title: 'This is title',
texts: 'These are texts',
creator: {
_id: new ObjectId("62ae7514d2106b408c190667"),
name: 'Md.Rashel',
email: 'example#gmail.com',
}
Whenever I try to retrieve item.creator.name it shows this error : "Uncaught TypeError: Cannot read properties of undefined (reading 'name')". But, once I reload the page, it works fine again.
I'm not sure how to fix this. I'm not also sure whether it is redux issue or react issue. Could you please help fix the issue? Thanks in advance.

Related

React Redux How to ensure Item is deleted without needing to refresh page using dispatch and slice

I am working on the following tutorial: https://www.youtube.com/watch?v=UXjMo25Nnvc&list=PLillGF-RfqbbQeVSccR9PGKHzPJSWqcsm&index=4
Full GitHub project here from Brad Traversy: https://github.com/bradtraversy/mern-tutorial
For the Delete Goals section (starting at 37:06 to 44:56 timestamp)
Dashboard file: https://github.com/htqanh305/vocab-app/blob/main/frontend/src/pages/Dashboard.jsx
import {useEffect} from 'react'
import {useNavigate} from 'react-router-dom' // for redirecting
import {useSelector, useDispatch} from 'react-redux' // grab user to check state
import GoalForm from '../components/GoalForm'
import GoalItem from '../components/GoalItem'
import Spinner from '../components/Spinner'
import {getGoals, reset} from '../features/goals/goalSlice'
function Dashboard() {
const navigate = useNavigate()
const dispatch = useDispatch()
const {user} = useSelector((state) => state.auth)
const {goals, isLoading, isError, message} = useSelector((state) => state.goals)
useEffect(() => {
if(isError) {
console.log(message)
}
if(!user) {
navigate('/login')
}
dispatch(getGoals())
console.log("I reached this point")
return () => {
dispatch(reset())
}
}, [user, navigate, isError, message, dispatch])
if(isLoading) {
return <Spinner />
}
if(!user) {
navigate('/login')
} else {
return (
<>
<section className="heading">
<h1>Welcome {user.name} </h1>
<p>Goals Dashboard</p>
</section>
<GoalForm/>
<section className="content">
{goals.length > 0 ? (
<div className="goals">
{goals.map((goal) => (
<GoalItem key={goal._id} goal={goal} />
))}
</div>
) :
(<h3> You have not set any goals </h3>)}
</section>
</>
)
}
}
export default Dashboard
goalSlice file: https://github.com/bradtraversy/mern-tutorial/blob/main/frontend/src/features/goals/goalSlice.js
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit'
import goalService from './goalService'
const initialState = {
goals: [],
isError: false,
isSuccess: false,
isLoading: false,
message: ''
}
// Create new goal
//thunkAPI object has a getState method that helps get anything we want/ any part of the state
// ie. get auth state to get token to access user so we can set/get goals
export const createGoal = createAsyncThunk('goals/create', async(goalData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.createGoal(goalData, token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
} )
// Get user goals
export const getGoals = createAsyncThunk('goals/getAll', async (_, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.getGoals(token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
// Delete goal
export const deleteGoal = createAsyncThunk('goals/delete', async(id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.deleteGoal(id, token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
} )
export const goalSlice = createSlice({
name: 'goal',
initialState,
reducers: {
reset: (state) => initialState
},
extraReducers: (builder) => {
builder
.addCase(createGoal.pending, (state) => {
state.isLoading = true
})
.addCase(createGoal.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals.push(action.payload)
})
.addCase(createGoal.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(getGoals.pending, (state) => {
state.isLoading = true
})
.addCase(getGoals.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals = action.payload
})
.addCase(getGoals.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(deleteGoal.pending, (state) => {
state.isLoading = true
})
.addCase(deleteGoal.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
// filter out the UI when delete a goal, only show goals that are not deleted
console.log("confirm")
state.goals = state.goals.filter(
goal => goal._id !== action.payload.id)
console.log(action.payload.id)
console.log(state.goals)
})
.addCase(deleteGoal.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
}
})
export const {reset} = goalSlice.actions
export default goalSlice.reducer
GoalItem file:
import {useDispatch} from 'react-redux'
import { deleteGoal } from '../features/goals/goalSlice'
function GoalItem({goal}) {
const dispatch = useDispatch()
return (
<div className="goal">
<div>
{new Date(goal.createdAt).toLocaleDateString('en-US')}
</div>
<h2>{goal.text}</h2>
<button onClick={() => dispatch(deleteGoal(goal._id))} className="close">X</button>
</div>
)
}
export default GoalItem
I'm following along in the tutorial, and no matter how much I try, I can't seem to get the delete goal functionality working.
When I create a new goal (item) and try to delete it, the item still remains on the page until I refresh or click on it twice. I'm not sure if it's something wrong with the goalSlice function or my Dashboard file, but for some reason it's not working.
I followed the tutorial to a tee, but it still looks like it isn't doing what it's supposed to.
Does anyone happen to know what is causing this Redux issue, or if I'm going crazy? Any suggestions or advice would be helpful. Thanks!

Redux toolkit put request is not working, state undefined

I am having tough time updating blog post via React Redux Axio. My server is an Express JS server with Mongodb as database.
This is my post slice:
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import blogService from "./blogService";
const initialState = {
allposts: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
export const getAllPosts = createAsyncThunk("allposts", async (post) => {
try {
return await blogService.getAllPosts(post);
} catch (error) {
console.log("Error here");
return error;
}
});
export const updatePost = createAsyncThunk("update", async (id, { title, body }) => {
try {
return await blogService.editPost(id, { title, body });
} catch (error) {
console.log("Error here");
return error;
}
});
const postSlice = createSlice({
name: "allposts",
initialState,
reducers: {
reset: (state) => {
return initialState;
},
},
extraReducers: (builder) => {
// Get All Blogs
builder
.addCase(getAllPosts.pending, (state) => {
state.isLoading = true;
})
.addCase(getAllPosts.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.allposts = action.payload;
})
.addCase(getAllPosts.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
// Edit blog post
.addCase(updatePost.pending, (state) => {
state.isLoading = true;
})
.addCase(updatePost.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
console.log(action.payload);
state.map((post) => {
if (post.id === action.payload.id) {
post.title = action.payload.title;
post.body = action.payload.body;
}
});
})
.addCase(updatePost.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
export const { reset } = postSlice.actions;
export default postSlice.reducer;
And, This is blogService
import Axios from "axios";
const api_Link = "http://localhost:8080/";
const getAllPosts = async () => {
const response = await Axios.get(api_Link + `blogs`);
const data = response.data.data;
return data;
};
const getSinglePost = async (id) => {
const response = await Axios.get(api_Link + `blogs/` + id);
const data = response.data.data;
return data;
};
const deletePost = async (id) => {
const response = await Axios.delete(api_Link + `blogs/` + id);
const data = response.data;
return data;
};
const editPost = async (id, { title, body }) => {
const response = await Axios.put(api_Link + `blogs/` + id, {
title,
body,
});
const data = response.data;
console.log(data);
return data;
};
const blogService = {
getAllPosts,
getSinglePost,
deletePost,
editPost,
};
export default blogService;
And, finally, this is the edit Page:
import React from "react";
import { useEffect } from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { updatePost } from "../features/blogs/postSlice";
const Editpage = () => {
const params = useParams();
const navigate = useNavigate();
const dispatch = useDispatch();
console.log(params.id);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(updatePost(params.id, { title: "This is title", body: "This is body" }));
};
return (
<>
<div className="container">
<div className="edit_Section">
<h1 className="text-center p-3 text-danger bg-light">Edit The Post</h1>
<div className="myForm d-flex justify-content-center bg-secondary text-light p-5">
<form className="w-50 border border-primary p-5 bg-primary">
<div className="mb-3">
<label htmlFor="postTitle" className="form-label">
Post Title
</label>
<input
type="text"
name="title"
value={postTitle}
onChange={(e) => setTitle(e.target.value)}
className="form-control"
id="postTitle"
/>
</div>
<div className="mb-3">
<label htmlFor="postTitle" className="form-label">
Post Body
</label>
<textarea
className="form-control"
value={postBody}
onChange={(e) => setBody(e.target.value)}
name="body"
id="postBody"
rows="10"
></textarea>
</div>
<button type="submit" className="btn btn-success" id={params.id} onClick={handleSubmit}>
Save Changes
</button>
</form>
</div>
</div>
</div>
</>
);
};
export default Editpage;
My primary objective is to update my mongodb post collection. I can create, delete and read. Only, updating db via React Redux is the issue I couldn't fix myself. It says, state is undefined in the postslice page. I'm not sure how to get that state to work.

Log-in page not redirecting to user dashboard after firebase log-in

I'm trying to make protected routes for my website using React and firebase. It works fine when users log-in with email but when users log-in with firebase, the page refreshes before it has time to update verification status so it reloads to the log-in page again but when I manually refresh, it goes to the protected page. I'm not sure how to fix so that it will work for firebase log-in as well.
Actions.js:
import * as types from "./actionTypes";
import { auth, googleAuthProvider, facebookAuthProvider } from '../Firebase';
const registerStart = () => ({
type: types.REGISTER_START,
});
const registerSuccess = (user) => ({
type: types.REGISTER_SUCCESS,
payload: user,
});
const registerFail = (error) => ({
type: types.REGISTER_FAIL,
payload: error,
});
const loginStart = () => ({
type: types.LOGIN_START,
});
const loginSuccess = (user) => ({
type: types.LOGIN_SUCCESS,
payload: user,
});
const loginFail = (error) => ({
type: types.LOGIN_FAIL,
payload: error,
});
const logoutStart = () => ({
type: types.LOGOUT_START,
});
const logoutSuccess = (user) => ({
type: types.LOGOUT_SUCCESS,
});
const logoutFail = (error) => ({
type: types.LOGOUT_FAIL,
payload: error,
});
export const setUser = (user) => ({
type: types.SET_USER,
payload: user,
})
const googleSignInStart = () => ({
type: types.GOOGLE_SIGN_IN_START,
});
const googleSignInSuccess = (user) => ({
type: types.GOOGLE_SIGN_IN_SUCCESS,
});
const googleSignInFail = (error) => ({
type: types.GOOGLE_SIGN_IN_FAIL,
payload: error,
});
const fbSignInStart = () => ({
type: types.FACEBOOK_SIGN_IN_START,
});
const fbSignInSuccess = (user) => ({
type: types.FACEBOOK_SIGN_IN_SUCCESS,
});
const fbSignInFail = (error) => ({
type: types.FACEBOOK_SIGN_IN_FAIL,
payload: error,
});
export const registerInitiate = (email, password, displayName) => {
return function (dispatch) {
dispatch(registerStart());
auth.createUserWithEmailAndPassword(email, password).then(({user}) => {
user.updateProfile({
displayName
})
dispatch(registerSuccess(user));
}).catch((error) => dispatch(registerFail(error.message)))
}
};
export const loginInitiate = (email, password) => {
return function (dispatch) {
dispatch(loginStart());
auth.signInWithEmailAndPassword(email, password).then(({user}) => {
dispatch(loginSuccess(user));
}).catch((error) => dispatch(loginFail(error.message)))
}
};
export const logoutInitiate = () => {
return function (dispatch) {
dispatch(logoutStart());
auth.signOut().then((resp) =>
dispatch(logoutSuccess())
).catch((error) => dispatch(logoutFail(error.message)));
}
};
export const googleSignInInitiate = () => {
return function (dispatch) {
dispatch(googleSignInStart());
auth.signInWithPopup(googleAuthProvider).then(({user}) => {
dispatch(googleSignInSuccess(user));
}).catch((error) => dispatch(googleSignInFail(error.message)));
}
};
export const fbSignInInitiate = () => {
return function (dispatch) {
dispatch(fbSignInStart());
auth.signInWithPopup(facebookAuthProvider.addScope("user_birthday, email")).then(({user}) => {
dispatch(fbSignInSuccess(user));
}).catch((error) => dispatch(fbSignInFail(error.message)));
}
};
actionTypes.js:
export const REGISTER_START = "REGISTER_START";
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const LOGIN_START = "LOGIN_START";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT_START = "LOGOUT_START";
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
export const LOGOUT_FAIL = "LOGOUT_FAIL";
export const SET_USER = "SET_USER";
export const GOOGLE_SIGN_IN_START = "GOOGLE_SIGN_IN_START";
export const GOOGLE_SIGN_IN_SUCCESS = "GOOGLE_SIGN_IN_SUCCESS";
export const GOOGLE_SIGN_IN_FAIL = "GOOGLE_SIGN_IN_FAIL";
export const FACEBOOK_SIGN_IN_START = "FACEBOOK_SIGN_IN_START";
export const FACEBOOK_SIGN_IN_SUCCESS = "FACEBOOK_SIGN_IN_SUCCESS";
export const FACEBOOK_SIGN_IN_FAIL = "FACEBOOK_SIGN_IN_FAIL";
reducer.js:
const initialState = {
loading: false,
currentUser: null,
error: null,
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case types.REGISTER_START:
case types.LOGIN_START:
case types.LOGOUT_START:
case types.GOOGLE_SIGN_IN_START:
case types.FACEBOOK_SIGN_IN_START:
return {
...state,
loading: true
};
case types.LOGOUT_SUCCESS:
return {
...state,
currentUser: null,
}
case types.SET_USER:
return {
...state,
loading: false,
currentUser: action.payload,
}
case types.REGISTER_SUCCESS:
case types.LOGIN_SUCCESS:
case types.GOOGLE_SIGN_IN_SUCCESS:
case types.FACEBOOK_SIGN_IN_SUCCESS:
return {
...state,
loading: false,
currentUser: action.payload,
};
case types.REGISTER_FAIL:
case types.LOGIN_FAIL:
case types.LOGOUT_FAIL:
case types.GOOGLE_SIGN_IN_FAIL:
case types.FACEBOOK_SIGN_IN_FAIL:
return {
...state,
loading: false,
error: action.payload,
}
default:
return state;
}
}
export default userReducer;
rootReducer.js:
import userReducer from "./reducer";
const rootReducer = combineReducers({
user: userReducer
})
export default rootReducer;
store.js:
import { createStore, applyMiddleware } from "redux";
import { createLogger } from "redux-logger";
import thunk from "redux-thunk";
import rootReducer from './rootReducer';
const middleware = [thunk];
const logger = createLogger({});
if(process.env.NODE_ENV === "development") {
middleware.push(logger)
}
export const store = createStore(rootReducer, applyMiddleware(...middleware));
login.js:
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector} from "react-redux";
import { useNavigate, Link } from "react-router-dom";
import { fbSignInInitiate, googleSignInInitiate, loginInitiate } from '../../userauth/actions';
import './login.css';
const Login = () => {
const [state, setState] = useState({
email: "",
password: "",
});
const { email, password } = state;
const { currentUser } = useSelector((state) => state.user);
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
useEffect(() => {
if(currentUser || loading) {
navigate("/dashboard");
}
}, [currentUser, navigate]);
const dispatch = useDispatch();
const handleGoogleSignIn = () => {
dispatch(googleSignInInitiate());
setLoading(true);
};
const handleFBSignIn = () => {
dispatch(fbSignInInitiate());
setLoading(true);
};
const handleSubmit = (e) => {
e.preventDefault();
if(!email || !password) {
return;
}
dispatch(loginInitiate(email, password));
setState({ email: "", password: "" });
setLoading(true);
};
const handleChange = (e) => {
let { name, value } = e.target;
setState({...state, [name]: value });
};
return (
<div>
<div id="logreg-form">
<form className='form-signin' onSubmit={handleSubmit}>
<h1>
Sign in
</h1>
<div className="social-login">
<button
className='btn google-btn social-btn'
type='button'
onClick={handleGoogleSignIn}>
<span>
<i className='fab fa-google-plus-g'>Sign in with Google</i>
</span>
</button>
<button
className='btn facebook-btn social-btn'
type='button'
onClick={handleFBSignIn}>
<span>
<i className='fab fa-facebook-f'>Sign in with Facebook</i>
</span>
</button>
</div>
<p>OR</p>
<input
type="email"
id="inputEmail"
className='form-control'
placeholder='Email Address'
name="email"
onChange={handleChange}
value={email}
required
/>
<input
type="password"
id="inputPassword"
className='form-control'
placeholder='Password'
name="password"
onChange={handleChange}
value={password}
required
/>
<button
className='btn btn-secondary btn-block'
type="submit">
<i className="fas fa-sign-in-alt"></i>Sign In
</button>
<hr />
<p>Don't have an account</p>
<Link to="/signup">
<button
className='btn btn-primary btn-block'
type="button" id="btn-signup">
<i className='fas fa-user-plus'></i>Sign up New Account
</button>
</Link>
</form>
</div>
</div>
)
}
export default Login;
Dashboard.js:
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { logoutInitiate } from '../../userauth/actions';
const Dashboard = () => {
const { currentUser } = useSelector((state) => state.user);
const dispatch = useDispatch();
const handleAuth = () => {
if(currentUser) {
dispatch(logoutInitiate());
}
}
return (
<div>
<h1>User Dashboard</h1>
<br />
<button className='btn btn-danger' onClick={handleAuth}>Logout</button>
</div>
)
}
export default Dashboard
protectedRoute.js:
import React from 'react';
import { useSelector } from 'react-redux';
import LoadingToRedirect from './LoadingToRedirect';
const ProtectedRoute = ({children}) => {
const { currentUser } = useSelector((state) => ({...state.user}));
return currentUser ? children : <LoadingToRedirect />;
}
export default ProtectedRoute;
App.js:
import React, { useEffect, useState } from 'react';
import { Landing, Login, Signup, Contact, Dashboard, Error } from './pages';
import ProtectedRoute from './components/ProtectedRoute';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { useDispatch } from "react-redux";
import { auth } from "./Firebase";
import { setUser } from './userauth/actions';
import './App.css';
const App = () => {
const dispatch = useDispatch();
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if(authUser) {
dispatch(setUser(authUser));
} else {
dispatch(setUser(null));
}
})
}, [dispatch]);
return (
<BrowserRouter>
<div className='App'>
<Routes>
<Route exact path="/" element={<Landing/>} />
<Route path="/login" element={<Login/>} />
<Route path="/signup" element={<Signup/>} />
<Route path="/contact" element={<Contact/>} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route path="*" element={<Error />} />
</Routes>
</div>
</BrowserRouter>
)
}
export default App;
If anyone comes across the same situation, I was able to solve this problem on my own and it's a simple fix. The problem was that the user authorization status wasn't updated whenever I called on Protected Routes but instead it just used the initial state when App.js was called. To get the user authorization status, I saved the user auth in my localStorage. Then, whenever I called user auth status in my redirects, I just fetched from my local storage.

How can I obtain an image link that is in the body of my GET request using React.js?

I am using a spring boot backend with a react.js frontend for a web application. Once a user is logged in, it directs the user to their Profile where they will have their username, profile picture, and a banner displayed on the screen. I have developed a backend service that returns the necessary information in the body of my GET request on Postman such as the link of the image (Profile or Banner). How can I use React to obtain the necessary link in profile_img_complete and insert it into my image that has a default image in it already if the value is null? My demo user has an image in the database ready to be used, so it should not be displaying the default image, but it is. Any help would be deeply appreciated, here is an image of the information on Postman.
Profile.jsx:
import React, {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
import UserProfileService from '../../services/user-profile.service';
import './styles/Profile.css';
const Profile = () => {
const {user: currentUser} = useSelector((state) => state.auth);
const {id: currentId} = useSelector((state) => state.auth);
const [content, setContent] = useState('');
const [photoURL, setPhotoURL] = useState('../../images/user-solid.svg');
//user-solid is the default image I want if the profile image link is null
useEffect(() => {
UserProfileService.getProfile().then(
(response) => {
setContent(response.data);
},
(error) => {
const _content =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
setContent(_content);
}
);
if (currentId && currentId.profile_img_complete) {
setPhotoURL(currentId.profile_img_complete);
}
}, [currentId]);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<div className='page'>
<div className='profile-container'>
<header className='jumbotron'>
<h3>
<strong id='profile-name'>{currentUser.username}</strong> Profile
</h3>
</header>
<p>
<img src={photoURL} alt='Avatar' className='avatar'></img>
<strong>Token:</strong> {currentUser.accessToken.substring(0, 20)} ...{' '}
{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}
</p>
<p>
<strong>Id:</strong> {currentUser.id}
</p>
<p>
<strong>Email:</strong> {currentUser.email}
</p>
<strong>Authorities:</strong>
<ul>
{currentUser.roles &&
currentUser.roles.map((role, index) => <li key={index}>{role}</li>)}
</ul>
<button>
<Link to={'/profile/edit'}>Edit Profile</Link>
</button>
</div>
</div>
);
};
export default Profile;
auth.js:
// We’re gonna import AuthService to make asynchronous HTTP requests with trigger one or more dispatch in the result.
// – register(): calls the AuthService.register(username, email, password) & dispatch setMessage if successful/failed
// – login(): calls the AuthService.login(username, password) & dispatch setMessage if successful/failed
// – logout(): calls the AuthService.logout().
// setMessage is imported from message slice that we’ve created above.
// We also need to use Redux Toolkit createAsyncThunk which provides a thunk that will take care of the action types and dispatching the right actions based on the returned promise.
//There are 3 async Thunks to be exported:
// register
// login
// logout
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
user-profile.service.js:
import axios from 'axios';
import authHeader from './auth-header';
const API_URL = 'http://localhost:8080/';
const getProfile = () => {
return axios.get(API_URL + 'profile', {headers: authHeader()});
};
const user_profile = {
getProfile,
};
export default user_profile;
I'm assuming that the data in the image is the value of response.data that is returned by UserProfileService.getProfile(). You need to update the photoURL when the UserProfileService.getProfile() request is fulfilled. Besides, currentId is a string. It does not contain profile_img_complete attribute.
import React, {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
import UserProfileService from '../../services/user-profile.service';
import './styles/Profile.css';
const Profile = () => {
const {user: currentUser} = useSelector((state) => state.auth);
const {id: currentId} = useSelector((state) => state.auth);
const [content, setContent] = useState('');
const [photoURL, setPhotoURL] = useState('../../images/user-solid.svg');
//user-solid is the default image I want if the profile image link is null
useEffect(() => {
UserProfileService.getProfile().then(
(response) => {
setContent(response.data);
if (response.data.profile_img_complete)
setPhotoURL(response.data.profile_img_complete);
},
(error) => {
const _content =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
setContent(_content);
}
);
}, [currentId]);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<div className='page'>
<div className='profile-container'>
<header className='jumbotron'>
<h3>
<strong id='profile-name'>{currentUser.username}</strong> Profile
</h3>
</header>
<p>
<img src={photoURL} alt='Avatar' className='avatar'></img>
<strong>Token:</strong> {currentUser.accessToken.substring(0, 20)} ...{' '}
{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}
</p>
<p>
<strong>Id:</strong> {currentUser.id}
</p>
<p>
<strong>Email:</strong> {currentUser.email}
</p>
<strong>Authorities:</strong>
<ul>
{currentUser.roles &&
currentUser.roles.map((role, index) => <li key={index}>{role}</li>)}
</ul>
<button>
<Link to={'/profile/edit'}>Edit Profile</Link>
</button>
</div>
</div>
);
};
export default Profile;
Alternate Solution
state.auth should already hold profile_img_complete. So, you can also do this
import React, {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
import UserProfileService from '../../services/user-profile.service';
import './styles/Profile.css';
const Profile = () => {
const {user: currentUser} = useSelector((state) => state.auth);
const auth = useSelector((state) => state.auth);
const {id: currentId} = useSelector((state) => state.auth);
const [content, setContent] = useState('');
const [photoURL, setPhotoURL] = useState(auth.profile_img_complete || '../../images/user-solid.svg');
//user-solid is the default image I want if the profile image link is null
useEffect(() => {
UserProfileService.getProfile().then(
(response) => {
setContent(response.data);
if (response.data.profile_img_complete)
setPhotoURL(response.data.profile_img_complete);
},
(error) => {
const _content =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
setContent(_content);
}
);
}, [currentId]);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<div className='page'>
<div className='profile-container'>
<header className='jumbotron'>
<h3>
<strong id='profile-name'>{currentUser.username}</strong> Profile
</h3>
</header>
<p>
<img src={photoURL} alt='Avatar' className='avatar'></img>
<strong>Token:</strong> {currentUser.accessToken.substring(0, 20)} ...{' '}
{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}
</p>
<p>
<strong>Id:</strong> {currentUser.id}
</p>
<p>
<strong>Email:</strong> {currentUser.email}
</p>
<strong>Authorities:</strong>
<ul>
{currentUser.roles &&
currentUser.roles.map((role, index) => <li key={index}>{role}</li>)}
</ul>
<button>
<Link to={'/profile/edit'}>Edit Profile</Link>
</button>
</div>
</div>
);
};
export default Profile;
In your .then() of UserServices.getProfile() use the following:
setPhotoURL(response.data.profile_img_complete);
Also remove the following code from your useEffect:
if (currentId && currentId.profile_img_complete) {
setPhotoURL(currentId.profile_img_complete);
}
const image = req.query.profile_img or const {profile_img} = req.query

REACT/REDUX Action not getting dispatched

What I am tying to do is when the user clicks on sign in button my action gets dispatch with email and password.
But, my action is not getting dispatched. Like when I checked my redux-dev-tools it is not showing anything:
There are no error message in console. I checked other answer's but nothing helped.
Here is the source code:
LoginScreen.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import ErrorMessage from "../../components/ErrorMessage/ErrorMessage";
import Loader from "../../components/Loader/Loader";
import { login } from "../../redux/actions/userActions";
import "./LoginScreen.scss";
const LoginScreen = ({ location, history }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const userLogin = useSelector((state) => state.userLogin);
const { loading, error, userInfo } = userLogin;
const redirect = location.search ? location.search.split("=")[1] : "/";
useEffect(() => {
if (userInfo) {
history.push(redirect);
}
}, [history, userInfo, redirect]);
const submitHandler = (e) => {
e.preventDefault();
dispatch(login(email, password));
};
return (
<>
<div className="login-container">
<div className="login-form">
<h1>Login</h1>
{loading ? (
<Loader />
) : error ? (
<ErrorMessage error={error} />
) : (
<form onSubmit={submitHandler}>
<div className="login-form-items">
<input
className="login-input"
type="email"
placeholder="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="login-input"
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit" value="submit">
Login
</button>
<h4>OR</h4>
<div className="login-form-social">
<button className="social">
<img
className="googleLogo"
src="/logo/google.svg"
alt="G"
/>{" "}
Login with Google
</button>
<button className="social social-github">
<img
className="githubLogo"
src="/logo/github.svg"
alt="GH"
/>{" "}
Login with GitHub
</button>
</div>
</div>
</form>
)}
</div>
</div>
</>
);
};
export default LoginScreen;
userAction.js
import axios from "axios";
import {
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
} from "../constants/userConstants";
export const login = () => (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-Type": "appllication/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,
});
}
};
userReducer.js
import {
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGOUT,
} from "../constants/userConstants";
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true };
case USER_LOGIN_SUCCESS:
return { loading: false, userInfo: action.payload };
case USER_LOGIN_FAIL:
return { loading: false, error: action.payload };
case USER_LOGOUT:
return {};
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 } from "./reducers/userReducers";
const reducer = combineReducers({
userLogin: userLoginReducer,
});
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;
You've defined your action wrong. With redux-thunk you define your actions like this:
export const login = (email, password) => async (dispatch) => {
// your action code
};
// The above code is equivalent to
export const login = (email, password) => {
return async (dispatch) => {
// your action code
}
}
Not like this:
export const login = () => (email, password) => async (dispatch) => {
// your action code
};
// The above code is equivalent to
export const login = () => {
return (email, password) => {
return async (dispatch) => { // this is wrong
}
}
}
So your action is returning a function which then returns another function.
The way you use it caught my attention. Out of general use. Generally, api operations are done with packages such as saga or thunk. Action is only used as a hyperlink. I suggest you review the article below. I think this build will solve your problem.
https://blog.devgenius.io/reactjs-simple-understanding-redux-with-redux-saga-f635e273e24a

Categories

Resources