Home > Software engineering >  Hooks inside of utility components
Hooks inside of utility components

Time:09-21

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);

CodePudding user response:

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.

  • Related