I have a simple meteor application with a component to show the username:
const Welcome = ({ isLoading, username }) => {
if (isLoading) {
return <Loader />;
} else {
return <div>{username}</div>;
}
};
I'm trying to pass the username in via
const WelcomeContainer = withTracker(() => {
const isLoading = !Meteor.user() || Meteor.loggingIn();
const username = Meteor.user() ? Meteor.user().username : '';
return {
isLoading,
username,
};
})(Welcome);
If I navigate to the component normally, everything seems to work fine. However, if I refresh the page, about half the time it gets stuck on the loading component. (Notably, I'm using ReactRouter)
What's more is the logs are quite similar:
console.log(`client: ${Meteor.isClient} and isLoading: ${isLoading}`);
// Username displayed successfully
client: true and isLoading: true (x2)
client: true and isLoading: true (x2)
client: true and isLoading: false (x2)
// Stuck on Loading
client: true and isLoading: true (x2)
client: true and isLoading: false (x2)
Am I misunderstanding trackers? Do I need to subscribe to something here? How can I get the component to consistently display the username?
The problem was I had <React.StrictMode>. Removing this resoles the issue.
Related
In an app I'm building, a user logins in (validates through Magic Auth), and we save the user data in the session variable (using Iron for session management):
export default withSessionRoute(async (req, res) => {
try {
const didToken = req.headers?.authorization?.substr(7);
if (didToken) {
magic.token.validate(didToken);
const metadata: any = await magic.users.getMetadataByToken(didToken);
const response = await createUser({ email: metadata.email });
req.session.user = response.data.user;
req.session.userMetadata = metadata;
await req.session.save();
return res.send({ user: response.data.user, authenticated: true });
} else {
res.status(200).json({ authenticated: false });
}
} catch (error: any) {
console.log(error);
res.status(500).json({ error: error.message });
}
});
This works totally fine locally (req.session.user remains filled with user data), but for some reason on my deployed app, it does not work at all. It still returns the user data in "req.send", but the session variable is empty, so it does not seem to have saved.
Any ideas on what the issue could be?
This is how we are authorizing users in our website
signIn('credentials', {
phoneNumber: verifiedPhone,
code: otp.data,
type: 'phone',
redirect: false,
}).then((res) => {
console.log(res) // default response {error,status,ok,url}
// How can i add additional data to this response, in my case user session
// if (!res.user.name) openModal('add_name')
// else toast.success('You are all set!')
});
By default, signIn will then return a Promise, that resolves:
{
error: string | undefined
status: number
ok: boolean
url: string | null
}
And we wanna add custom data to this promise return.
Actually what we wanna do is to sign user in and if the user is new, he/she is supposed to have no username so a modal opens up, enters his/her username and we update the next-auth session.
[...nextauth].js:
...
async authorize(credentials, req) {
// check the code here
const res = await requests.auth.signInEnterOtp(
credentials.phoneNumber,
credentials.code,
credentials.type
);
if (!res.ok) return null
return {
user: {
access_token: res.data?.access_token,
token_type: res.data?.token_type,
expires_at: res.data?.expires_at,
user_info: {
id: res.data?.user.id,
name: res.data?.user.name,
phone: res.data?.user.phone,
user_type: res.data?.user.user_type,
},
},
};
},
...
I eventually figured it like this:
...
.then(async (res) => {
const session = await getSession()
...
})
...
Bu i have another problem, it is to update the session with new username (
EDIT
i found a way of how to change session after sign in
[...nextauth].js :
...
async authorize(credentials, req){
...
if(credentials.type === 'update_name'){
const session = await getSession({ req })
return session.user.name = credentails.name
}
...
}
on the client :
signIn('credentials', {
name: newName,
type: 'name_update',
redirect: false
)
I have a product page at /products/[slug].js
and I use Incremental Static Generation for a wordpress/graphql site:
export async function getStaticProps(context) {
const {params: { slug }} = context
const {data} = await client.query(({
query: PRODUCT_SLUG,
variables: { slug }
}));
return {
props: {
categoryName: data?.productCategory?.name ?? '',
products: data?.productCategory?.products?.nodes ?? []
},
revalidate: 1
}
}
export async function getStaticPaths () {
const { data } = await client.query({
query: PRODUCT_SLUGS,
})
const pathsData = []
data?.productCategories?.nodes && data?.productCategories?.nodes.map((productCategory) => {
if (!isEmpty(productCategory?.slug)) {
pathsData.push({ params: { slug: productCategory?.slug } })
}
})
return {
paths: pathsData,
fallback: true,
}
}
Everything works as expected except one thing. If I delete a product from wordpress which was previously published, NextJs serves the cached page instead of showing 404 - Not found page, and I think this is how it is supposed to work, meaning that if something isn't rebuilt, show the previous (stale) page.
But how can I completely remove the cache for a specific product which has been deleted and it is not fetched again from the PRODUCT_SLUGS query ?
I have read the fallback options: true, false, blocking but none of them seems to work.
Is there a solution to this, either a next.config.js configuration or another work around ?
So I ran into this same issue, although I am using GraphCMS. So here is what you need to do to fix:
export async function getStaticProps(context) {
const {params: { slug }} = context
const {data} = await client.query(({
query: PRODUCT_SLUG,
variables: { slug }
}));
if (!data) {
return {
notFound: true
}
} else {
return {
props: {
categoryName: data?.productCategory?.name ?? '',
products: data?.productCategory?.products?.nodes ?? []
},
revalidate: 1
}
}
}
You need to return notFound: true in getStaticProps
notFound - An optional boolean value to allow the page to return a 404 status and page.
See this page in the docs https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
Then in getStaticPaths change fallback to fallback: "blocking". If you keep fallback: true it is going to serve the stale page since that built successfully.
I think this is possible starting from next#12.1.x using this feature On-demand Incremental Static Regeneration
https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta
basically you can define an api path in this way
// pages/api/revalidate.js
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
const PRODUCT_SLUGS = req.query.product_slug;
try {
await res.unstable_revalidate(`/products/${PRODUCT_SLUGS}`)
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}
Using this api path you can invalidate the cache for a specific product
I am attempting to implement NextAuth in my NextJs app. I am following the official documentation. But for one reason or the other, it seems like the user session object is not generated on login.
Here is my code from my pages/api/auth/[...nextauth].js file
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import axios from "axios";
export default (req, res) =>
NextAuth(req, res, {
providers: [
Providers.Credentials({
id: 'app-login',
name: APP
authorize: async (credentials) => {
console.log("credentials_:", credentials);
try {
const data = {
username: credentials.username,
password: credentials.password
}
// API call associated with authentification
// look up the user from the credentials supplied
const user = await login(data);
if (user) {
// Any object returned will be saved in `user` property of the JWT
return Promise.resolve(user);
}
} catch (error) {
if (error.response) {
console.log(error.response);
Promise.reject(new Error('Invalid Username and Password combination'));
}
}
},
}),
],
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
// Use JSON Web Tokens for session instead of database sessions.
// This option can be used with or without a database for users/accounts.
// Note: `jwt` is automatically set to `true` if no database is specified.
jwt: true,
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 1 * 3 * 60 * 60, // 3 hrs
// Seconds - Throttle how frequently to write to database to extend a session.
// Use it to limit write operations. Set to 0 to always update the database.
// Note: This option is ignored if using JSON Web Tokens
updateAge: 24 * 60 * 60, // 24 hours
},
callbacks: {
// signIn: async (user, account, profile) => { return Promise.resolve(true) },
// redirect: async (url, baseUrl) => { return Promise.resolve(baseUrl) },
// session: async (session, user) => { return Promise.resolve(session) },
// jwt: async (token, user, account, profile, isNewUser) => { return Promise.resolve(token) }
},
pages: {
signIn: '/auth/credentials-signin',
signOut: '/auth/credentials-signin?logout=true',
error: '/auth/credentials-signin', // Error code passed in query string as ?error=
newUser:'/'
},
debug: process.env.NODE_ENV === "development",
secret: process.env.NEXT_PUBLIC_AUTH_SECRET,
jwt: {
secret: process.env.NEXT_PUBLIC_JWT_SECRET,
}
});
const login = async data => {
var config = {
headers: {
'Content-Type': "application/json; charset=utf-8",
'corsOrigin': '*',
"Access-Control-Allow-Origin": "*"
}
};
const url = remote_user_url;
const result = await axios.post(url, data, config);
console.log('result', result);
return result;
};
What am I not getting it right here? Thanks for the help.
I managed to resolve the issue eventually. Something was wrong due to specifying the 'id' and 'name' options for the custom credential provider
I have removed them and the code is working now.
I'm new to redux thunk and have been using it to call an action creator that makes a request to an API to see if the user is logged in. If the user is not logged I want to redirect to the login page. I want a loader component to show while the user is still being fetched but also to wait until it has been fetched before either redirecting or showing the page component. This is what my user reducer looks like.
//User reducer
export default (state = { isFetching: false, hasFetched: false }, action) => {
switch (action.type) {
case GET_USER_REQUEST:
return { ...state, isFetching: true };
case GET_USER_SUCCESS:
return {
...state,
...action.payload,
isFetching: false,
hasFetched: true
};
case GET_USER_FAILURE:
return {
...state,
...action.payload,
isFetching: false,
hasFetched: true
};
default:
return state;
}
};
I can then use the simple function below to check if user state is ready:
const isStateReady = state => {
return !state.isFetching && state.hasFetched;
};
I was running into issues without the 'hasFetched' flag as the app would redirect to the login page before the fetch was complete.
Is this an okay way to handle my redux state in my whole application? Are there any other patterns?