Next.js Discord

Discord Forum

How to revalidateTag without API routes?

Unanswered
Oak saucer gall posted this in #help-forum
Open in Discord
Avatar
Oak saucer gallOP
I have try to revalidate user settings, but the changes only reflect in database, i need to relog to apply the changes..

How can i define an tag user for example, to revalidate later?

export async function getUserByEmail(email: string) {
  try {
    return await db.user.findUnique({ where: { email } });
  } catch (error) {
    return null;
  }
}


export async function updateProfile(state: UpdateProfile, formData: FormData) {
  const validatedFields = UpdateProfileSchema.safeParse({
    image: formData.get('image') || null,
  })

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      error: 'Invalid fields.',
      success: '',
    }
  }

  const { image } = validatedFields.data;
  const blob = await put(image.name, image, { access: 'public' })

  const user = await currentUser();

  if (!user) {
    return { error: 'Unauthorized.', success: '' }
  }

  const dbUser = await getUserById(user.id);

  if (!dbUser) {
    return { error: 'Unauthorized.', success: '' }
  }

  if (user.id !== dbUser.id) {
    return { error: 'Unauthorized', success: '' }
  }

  try {
    const updatedUser = await db.user.update({
      where: { id: dbUser.id },
      data: {
        image: blob.url,
        firstName,
        lastName,
      }
    });

    update({
      user: {
        image: updatedUser.image,
      }
    });

    // revalidateTag('user');
    return { error: '', success: 'Profile updated with successfully!' }
  } catch (error) {
    throw error    
  }
}

10 Replies

Avatar
Ray
you could use unstable_cache to tag the db query
https://nextjs.org/docs/app/api-reference/functions/unstable_cache
like this
export const getUserByEmail = (email: string) => {
  return unstable_cache(
    async (email: string) => {
      return await db.user.findUnique({ where: { email } });
    },
    [`user-by-email`],
    { tags: [`user-by-email-${email}`] }
  )(email);
};
and revalidate it like this
revalidateTag(`user-by-email-user1@user.com`);
Avatar
Oak saucer gallOP
Iam wrong? why doesnt work?

async function getUserByEmail(email: string) {
  try {
    return await db.user.findUnique({ where: { email } });
  } catch (error) {
    return null;
  }
}

export const getCachedUserByEmail = unstable_cache((email) => getUserByEmail(email), ['user'])
// ...
  try {
    const updatedUser = await db.user.update({
      where: { id: dbUser.id },
      data: {
        image: blob.url,
        firstName,
        lastName,
      }
    });

    update({
      user: {
        image: updatedUser.image,
        firstName: updatedUser.firstName,
        lastName: updatedUser.lastName,
      }
    });

    revalidateTag('user');
    return { error: '', success: 'Profile updated with successfully!' }
  } catch (error) {
    throw error    
  }
}
Avatar
Multiflora rose seed chalcid
Avatar
Oak saucer gallOP
In the case of using next auth, is it considered bad practice to use user information in the token, such as name, email and image? Because whenever I change the image, for example, for it to be reflected properly it is necessary to relog the application, not even CTRL + F5 can help.

Furthermore, if I try to use unstable_cache it gives me an error.

export const cacheGetUserById = unstable_cache(
  async (id: string) => {
    return await db.user.findUnique({ where: { id } });
  },
  ['user'],
  {
    tags: ['user']
  }
)

error:
[auth][error] JWTSessionError: Read more at https://errors.authjs.dev#jwtsessionerror
[auth][cause]: Error: Invariant: incrementalCache missing in unstable_cache async (id)=>{
  return await _lib_db__WEBPACK_IMPORTED_MODULE_0__.db.user.findUnique({
      where: {
          id
      }
  });
}
  at cachedCb (webpack-internal:///(middleware)/./node_modules/next/dist/esm/server/web/spec-extension/unstable-cache.js:37:19)
  at Object.jwt (webpack-internal:///(middleware)/./app/auth/auth.config.ts:58:92)
  at Module.session (webpack-internal:///(middleware)/./node_modules/@auth/core/lib/actions/session.js:30:43)
  at async AuthInternal (webpack-internal:///(middleware)/./node_modules/@auth/core/lib/index.js:50:24)
  at async Auth (webpack-internal:///(middleware)/./node_modules/@auth/core/index.js:123:29)
  at async handleAuth (webpack-internal:///(middleware)/./node_modules/next-auth/lib/index.js:81:29)
  at async adapter (webpack-internal:///(middleware)/./node_modules/next/dist/esm/server/web/adapter.js:176:16)
  at async runWithTaggedErrors (/root/templates/auth-template/node_modules/next/dist/server/web/sandbox/sandbox.js:99:24)
  at async DevServer.runMiddleware (/root/templates/auth-template/node_modules/next/dist/server/next-server.js:1035:24)
  at async DevServer.runMiddleware (/root/templates/auth-template/node_modules/next/dist/server/dev/next-dev-server.js:260:28)
[auth][details]: {}
auth.config.ts:
 async jwt({ token }) {
    if (!token.sub) return token;

    const user = await cacheGetUserById(token.sub);

    if (!user) return token;

    const existingAccount = await getAccountById(user.id);

    if (user) {
      token.firstName = user.firstName;
      token.lastName = user.lastName;
      token.email = user.email;
      token.emailVerified = user.emailVerified;
      token.role = user.role as UserRole;
      token.isTwoFactorEnabled = user.isTwoFactorEnabled;
      token.isOAuth = !!existingAccount;
    }

    return token;
  },
  session({ session, token }) {
    if (token.sub) {
      session.user.id = token.sub
    }
    
    if (session.user) {
      session.user.firstName = token.firstName;
      session.user.lastName = token.lastName;
      session.user.email = token.email;
      session.user.emailVerified = !!token.emailVerified;
      session.user.isTwoFactorEnabled = !!token.isTwoFactorEnabled;
      session.user.isOAuth = !!token.isOAuth;
      session.user.role = token.role;
    }

    return session;
  },
update-profile page:
import { auth } from "#/app/auth/providers";
import { UserProfile } from "#/components/user/account/user-profile";

export default async function UpdateProfilePage() {
  const session = await auth();

  return (
    <main className="w-full max-w-screen-md mx-auto border rounded bg-gray-50">
      <UserProfile user={session?.user} />
    </main>
  )
}

update-user form:
export function UpdateUserForm({ user }: User) {
  const [state, dispatch] = useFormState(updateProfile, initialState)
  const username = `${user.firstName} ${user.lastName}`

  return (
    <form action={dispatch} className="grid gap-6">
      <UpdateAvatar username={username} image={user.image} />
      <div className="grid gap-1">
        <label htmlFor="">First name</label>
        <Input type="text" name="firstName" defaultValue={user.firstName} />
      </div>
      <div className="grid gap-1">
        <label htmlFor="">Last name</label>
        <Input type="text" name="lastName" defaultValue={user.lastName} />
      </div>
      <FormError message={state.error} />
      <FormSuccess message={state.success} />
      <div className="flex gap-4 items-center">
        <Button variant="ghost">Cancel</Button>
        <Button>Update profile</Button>
      </div>
    </form>
  )
}
Avatar
Ray
export const getCachedUserByEmail = unstable_cache((email) => getUserByEmail(email), ['user'], { tags: ['user'] })

you didn't set the tag for it