2

I'm trying to follow Ben Awad's lireddit tutorial.

He shows how to make a utility file to show any authentication error across the app.

I think things may have changed with the next/router since he made the video. I'm struggling to figure out a way to use the router to redirect inside a hook, inside a component in this way.

I can't put the useRouter hook inside the exchangeError - I think the reason for that is that when I want to use the exchangeError inside other components, the hook is no longer at the top level, but then I can't figure out how to make the exchangeError work without it, or adapt to the udpated version of next/router.

My best attempt (I know I can't use it like this) is below:

import { dedupExchange, fetchExchange, createClient, Exchange } from "urql";
import {  pipe, tap } from 'wonka'
import { useRouter } from 'next/router'


const errorExchange: Exchange = ({ forward }) => (ops$) => {
    // where can i put this
    // const Router = useRouter();

    return pipe(
      forward(ops$),
      tap(({ error }) => {
        if (error?.message.includes("not authenticated")) {
        //   Router.replace("/auth/login");
        }
      })
    );
  };

The version Ben published in lireddit is:

const errorExchange: Exchange = ({ forward }) => (ops$) => {
  return pipe(
    forward(ops$),
    tap(({ error }) => {
      if (error?.message.includes("not authenticated")) {
        Router.replace("/login");
      }
    })
  );
};

The whole utility file is below:

import { dedupExchange, fetchExchange, createClient, Exchange } from "urql";
import { cacheExchange } from '@urql/exchange-graphcache'
import { MeDocument, LoginMutation, RegisterMutation, MeQuery, LogoutMutation } from "../generated/graphql"
import { betterUpdateQuery } from '../utils/betterUpdateQuery'
import {  pipe, tap } from 'wonka'
import { useRouter } from 'next/router'


const errorExchange: Exchange = ({ forward }) => (ops$) => {
    // where can i put this
    // const Router = useRouter();

    return pipe(
      forward(ops$),
      tap(({ error }) => {
        if (error?.message.includes("not authenticated")) {
        //   Router.replace("/auth/login");
        }
      })
    );
  };


export const createUrqlClient = (ssrExchange: any) => (
    {
        url: 'http://localhost:4000/graphql',
        fetchOptions: {
            credentials: 'include' as const,
        },
        exchanges: [
            dedupExchange,
            cacheExchange({
            updates: {
                Mutation: {
                logout: (_result, args, cache, info) => {
                    betterUpdateQuery<LogoutMutation, MeQuery>(
                    cache,
                    {query: MeDocument},
                    _result,
                    (result, query) => ({me: null})
                    )
                },
                login: (_result, args, cache, info) => {
                    betterUpdateQuery<LoginMutation, MeQuery>(
                    cache,
                    { query: MeDocument },
                    _result,
                    (result, query) => {
                        if (result.login.errors) {
                        return query;
                        } else {
                        return {
                            me: result.login.user,
                        };
                        }
                    }
                    );
                },
                register: (_result, args, cache, info) => {
                    betterUpdateQuery<RegisterMutation, MeQuery>(
                    cache,
                    { query: MeDocument },
                    _result,
                    (result, query) => {
                        if (result.register.errors) {
                        return query;
                        } else {
                        return {
                            me: result.register.user,
                        };
                        }
                    }
                    );
                },
                },
            },
            }),
            errorExchange,
            ssrExchange,
            fetchExchange,
        ],
    } 
)

Then, in the create post form, this utility is used as follows:

import { withUrqlClient } from "next-urql";
import { useRouter } from "next/router";
import React from "react";
import { useCreatePostMutation } from "../generated/graphql";
import { createUrqlClient } from "../utils/createUrqlClient";
import { useIsAuth } from "../utils/useIsAuth";

const CreatePost: React.FC<{}> = ({}) => {
  // const router = useRouter();
  // useIsAuth();
  const [, createPost] = useCreatePostMutation();
  return (
    <Layout variant="small">
      <Formik
        initialValues={{ title: "", text: "" }}
        onSubmit={async (values) => {
          const { error } = await createPost({ input: values });
          if (!error) {
            router.push("/");
          }
        }}
      >

export default withUrqlClient(createUrqlClient)(CreatePost);
juliomalves
  • 42,130
  • 20
  • 150
  • 146
Mel
  • 2,481
  • 26
  • 113
  • 273
  • Use import Router from "next/router"; instead of hook – DraganS Sep 21 '21 at 00:07
  • That produces errors (replace is not available on Router anymore). To resolve those, the suggestions are to use the useRouter hook: https://stackoverflow.com/questions/66676995/next-js-router-replace-is-not-a-function – Mel Sep 21 '21 at 00:28
  • Can you please show us the code that invokes `errorExchange`? – hackape Sep 21 '21 at 00:36
  • I tried look into benawad's code base, saw `errorExchange` is dependency of `createUrqlClient`, but couldn't find where he invokes `createUrqlClient`. Not familiar with the whole stack, I'll need a bit of context to properly understand the problem. – hackape Sep 21 '21 at 00:42
  • Please check https://github.com/vercel/next.js/blob/master/examples/with-passport-and-next-connect/pages/profile.js#L37 – DraganS Sep 21 '21 at 00:45
  • I added the form that is wrapped in the utility (not sure if that's correct terminology) – Mel Sep 21 '21 at 00:45
  • That is so random @DraganS- yesterday I used this in the way Ben shows, and got errors saying replace isn't available on Router - which led me down the hooks path. I'm not getting that today. I can't explain the reasons - I literally uncommented the original that produced the error. – Mel Sep 21 '21 at 00:55

1 Answers1

0

So I spent some time diving into source code. Conclusion:

import { dedupExchange, fetchExchange, createClient, Exchange } from "urql";
import { pipe, tap } from 'wonka'
import Router from 'next/router'

const errorExchange: Exchange = ({ forward }) => (ops$) => {

    return pipe(
      forward(ops$),
      tap(({ error }) => {
        if (error?.message.includes("not authenticated")) {
          Router.router.replace("/auth/login");  // <-- `Router.router` is the way
        }
      })
    );
  };

I have no clue why they decide to expose the API this way. Router.router looks really weird to me, but that's the solution.


Note on why useRouter hook is not an option.

Hook must be called inside an react component instance. But errorExchange is meant to be called by createUrqlClient, which is in turn called by withUrqlClient(createUrqlClient)(CreatePost).

withUrlClient is a decorator, or HOC in react's term, and so I checked the timing it calls errorExchange is just not right for a hook function to step-in, thus an dead end.

hackape
  • 18,643
  • 2
  • 29
  • 57
  • Thanks a lot for sharing this. I have no skills in investigating the source of these probelms so I just go down a maze of dead ends. Thank you very much again. – Mel Sep 21 '21 at 01:40
  • I spent a solid hour investigating...And I'm baffled these days things got so complicated just to write an app. – hackape Sep 21 '21 at 01:42