`useEffect` not being able to fetch data on component mount - node.js

I am trying to set an array of objects into dineIns after fetching them inside useEffect. What I understood is that there is some kind of delay in receiving the data because the state variable returns an empty array when I log it after fetching the data.
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import jwtDecode from 'jwt-decode';
function CheckIns() {
const [dineIns, setDineIns] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
const user_data = await axios
.get(`/api/users/${user_email}`)
.then((res) => {
const info = res.data.reservations;
setDineIns(info);
console.log(dineIns);
});
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);
}
What needs to be corrected here to set the state in time?

set state is an async operation, which log the data after set it, will log the old value.
To ensure that the data set correctly, you can use setState again
const info = res.data.reservations
setDineIns(info)
setDineIns(prev => {
console.log(prev)
return prev;
})
Or you can use effect with dineIns dependence.
I think your code works fine.

You are expecting a Promise from the axios call but you are awaiting it.
Try to change your code like this:
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
try {
const { data } = await axios.get(`/api/users/${user_email}`);
setDineIns(data.reservations);
console.log(dineIns);
} catch (err) {
console.log(err);
}
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);

Related

why i cant get an axios response in react?

so I'm having a problem getting data from my server to my front-end using axios.
as you can see in this picture I'm getting a response for the GET method for users/users.
this is my showUsers function
const showUsers = async (req, res) => {
await User.find({})
.then((user) => {
res.status(200).json(user);
})
.catch((error) => {
res.status(400).send(error);
});
};
this is my axios api export
import axios from "axios";
export default axios.create({
baseUrl: "http://localhost:8080/users",
});
and this is my useEffect
import api from "../api/users";
import { useState, useEffect } from "react";
export const LogIn = (props) => {
const { setIsNewMember } = props;
const [users, setUsers] = useState([]);
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await api.get("/users");
setUsers(response.data);
} catch (err) {
if (err.response) {
console.log(err.response.data);
console.log(err.response.status);
console.log(err.response.headers);
} else {
console.log(`Error: ${err.message}`);
}
}
};
fetchUsers();
}, []);
I'm getting this error on the frontend
so although I'm getting it from the postman and other services I'm not getting it on the front.
any idea why is that happening?

How can I set a flash variable in Next.js before a redirect?

Laravel in PHP made this easy with https://laravel.com/docs/9.x/session#flash-data, so I figured Next.js would have an easy way too.
I thought I'd be able to do something like:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
ctx.res.setHeader("yourFlashVariable", "yourFlashValue");
console.log('headers', ctx.res.getHeaders()); // Why is it not even appearing here?
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
const props = ...
return { props };
};
and then in my other page:
export const getServerSideProps: GetServerSideProps = async (context) => {
const { headers, rawHeaders } = context.req;
// look inside the headers for the variable
// ...
But the header doesn't appear.
If you know how to achieve the goal of a flash variable (even if not using headers), I'm interested in whatever approach.
(Originally I asked How can I show a toast notification when redirecting due to lack of session using Next-Auth in Next.js? but now feel like I should have asked this more generic question.)
UPDATE
I appreciate the reasonable suggestion from https://stackoverflow.com/a/72210574/470749 so have tried it.
Unfortunately, index.tsx still does not get any value from getFlash.
// getFlash.ts
import { Session } from 'next-session/lib/types';
export default function getFlash(session: Session) {
// If there's a flash message, transfer it to a context, then clear it.
const { flash = null } = session;
console.log({ flash });
// eslint-disable-next-line no-param-reassign
delete session.flash;
return flash;
}
// getNextSession.ts
import nextSession from 'next-session';
export default nextSession();
// foo.tsx
import { getSession } from 'next-auth/react';
import { GetServerSideProps, InferGetServerSidePropsType, NextApiRequest, NextApiResponse } from 'next';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
const req = ctx.req as NextApiRequest;
const res = ctx.res as NextApiResponse;
const nSession = await getNextSession(req, res);
nSession.flash = 'You must be logged in to access this page.'; // THIS LINE CAUSES A WARNING
console.log({ nSession });
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
// ...
return { props };
};
// index.tsx
import { GetServerSideProps } from 'next';
import getFlash from '../helpers/getFlash';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getNextSession(context.req, context.res);
let toast = getFlash(session);
console.log({ toast });
if (!toast) {
toast = 'no toast';
}
console.log({ toast });
return {
props: { toast }, // will be passed to the page component as props
};
};
Also, the nSession.flash = line causes this warning:
warn - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
Your first code is working fine for me (printing the headers in terminal). However, the combination will not work as intended because the headers you set in /foo (say) will be sent to browser, along with a status code of 307, and a location header of /. Now "the browser" will be redirecting to the location and it won't forward your headers. Similar threads: https://stackoverflow.com/a/30683594, https://stackoverflow.com/a/12883411.
To overcome this, you can do something like this. This works because the browser does send the cookies (in this case, set when you create a session).
// lib/session.ts
import type { IronSessionOptions } from 'iron-session'
import type { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
export const sessionOptions: IronSessionOptions = {
password: process.env.SECRET_COOKIE_PASSWORD as string,
cookieName: 'sid',
cookieOptions: { secure: process.env.NODE_ENV === 'production' },
}
declare module 'iron-session' {
interface IronSessionData {
flash?: string | undefined
}
}
export const withSessionRoute = (handler: NextApiHandler) =>
withIronSessionApiRoute(handler, sessionOptions)
export const withSessionSsr = <P extends Record<string, unknown> = Record<string, unknown>>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) => withIronSessionSsr(handler, sessionOptions)
// pages/protected.tsx
import type { NextPage } from 'next'
import { getSession } from 'next-auth/react'
import { withSessionSsr } from 'lib/session'
const ProtectedPage: NextPage = () => <h1>Protected Page</h1>
const getServerSideProps = withSessionSsr(async ({ req, res }) => {
const session = await getSession({ req })
if (!session) {
req.session.flash = 'You must be logged in to access this page.'
await req.session.save()
return { redirect: { destination: '/', permanent: false } }
}
return { props: {} }
})
export default ProtectedPage
export { getServerSideProps }
// pages/index.tsx
import type { InferGetServerSidePropsType, NextPage } from 'next'
import { withSessionSsr } from 'lib/session'
const IndexPage: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ flash }) => {
// TODO: use `flash`
}
const getServerSideProps = withSessionSsr(async ({ req }) => {
// if there's a flash message, transfer
// it to a context, then clear it
// (extract this to a separate function for ease)
const { flash = null } = req.session
delete req.session.flash
await req.session.save()
return { props: { flash } }
})
export default IndexPage
export { getServerSideProps }
This also works if you want to set flash data in an API route instead of pages:
import { withSessionRoute } from 'lib/session'
const handler = withSessionRoute(async (req, res) => {
req.session.flash = 'Test'
await req.session.save()
res.redirect(307, '/')
})
export default handler
Complete example: https://github.com/brc-dd/next-flash/tree/with-iron-session

