My friends and I are trying to create a React App. In this scenario, we want to figure out what user is currently logged in and then send a post request to determine everyone that is in the same "room" as them and display it on the app when the page loads. We send our data back as an array. Though when we console.log our "response" we get multiple outputs. Furthermore, when we try doing setRoomies we get non-stop requests to our server.
Below I have provided our code for React, server, and as well as the console.
function Dashboard() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [user, setUser] = useState('');
const [roomKey, setRoomKey] = useState('')
const [roomies, setRoomies] = useState('')
setTimeout(function () {
const currUser = JSON.parse(localStorage.getItem('user'));
if (currUser) {
setEmail(currUser.email);
setUser(currUser.name);
setRoomKey(currUser.roomKey)
} else {
setUser(null)
}
}, 10);
const payload = {
roomKey: roomKey
}
setTimeout(async function () {
const response = await axios({
url: 'http://localhost:4000/api/dashboard',
method: 'post',
data: payload
})
// setRoomies(response.data.roommates)
console.log(response.data.roommates)
}, 10)
userRouter.post('/dashboard', async function (req, res) {
console.log(req.body)
const sendKey = req.body.roomKey
user.find({ roomKey: sendKey }, await function (err, foundMates) {
console.log(foundMates)
if (err) {
res.send({
token: USER_LOGIN_FAIL
})
} else {
console.log(foundMates);
res.send({
token: USER_LOGIN_SUCCESS,
roommates: foundMates,
})
}
})
})
console
You should not use setTimeout like that. Instead you should use useEffect to achieve what you want. Might require some changes, but the idea is to do something like this
useEffect(() => {
const currUser = JSON.parse(localStorage.getItem('user'));
if (currUser) {
setEmail(currUser.email);
setUser(currUser.name);
setRoomKey(currUser.roomKey)
} else {
setUser(null)
}
}, []);
useEffect(() => {
if(!roomKey) {
return;
}
const payload = {
roomKey: roomKey
}
async function getInfo() {
const response = await axios({
url: 'http://localhost:4000/api/dashboard',
method: 'post',
data: payload
})
// setRoomies(response.data.roommates)
console.log(response.data.roommates)
}
getInfo();
}, [roomKey]);
Related
I am building a website using nextjs and axios. Users can apply to become a member and then be approved by admins. In the admin dashboard I initially load the users and the unapproved users and display them in a list.
When an admin clicks on a button the unapproved user should be approved. The functionality works. The only aspect I can't figure out is how to update the state.
Here is my code:
const AdminIndex = () => {
const [users, setUsers] = useState([])
const [unapprovedUsers, setUnapprovedUsers] = useState([])
useEffect(() => {
loadUnapprovedUsers()
loadUsers()
}, [])
const loadUnapprovedUsers = async () => {
const { data } = await axios.get('/api/admin/unapprovedUsers')
setUnapprovedUsers(data)
}
const loadUsers = async () => {
const { data } = await axios.get('/api/admin/users')
setUsers(data)
}
const approveUnapprovedUser = async (email) => {
try {
const { data } = await axios.put(
`/api/admin/approveUnapprovedUser/${email}`
)
setUnapprovedUsers([]) // only remove the approved user
setUsers(...data) // include the approved user into the array
} catch (err) {
console.log(err)
}
}
}
I am trying to remove the approved user from the unapprovedUsers array and try to add the user to the users array, hence updating the UI. The response returned by axios is an object, which doesn't make things easier.
I would be very thankful for any kind of help!
Just try to filter the unapprovedUsers with the users that don't have that email, also add the approved user to users state
const AdminIndex = () => {
const [users, setUsers] = useState([])
const [unapprovedUsers, setUnapprovedUsers] = useState([])
useEffect(() => {
loadUnapprovedUsers()
loadUsers()
}, [])
const loadUnapprovedUsers = async () => {
const { data } = await axios.get('/api/admin/unapprovedUsers')
setUnapprovedUsers(data)
}
const loadUsers = async () => {
const { data } = await axios.get('/api/admin/users')
setUsers(data)
}
const approveUnapprovedUser = async (email) => {
try {
const { data } = await axios.put(
`/api/admin/approveUnapprovedUser/${email}`
)
setUnapprovedUsers(prev => prev.filter(user => user.email !== email)) // only remove the approved user
setUsers(prev => [...prev, data]) // include the approved user into the array
} catch (err) {
console.log(err)
}
}
}
I've been trying to deploy my first React App and i cannot seem to be able to get rid of the following warning which on production stops me from doing a deployment:
Line 60:11: The 'refreshToken' function makes the dependencies of useEffect Hook (at line 142) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'refreshToken' in its own useCallback() Hook react-hooks/exhaustive-deps
Is there an easy way to fix this without breaking the JWT token authentification?
AuthContext.js
import React, { useEffect, useState, useCallback } from 'react'
import { API } from "../api"
import axios from "axios"
import { isAfter, isEqual, parseISO, sub } from 'date-fns'
export const AuthContext = React.createContext(null)
export function AuthContextProvider({ children }) {
const [accessTokenExpiration, setAccessTokenExpiraton] = useState(undefined);
const getUser = () => {
return JSON.parse(localStorage.getItem('user'))
}
const isLoggedIn = () => {
return localStorage.getItem('user') !== null
}
const [user, setUser] = useState(() => {
return isLoggedIn() ? getUser() : null;
})
const [shouldGoToLogin, setShouldGoToLogin] = useState(() => {
if (!user || !user.access_token || !user.refresh_token) {
return true;
}
return false;
})
const logout = async () => {
if (!user) {
return;
}
const { access_token } = user;
localStorage.removeItem('user')
setUser(null);
return axios.post(API.auth.logout, {
headers: {
"Authorization": `Bearer ${access_token}`,
"Content-Type": "application/json"
},
withCredentials: true
});
}
const login = async (values) => {
console.log(values);
const correctedValues = { ...values, username: values.email };
return axios.post(API.auth.login, correctedValues)
.then(res => {
const data = res.data;
processApiData(data);
})
}
const processApiData = useCallback((resp) => {
let newUser = { ...user, ...resp };
delete(newUser.user); // Delete the user sub-object since we merged that directly into the top-level object
saveUser(newUser); // Save the user
const { access_token_expiration } = newUser;
if (access_token_expiration) {
console.log("have expiration", access_token_expiration);
const nextExpiration = parseISO(access_token_expiration); // Convert from ISO 8601 to a Date Object
const earlyRefreshTime = sub(nextExpiration, { minutes: 55 }); // Do an hourish early
setAccessTokenExpiraton(earlyRefreshTime); // Set the upcoming expiraton
}
}, [user])
const refreshToken = useCallback(async () => {
const user = getUser();
const redirectToLogout = () => {
localStorage.clear(); // Clear our localStorage
setShouldGoToLogin(true);
};
if (!user) { // No user
redirectToLogout();
}
console.log(API.auth.refreshToken);
const resp = await fetch(API.auth.refreshToken, {
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({'refresh': user?.refresh_token}),
method: "POST",
withCredentials: true
})
console.log("status", resp.status);
if (resp.status === 200) {
const data = await resp.json(); // Convert to JSON
console.log("refresh token data", data);
processApiData(data);
} else {
redirectToLogout();
}
}, [processApiData])
const resetPassword = async (values) => {
return axios.post(API.auth.passwordReset, values);
}
const saveUser = async (newUser) => {
localStorage.setItem('user', JSON.stringify(newUser))
setUser(newUser)
}
const signup = async (values) => {
return axios.post(API.auth.signup, values);
}
useEffect(() => {
if (!user) {
return;
}
const interval = setInterval(()=> {
if(!user){
return false;
}
if (accessTokenExpiration) {
const now = new Date(); // Get the current time
console.log(now);
console.log(accessTokenExpiration);
if (isAfter(now, accessTokenExpiration) || isEqual(now, accessTokenExpiration)) { // If we are late to the party or the stars have aligned
refreshToken(); // Refresh the token
}
} else { // We do not have an access token expiration yet
refreshToken(); // Refresh the token immediately so we get a time
}
}, 1000 * 15)
return ()=> clearInterval(interval)
}, [accessTokenExpiration, refreshToken, user])
return (
<AuthContext.Provider value={{
getUser,
isLoggedIn,
logout,
login,
resetPassword,
signup,
user,
shouldGoToLogin
}}>
{children}
</AuthContext.Provider>
)
}
Put refreshToken function directly in your useEffect hook or wrap in useCallback.
I am currently making a Spotify clone which gives user a preview of the song. The problem occurs when I am making many different api requests. When there are more than one requests on the page, it throws a 429 error(making too many requests at once).
Please read through the whole question as I have mentioned the steps I have taken to fix this below.
Profile.js
const { api, refreshableCall } = useSpotify()
const [error, setError] = useState(null)
const [userName, setUserName] = useState("")
const [userFollowers, setUserFollowers] = useState("")
const [userImage, setUserImage] = useState([])
const [userLink, setUserLink] = useState("")
const [userId, setUserId] = useState("")
const [userFollowing, setUserFollowing] = useState("")
const [userTopArtists, setUserTopArtists] = useState([])
const [userTopSongs, setUserTopSongs] = useState([])
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMyTopTracks({
limit: 10,
time_range: "long_term"
}))
.then((res) => {
if (disposed) return
setUserTopSongs(res.body.items)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserTopSongs([])
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMe())
.then((res) => {
if (disposed) return
var data = res.body
setUserName(data.display_name)
setUserImage(data.images)
setUserFollowers(data.followers["total"])
setUserLink(data.external_urls.spotify)
setUserId(data.id)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserName("")
setUserImage([])
setUserFollowers("")
setUserLink("")
setUserId("")
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getFollowedArtists())
.then((res) => {
if (disposed) return
var data = res.body
var artists = data.artists
setUserFollowing(artists.total)
})
.catch((err) => {
if (disposed) return
setUserFollowing([])
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMyTopArtists({
limit: 10,
time_range: "long_term"
}))
.then((res) => {
if (disposed) return
var data = res.body
var artists = data.items
setUserTopArtists(artists)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserTopArtists([])
setError(err)
});
return () => disposed = true
})
SpotifyContext.js
import React, { useState, useEffect, useContext } from "react"
import axios from "axios"
import SpotifyWebApi from 'spotify-web-api-node';
const spotifyApi = new SpotifyWebApi({
clientId: 1234567890,
});
export const SpotifyAuthContext = React.createContext({
exchangeCode: () => { throw new Error("context not loaded") },
refreshAccessToken: () => { throw new Error("context not loaded") },
hasToken: spotifyApi.getAccessToken() !== undefined,
api: spotifyApi
});
export const useSpotify = () => useContext(SpotifyAuthContext);
function setStoredJSON(id, obj) {
localStorage.setItem(id, JSON.stringify(obj));
}
function getStoredJSON(id, fallbackValue = null) {
const storedValue = localStorage.getItem(id);
return storedValue === null
? fallbackValue
: JSON.parse(storedValue);
}
export function SpotifyAuthContextProvider({ children }) {
const [tokenInfo, setTokenInfo] = useState(() => getStoredJSON('myApp:spotify', null))
const hasToken = tokenInfo !== null
useEffect(() => {
if (tokenInfo === null) return;
// attach tokens to `SpotifyWebApi` instance
spotifyApi.setCredentials({
accessToken: tokenInfo.accessToken,
refreshToken: tokenInfo.refreshToken,
})
// persist tokens
setStoredJSON('myApp:spotify', tokenInfo)
}, [tokenInfo])
function exchangeCode(code) {
return axios
.post("http://localhost:3001/login", {
code
})
.then(res => {
// TODO: Confirm whether response contains `accessToken` or `access_token`
const { accessToken, refreshToken, expiresIn } = res.data;
// store expiry time instead of expires in
setTokenInfo({
accessToken,
refreshToken,
expiresAt: Date.now() + (expiresIn * 1000)
});
})
}
function refreshAccessToken() {
const refreshToken = tokenInfo.refreshToken;
return axios
.post("http://localhost:3001/refresh", {
refreshToken
})
.then(res => {
const refreshedTokenInfo = {
accessToken: res.data.accessToken,
// some refreshes may include a new refresh token!
refreshToken: res.data.refreshToken || tokenInfo.refreshToken,
// store expiry time instead of expires in
expiresAt: Date.now() + (res.data.expiresIn * 1000)
}
setTokenInfo(refreshedTokenInfo)
// attach tokens to `SpotifyWebApi` instance
spotifyApi.setCredentials({
accessToken: refreshedTokenInfo.accessToken,
refreshToken: refreshedTokenInfo.refreshToken,
})
return refreshedTokenInfo
})
}
async function refreshableCall(callApiFunc) {
if (Date.now() > tokenInfo.expiresAt)
await refreshAccessToken();
try {
return await callApiFunc()
} catch (err) {
if (err.name !== "WebapiAuthenticationError")
throw err; // rethrow irrelevant errors
}
// if here, has an authentication error, try refreshing now
return refreshAccessToken()
.then(callApiFunc)
}
return (
<SpotifyAuthContext.Provider value={{
api: spotifyApi,
exchangeCode,
hasToken,
refreshableCall,
refreshAccessToken
}}>
{children}
</SpotifyAuthContext.Provider>
)
}
Errors
Without the dependency, it keeps cycling and firing off requests, likely hundreds per second. (Error 429)
With the dependency, it seems the Access Token is being ignored or sidestepped. (Error: WebApiAuthentication - No token provided)
What I have tried to do ?
I tried to implement all the requests in a single useEffect, still getting the errors.
Calling useEffect with dependency array and without.
Link to the Github Repo
https://github.com/amoghkapoor/spotify-clone
status 429 means you have made too many calls in a specific time window.
you are therefore banned for this specific time window.
try waiting a bit before retrying.
did you try :
useEffect(..., [])
this guaranties it will be run only once.
None of your useEffect calls are using a dependency array, remember if useEffect is called without any dependencies it goes into an infinite loop. Either find what dependency or state change should re-run the useEffect hook and include it in the dependency array:
useEffect(() => { /* your logic */ }, [dependencies])
or if there are no dependencies simply fire it once the component mounts:
useEffect(() => { /* your logic */ }, [])
when i act onPress onPress={() => Login()} function
first of all, i want to get token by using signInWithKakao function,
second of all, right after i got token, if i have token, i want to dispatch profile by using
kakaoprofile function
if i use my code when i act onPress, this error occure
token is not defined
I think it is because signInWithKakao is not recognized because it is in the Login function.
how can i fix my code?
this is my code
import {
getProfile as getKakaoProfile,
login,
} from '#react-native-seoul/kakao-login';
const Kakao = () => {
const [result, setResult] = useState('');
const Login = () => {
const signInWithKakao = async() => {
const token = await login();
setResult(JSON.stringify(token));
};
if (token) {
const kakaoprofile = async() => {
const profile = await getKakaoProfile();
// console.log("profile:",profile);
dispatch({
type: KAKAOLOG_IN_REQUEST,
data: profile
})
};
}
}
return (
<Container>
<LoginButton onPress = {() => Login()}>
<Label>카카오 로그인</Label>
</LoginButton>
</Container>
);
};
export default Kakao;
You can try to create another function. let's say handleOrder and make it async and then put all your awaits there.
const Login = () => {
handleInOrder()
}
async function handleInOrder() {
try{
const token = await getKakaoProfile()
if(token){
let data = await nextInOrder(token)
//do a dispatch
}
} catch(err){
// handle the error
}
}
Let me know,if you need any help
I am working on my React application and noticed an error. When I console log the code below, the GET request from my Express back end, the console log runs without stopping.
api-helper.js (REACT)
export const getAllPages = async () => {
getToken();
const resp = await api.get(`pages/`);
console.log('><><><><><><><>< return getAllPages response ><><><><><><><><',resp) //
const parsedResponse = resp.data.map((page) => ({
...page,
siteName: page.siteUniqueName?.split("-")[1],
}));
return parsedResponse;
};
pageRouter.js (express)
pageRouter.get("/", restrict, async (req, res, next) => {
try {
const { user } = req;
const pages = await Page.findAll({
where: {
userId: user.id
},
include: [
{ model: Site },
{ model: VisitRecord },
{ model: Lead }
]
});
res.json(pages);
} catch (e) {
next(e);
}
});
This would obviously be bad in production if it were making calls on a live database. How do I get this to stop this get running constantly in the background?
Update:
This is the code on the dashboard that calls the API where all pages are rendered:
Dashboard.jsx
useEffect(() => {
props.getAllPages();
}, []);
App.js
getAllUserPages = async () => {
// console.log('called here1')
const pages = await getAllPages();
if (pages) {
this.setState({
sites: pages,
});
}
};
async componentDidMount() {
// console.log('called________')
const currentUser = await getCurrentUser();
if (currentUser) {
const preservedSites = await getAllPages();
this.setState({
currentUser,
preservedSites: preservedSites.map((site) => site.siteName),
});
} else {
if (
!(
this.props.history.location.pathname.startsWith("/reset/") ||
this.props.history.location.pathname === "/" ||
matchPath(this.props.history.location.pathname, {
path: "/:userId/:siteName",
exact: false,
strict: false,
})
)
) {
this.props.history.push("/auth");
}
}
const pages = await getAllPages();
if (pages) {
this.setState({
sites: pages,
});
}
const resp = await api.get("/users");
const users = resp.data;
this.setState({
users: users,
});
}