it is my first time using redux, I have been trying to activate an account through mail with redux, but I didn't know how to consume the method
here is my redux activate method
export const activateAccount= createAsyncThunk('user/activateUser', async (data, { rejectWithValue }) => {
try
{
const token = window.location.href.slice(window.location.href.indexOf('?') + 1);
const config = {
method: 'put',
url: `${API_ENDPOINT}/v1/api/account/${token}/enable`,
data,
};
const payload = await axios(config);
return payload.data;
} catch (err) {
return rejectWithValue(err.response.data);
}
});
here how I try to use it
function EnableAccount()
{
const dispatch = useDispatch();
useEffect(() => {
const Activate = (entry) => {
dispatch(
activateAccount({
...entry,
}),
).then(unwrapResult);
};
Activate();
}, []);
return <Result status="success" title="Successfully Activated Account " />;}
and here im calling it in the route
<Route exact path="/ActivateAccount/:token">
<EnableAccount />
</Route>
and this one is from my html template
<a href="${API_ENDPOINT}/ActivateAccount/token=?${confirmationCode}/enable"
>I want to activate my account
</a>
and thank you.
Related
I am trying to learn how to effectively use React Hooks but am having an issue. I would like to reflect whether or not a user is "logged in" to the site using a JWT in local storage. When I first visit the page, the hook works as I intend, retrieving the user data. But if I click the "Log Out" button in the example below, the component does not update to reflect this, although it will if I refresh the page. How might I properly implement this hook to get it to update when logging in/out?
Custom hooks:
export const useUser = () => {
const [token] = useToken();
const getPayloadFromToken = token => {
const encodedPayload = token.split('.')[1];
return JSON.parse(atob(encodedPayload))
}
const [user,setUser] = useState(() => {
if(!token) return null;
return getPayloadFromToken(token);
})
useEffect(() => {
if(!token) {
setUser(null);
} else {
setUser(getPayloadFromToken(token));
}
}, [token]);
return user;
}
export const useToken = () => {
const [token, setTokenInternal] = useState(() => {
return localStorage.getItem('token');
});
const setToken = newToken => {
localStorage.setItem('token',newToken);
setTokenInternal(newToken);
}
return [token, setToken];
}
Navigation Bar Component:
const NavigationBar = () => {
const user = useUser();
const logOut = () => {
localStorage.removeItem('token');
};
return(
<>
<div>{user ? 'logged in' : 'logged out'}</div>
<button onClick={logout}>Log Out</button>
</>
);
}
I think at a minimum you could expose out the setToken function directly via the useUser hook and when you call logout call setToken(null) (or similar) and this would be sufficient enough to trigger a render. Ideally though you'd have all this authentication "state" centrally located in a React context so the hooks all reference the same single state.
I suggest actually encapsulating the logout function within the useUser hook and exposing that out instead of directly exposing the setToken function. You want the hooks to maintain control over the state invariant and not rely on consumers to pass/set the correct state values.
Example:
export const useUser = () => {
const [token, setToken] = useToken();
const getPayloadFromToken = token => {
const encodedPayload = token.split('.')[1];
return JSON.parse(atob(encodedPayload));
}
const [user, setUser] = useState(() => {
if (!token) return null;
return getPayloadFromToken(token);
});
useEffect(() => {
if (!token) {
setUser(null);
} else {
setUser(getPayloadFromToken(token));
}
}, [token]);
const logout = () => {
setUser(null);
setToken(null);
};
return { user, logout };
}
...
export const useToken = () => {
const [token, setTokenInternal] = useState(() => {
// Initialize from localStorage
return JSON.parse(localStorage.getItem('token'));
});
useEffect(() => {
// Persist updated state to localStorage
localStorage.setItem('token', JSON.stringify(newToken));
}, [token]);
const setToken = newToken => {
setTokenInternal(newToken);
}
return [token, setToken];
};
...
const NavigationBar = () => {
const { logout, user } = useUser();
return(
<>
<div>{user ? 'logged in' : 'logged out'}</div>
<button onClick={logout}>Log Out</button>
</>
);
}
I encountered something similar. A solution I found that worked for me was to save whether or not the user is logged in using Context. Effectively this would involve creating a wrapper around components in your app which need access to whether or not a user is logged in as an alternative to using local storage to save this sort of stuff.
https://reactjs.org/docs/context.html
Context
I'm building a simple web application using the MERN stack for practice. In the app, logged-in users should be able to add a new blog to the site. However, for some reason my axios post request to the backend is failing and I'm receiving a 401 error. I'm using jsonwebtoken to handle the authentication. Submitting a POST request via Insomnia works fine so I don't believe it's an issue with my endpoint. I'm running backend server locally on my machine on port 3003 and have set up a proxy so there's no issues with cors. This works fine as the blogs from the backend are displays on the frontend once a user has logged in.
I've also checked the headers and can confirm that logged-in users have a valid bearer token.
What could be causing the issue?
Frontend
I can't post any images but here's a link to the frontend view:
https://imgur.com/a/DdUlfg9
App.js
import React, { useState, useEffect } from 'react'
import Blog from './components/Blog'
import blogService from './services/blogs'
import loginService from './services/login'
import LoginForm from './components/loginForm'
import BlogForm from './components/blogForm'
const App = () => {
const [blogs, setBlogs] = useState([])
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [user, setUser] = useState(null)
const [errorMessage, setErrorMessage] = useState(null)
const [newBlog, setNewBlog] = useState({
title: '',
author: '',
url: ''
})
useEffect(() => {
blogService.getAll().then(blogs =>
setBlogs( blogs )
)
}, [])
useEffect(() => {
const loggedInUser = window.localStorage.getItem("loggedBlogUser")
if(loggedInUser){
const user = JSON.parse(loggedInUser)
setUser(user)
}
},[])
const handleLogin = async (event) => {
event.preventDefault()
try {
const user = await loginService.login({
username, password
})
window.localStorage.setItem(
'loggedBlogUser', JSON.stringify(user)
)
blogService.setToken(user.token)
setUser(user)
setUsername('')
setPassword('')
} catch (exception){
setErrorMessage('Wrong credentials')
setTimeout(() => {
setErrorMessage(null)
}, 5000)
}
}
const handleLogout = async (event) => {
event.preventDefault()
if(user){
window.localStorage.removeItem("loggedBlogUser")
setUser(null)
}
}
const handleBlogField = (event) => {
event.preventDefault()
const {name, value} = event.target
console.log(newBlog.title)
setNewBlog(prevBlog => ({
...prevBlog,
[name] : value
}))
}
const addBlog = async (event) => {
event.preventDefault()
try {
const blog = await blogService.create(newBlog)
console.log("POST REQUEST: ",newBlog)
console.log('lets geddit')
setBlogs(blogs.concat(blog))
} catch (exception){
setErrorMessage('Uh oh, try again :[')
setTimeout(() => {
setErrorMessage(null)
}, 5000)
}
}
if(user === null){
return(
<>
{errorMessage}
<h2>Log into application</h2>
<LoginForm handleLogin={handleLogin} setUsername={setUsername} setPassword={setPassword} username={username} password={password}/>
</>
)
}
return (
<div>
<h2>blogs</h2>
{user &&
<div>
<h3>{user.username} logged in</h3>
<button onClick={handleLogout}>Logout</button>
</div>
}
<BlogForm handleSubmission={addBlog} newBlog={newBlog} handleBlogField={setNewBlog}/>
{/* <BlogForm addBlog={addBlog} title={newBlog.title} setTitle={setTitle} setAuthor={setAuthor} author={newBlog.author} url={newBlog.url} setUrl={setUrl}/> */}
{blogs.map(blog =>
<Blog key={blog.id} blog={blog} />
)}
</div>
)
}
export default App
Blogs.js
import axios from 'axios'
const baseUrl = '/api/blogs'
let token = null
//let config
const setToken = (newToken) => {
token = `bearer ${newToken}`
}
const getAll = async () => {
const response = await axios.get(baseUrl)
return response.data
}
const create = async (newObject) => {
const config = {
headers: {
Authorization: token
}
}
const response = await axios.post(baseUrl, newObject, config)
console.log(`RESPONSE: ${newObject}`)
return response.data
}
const blogService = {
getAll, setToken, create
}
export default blogService
Have you configured CORS?, in order to accept your localhost requests?
I'm trying to send a delete request to API using fetch and useEffect hook in react. I want to execute the delete request with the click of a button and pass the id of the specific JSON data that has to be deleted. I'm unable to figure out how how to pass this id to fetch function under the useEffect hook.
const Display = () => {
const [state, setState] = useState([])
let deleteJSON;
const handleDelete = (_id) => {
deleteJSON = async (id) => {
const res = await fetch(`http://localhost:8080/users/${id}`, {method: 'DELETE', mode: 'cors'})
console.log(res.status)
}
}
useEffect(() => {
(async () => {
const res = await fetch('http://localhost:8080/users', {method: 'GET', mode: 'cors'})
const jsonArr = await res.json()
setState((prevState) => {
return [...prevState, ...jsonArr]
})
})();
}, [])
return (
<div className='display'>
{
state.map((json) => {
const {name, email, phone, _id} = json
return (
<div key = {_id} >
<div>
<button onClick={(event) => { handleDelete(_id)} } className="box" >Delete</button>
<button className="box">Update</button>
</div>
<h2>{_id}</h2>
<h2>{name}</h2>
<h2>{email}</h2>
<h2>{phone}</h2>
</div>
)
})
}
</div>
)
}
As you can see I've passed the id to the handleDelete function but I can only call the deleteJSON function inside the useEffect hook. But inside the useEffect hook, I can't get access to this function.
I am trying to create upload profile image method that help user upload their profile picture on website but I am having trouble with I dont know how to send the image from client to server and make those image store on cloudinary or firebase.
My routes look like this:
ProfileAPI.js
const express = require("express");
const router = express.Router();
const { body, param } = require("express-validator");
const { catchErrors } = require("../errors/errorHandlers");
const multer = require('multer');
const uuidv4 = require('uuid/v4');
const upload_dir = './images';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, upload_dir);
},
filename: (req, file, cb) => {
cb(null, `${uuidv4()}-${file.filename.toLowerCase}`);
}
});
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (
file.mimetype == 'image/png' ||
file.mimetype == 'image/jpg' ||
file.mimetype == 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
}
});
const {
getUserProfile,
getUsersPublicProfile,
lookUpId,
updateUserProfile,
updateUserEmail,
deleteUserProfile,
// deleteUserSkill,
addPlayersProfile,
getCreatedPlayers,
updatePlayersProfile,
deleteUserCreatedPlayer,
} = require("./profilesController");
router.post(
"/upload",
upload.single('profileImg'),
updateUserProfile
);
So key points are the setup of storage which tells where to upload + the file filter in upload, right?
And the route.post which will `upload.single('profileImg'), right? the route will include my controller for updateUserProfile which can be found here:
profilesController.js
exports.updateUserProfile = async (req, res) => {
const userId = req.session.passport.user.id;
// This array will contain all the update functions to run.
const updates = [];
// If a gravatar url has not been generated, do it now.
const pictureValue = gravatar.url(
req.body.email,
{ s: "100", r: "pg", d: "retro" },
true
);
const payload = {
fullname: req.body.fullname,
location: req.body.location,
webpage: req.body.webpage,
linkedin: req.body.linkedin,
institution: req.body.institution,
bio: req.body.bio,
major: req.body.major,
mergedTo: userId,
picture: pictureValue,
skillone: req.body.skillone,
skilltwo: req.body.skilltwo,
skillthree: req.body.skillthree
};
}
So now to the frontend code (react.js):
This is the form I am loading in my react app:
UserProfile.js
const UserProfile = (serverUserData) => {
const appState = useContext(GlobalContext);
const { currentUser } = appState;
const { email, picture, name } = currentUser;
const [isVerified, setIsVerified] = useState(false);
const checkVerificationData = () => {
axios.get("/api/v1/profiles/profile").then((res) => {
const { data } = res;
if (data.verifiedDT) {
setIsVerified(data.verifiedDT);
}
});
};
useEffect(() => {
checkVerificationData();
}, [isVerified]);
// Upload user avatar function
const [imageSelected, setImageSelected] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('email', email);
formData.append('name', name);
formData.append('profileImg', imageSelected);
axios
.post(`/upload`, formData)
.then(() => console.log("success"))
.catch(err => console.log(err));
};
const onFileChange = (e) => {
setImageSelected({ profileImg: e.target.files[0] });
};
};
const classes = useStyles();
return (
<div className={classes.root}>
<Grid item xs={12}
container
direction="row"
justify="center"
alignItems="center"
spacing={4}>
<Grid item>
<Grid item>
<UserCard
picture={currentUser.picture}
userEmail={email}
name={name}
isVerified={isVerified}
handleSubmit={handleSubmit}
onFileChange={onFileChange}
/>
<br />
</Grid>
and here is where user can upload their profile photo:
UserCard.js
{picture ? (
<div>
<Avatar
src={picture}
alt="Avatar"
className="avatar--profile_image"
/>
<input
type="file"
onChange={onFileChange}
/>
<button onClick={handleSubmit}>Submit</button>
</div>
) : (
<AccountCircleIcon className="avatar--profile_image" />
)}
So when entering things and hitting the Add Button my api states that req.file is undefined and I cannot find out why.
Can anyone help me drilling down the error?
To upload files you need to use contentType: "multipart/form-data".
Use the following as a reference to achieve the file upload.
helper function to create a instance with requied header. You may add any others to here.
const getInstance = () => {
return axios.create({
headers: {
"Content-Type": "multipart/form-data",
},
});
}
call this method with the file to be uploaded
const fileUplaod = (file) => {
let formData = new FormData();
formData.append("images", file, file.name);
getInstance()
.post(endpoint_post_url, formData)
.then((response) => {
console.log("IMAGES_SUBMIT_SUCCESS");
})
.catch((err) => {
console.error("image submit error", err);
});
}
check the request body in your backend code. You can upload multiple images as well to the same property. It will be an array in the request object.
Edit the handleSubmit function to add a config to the axios call.
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('email', email);
formData.append('name', name);
formData.append('profileImg', imageSelected);
axios
.post(`/upload`, formData, {
headers: {
'Content-Type': "multipart/form-data"
}
})
.then(() => console.log("success"))
.catch(err => console.log(err));
};
i'm using Passport-local for authentication and Gatsby on front end
Generally, the code works fine. When I click on signout, the server returns a 200 call and I get a response "User sign out successfully". I'm then navigated to the signin page. From there, I am unable to access my Post page which is private route. My signin and post page are client side routes
The issue comes when I click on the home page (which is a static page). From there, when I click on the post link, I'm navigated to the post page which supposedly is inaccessible now that I have signed out. My fetchuser action creator runs and is able to fetch the user detail even though I have already signed out from my app
Anyone knows how to resolve this issue? Thanks in advance
SERVER
signout api
router.get("/signout", (req, res) => {
req.logout();
res.send("Sign Out Successfully");
});
me api
router.get("/me", (req, res) => {
res.send(req.user);
});
CLIENT
app
const App = () => {
useEffect(() => {
store.dispatch(fetchUser())
}, [])
return (
<Layout>
<Alert />
<Router basepath="/app">
<Signin path="/signin" />
<Signup path="/signup" />
<PrivateRoute path="/post" component={Post} />
{/* <Default path="/" /> */}
</Router>
</Layout>
)
}
export default App
fetchUser action creator
export const fetchUser = () => async dispatch => {
try {
const res = await axios.get("http://localhost:5000/api/users/me", {
withCredentials: true,
})
dispatch({
type: FETCH_USER,
payload: res.data,
})
} catch (err) {
console.log(err)
dispatch({
type: AUTH_ERROR,
})
}
}
signout action creator
export const signOut = () => async dispatch => {
const res = await axios.get("http://localhost:5000/api/users/signout")
console.log(res)
dispatch({
type: SIGNOUT,
})
navigate("/app/signin")
}
I think your approach is correct and valid, despite personally thinking that handling it with cookies or localStorage could be easily maintained.
Your <PrivateRoute> component should handle your logic and perform some actions depending on the user state (logged or not), something like:
import React from "react"
import { navigate } from "gatsby"
import { isLoggedIn } from "../services/auth"
const PrivateRoute = ({ component: Component, location, ...rest }) => {
if (!isLoggedIn() && location.pathname !== `/app/login`) {
navigate("/app/login") // or your desireed page
return null
}
return <Component {...rest} />
}
export default PrivateRoute
Your auth service, should handle your requests, in this case using localStorage but it can be replaced for your API requests:
export const isBrowser = () => typeof window !== "undefined"
export const getUser = () =>
isBrowser() && window.localStorage.getItem("gatsbyUser")
? JSON.parse(window.localStorage.getItem("gatsbyUser"))
: {}
const setUser = user =>
window.localStorage.setItem("gatsbyUser", JSON.stringify(user))
export const handleLogin = ({ username, password }) => {
if (username === `john` && password === `pass`) {
return setUser({
username: `john`,
name: `Johnny`,
email: `johnny#example.org`,
})
}
return false
}
export const isLoggedIn = () => {
const user = getUser()
return !!user.username
}
export const logout = callback => {
setUser({})
callback()
}