Get the current Firebase user from Next.js getServerSideProps

I'm looking for how to get the currently signed in Firebase user with Next.js through getServerSideProps. I've already tried using verifyIdAuthToken but I can't seem to change the persistence type through Firebase to get the cookie because I get this error:
[t [Error]: The current environment does not support the specified persistence type.] {
code: 'auth/unsupported-persistence-type',
a: null
}
My current code is this:
// utils/firebaseClient.js
import firebase from 'firebase';
const firebaseConfig = {
// redacted
}
if(!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION);
} else {
firebase.app();
}
const auth = firebase.auth();
const googleProvider = new firebase.auth.GoogleAuthProvider();
export { firebase, googleProvider };
// pages/dashboard.js
import { firebaseAdmin } from '../utils/firebaseAdmin';
import nookies from 'nookies';
export default function Dashboard({ user }) {
if (!user) {
return (
<>
<h1>Loading</h1>
</>
)
}
return (
<>
<h1>Hi</h1>
</>
);
}
export async function getServerSideProps(ctx) {
console.log(nookies.get(ctx));
try {
const cookies = nookies.get(ctx);
console.log(cookies);
const user = await firebaseAdmin.auth().verifyIdToken(cookies.token);
} catch (err) {
console.error(err);
return {
redirect: {
destination: '/',
permanent: false
}
}
}
return {}
}
// pages/index.js
import firebase from 'firebase';
import { googleProvider } from '../utils/firebaseClient';
export default function Authentication() {
firebase.auth().onAuthStateChanged((user) => {
if(user) window.location.replace('/dashboard');
});
const googleButton = () => {
firebase.auth().signInWithPopup(googleProvider).then((result) => {
window.location.href = '/dashboard';
}).catch((err) => {
return console.log(err);
});
}
return (
<>
<button onClick={googleButton}>Login with Google</button>
</>
)
}
Does anyone know how I can fix this, or if there is an alternate way to get the current Firebase user in getServerSideProps?
By the way, I have already read how to get firebase use in nextJS getserversideprops and the solution didn't work for me. It also seems like it didn't work for the author of that question. Thanks in advance!

How to use a promised function in a react app

I'm trying to implement Bullet train API in a React web app. According to their node client documentation, I have setup the following function:
export const isFeatureEnabled = async (nameOfTheFeature) => {
return new Promise((resolve) => {
bulletTrain.init({
environmentID: BULLET_TRAIN_ENV_ID
});
bulletTrain.hasFeature(nameOfTheFeature)
.then((featureFlag) => {
if (featureFlag[nameOfTheFeature].enabled) {
resolve(true);
}
})
.catch(err => resolve(false));
});
}
This is called in regular components like this:
render() {
return (<div>{await isFeatureEnabled('feature1') && <p>feature1 is enabled</p>}</div>)
};
which throws this:
Parsing error: Can not use keyword 'await' outside an async function
If we add the async keyword, with a proper return statement:
async render() {
return (<div>{await isFeatureEnabled('feature1') && <p>feature1 is enabled</p>}</div>)
};
Then it throws:
Your render method should have return statement
So what is the correct way to use this promised function inside a react app?
I would suggest you not to use await keyword in render instead use componentDidMount and constructor for this and use state object to check:
constructor(props){
super(props);
this.state = { isFeatEnabled: false };
}
componentDidMount(){
this.setState({isFeatEnabled:isFeatureEnabled('feature1')})
}
Now in the render:
render() {
return (<div>{this.state.isFeatEnabled && <p>feature1 is enabled</p>}</div>)
};
And remove the async from the method.
call function isFeatureEnabled inside an async function during mount (before/after your wish)
example -
export const isFeatureEnabled = async (nameOfTheFeature) => {
return new Promise((resolve) => {
bulletTrain.init({
environmentID: BULLET_TRAIN_ENV_ID
});
bulletTrain.hasFeature(nameOfTheFeature)
.then((featureFlag) => {
if (featureFlag[nameOfTheFeature].enabled) {
resolve(true);
}
})
.catch(err => resolve(false));
});
}
...
componentDidMount() {
this.checEnabled();
}
...
const checkEnabled = async () => {
const flag = await isFeatureEnabled('feature1');
this.setState({f1enabled: flag});
}
...
render() {
return (<div>{this.state.f1enabled ? <p>feature1 is enabled</p> : null}</div>)
}
If isFeatureEnabled is in the same file keep it outside class component or else keep it in another file and export the function.
You can't use promise at there, the proper way:
import React, { useEffect, useState } from 'react'
import bulletTrain from '../somewhere'
import BULLET_TRAIN_ENV_ID from '../somewhere'
export default function featureComponent({ featureName }) {
const [featureEnabled, setFeatureEnabled] = useState(false)
useEffect(() => {
bulletTrain.init({
environmentID: BULLET_TRAIN_ENV_ID
})
bulletTrain
.hasFeature(featureName)
.then(featureFlag => {
if (featureFlag[featureName].enabled) {
setFeatureEnabled(true)
}
})
.catch(err => setFeatureEnabled(false))
}, [featureName])
return <div>{featureEnabled && <p>{featureName} is enabled</p>}</div>
}
Append isFeatureEnabled function re-use answer below:
import React, { useEffect, useState } from 'react'
import isFeatureEnabled from '../somewhere'
export default function featureComponent({ featureName }) {
const [featureEnabled, setFeatureEnabled] = useState(false)
useEffect(() => {
const checkAndSetEnabled = async () => {
const enabled = await isFeatureEnabled(featureName)
setFeatureEnabled(enabled)
}
checkAndSetEnabled()
}, [featureName])
return <div>{featureEnabled && <p>{featureName} is enabled</p>}</div>
}

Data leak due to using asynchronous function

I am getting a data leak while using an asychronous function causing my application to not load the second page during navigation.
I am using async/await on my get request, and I have tried to use a cleanup function to prevent this leak, but it is not working.
How do I fix this leak, and still get the data to load when the page is loaded?
import React, { useEffect, useState, useContext } from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
import axios from "axios";
import StatusContext from "../../context/status/statusContext";
const Table = props => {
const [tableData, setTableData] = useState([]);
const statusContext = useContext(StatusContext);
useEffect(async () => {
await axios
.get("/api/status")
.then(function(response) {
console.log(response.data);
setTableData(
response.data.filter(item => {
let itemDate = new Date(item.date);
let variableDate = new Date() - 604800000;
return itemDate > variableDate;
})
);
})
.catch(function(error) {
console.log(error);
});
}, [statusContext]);
const columns = [
{
id: "Name",
Header: "Name",
accessor: "name"
},
{
Header: "Date",
accessor: "date"
},
{
Header: "Comment",
accessor: "comment"
}
];
return (
<ReactTable
data={tableData}
columns={columns}
pivotBy={["date"]}
defaultPageSize={7}
minRows={5}
/>
);
};
export default Table;
There's really no need to bring async/await into this situation, and in fact useEffect won't work if you do. The only thing you can return from useEffect is a cleanup function, and an async function returns a Promise.
This should work just fine, including a cleanup function in case you unmount your component before the promise resolves:
useEffect(() => {
let isMounted = true;
axios
.get("/api/status")
.then(function(response) {
if (!isMounted) {
return;
}
console.log(response.data);
setTableData(
response.data.filter(item => {
let itemDate = new Date(item.date);
let variableDate = new Date() - 604800000;
return itemDate > variableDate;
})
);
})
.catch(function(error) {
console.log(error);
});
return () => {
isMounted = false;
}
}, [statusContext]);

Resources