Why Taro’s useRouter Returns Null and How to Fix It with a Babel Plugin

An unexpected TypeError in Taro’s useRouter reveals that the router can be null after onHide, leading to crashes; this article dissects the root cause in Taro 3.6.29, examines the code flow, and presents a Babel plugin workaround that safely retrieves parameters.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
Why Taro’s useRouter Returns Null and How to Fix It with a Babel Plugin

1. Background

On an ordinary day, a developer encountered a TypeError: null is not an object while reading router parameters in a Taro mini‑program.

The error message was TypeError: null is not an object (evaluating '(0,X.useRouter)().params') , which originates from Taro’s useRouter method that should provide a default empty object.

2. Source of the Parameters

Analysis based on Taro 3.6.29.

Method Definition

The developer inspected the TypeScript definition of useRouter and found that it returns null by default, with a dynamic flag that can lock the router.

export const useRouter = (dynamic = false) => {
  return dynamic ? Current.router : React.useMemo(() => Current.router, [])
}
...
export const Current = {
  app: null,
  router: null,
  page: null,
  // RN navigation instance
}

Because the router was null, the error occurred.

Setting dynamic to true allowed the code to run, but the problem resurfaced later.

Parameter Updates

The router is updated in setCurrentRouter during the page’s onLoad lifecycle, and cleared to null in onHide, which explains why the error reappears after the page is hidden.

export function createNativePageConfig(Component, pageName: string, data, react, reactdom, pageConfig) {
  ...
  function setCurrentRouter(page: MpInstance) {
    const router = page.route || page.__route__ || page.$taroPath
    Current.router = {
      params: page.$taroParams!,
      path: addLeadingSlash(router),
      $taroPath: page.$taroPath,
      onReady: getOnReadyEventKey(id),
      onShow: getOnShowEventKey(id),
      onHide: getOnHideEventKey(id)
    }
    if (!isUndefined(page.exitState)) {
      Current.router.exitState = page.exitState
    }
  }
  ...
  [ONSHOW]() {
    // set Current page and router
    Current.page = this as any
    setCurrentRouter(this)
  },
  [ONHIDE]() {
    if (Current.page === this) {
      Current.page = null
      Current.router = null
    }
  },
  ...
}

Asynchronous Issue

The developer reproduced the error by initializing a component that calls useRouter and logs the router in useDidHide. The component’s rendering logic includes a ternary expression that hides the page while awaiting an API response, triggering the bug.

const Example = () => {
  const router = useRouter()
  useDidHide(() => {
    console.log('hide', router)
  })
}

The image illustrates the component’s conditional rendering.

Component rendering diagram
Component rendering diagram

3. Solution

Since the issue cannot be fixed inside Taro, a custom Babel plugin was created to replace all calls to useRouter with a wrapper that safely retrieves parameters even after onHide.

import { useRouter as useTaroRouter, RouterInfo, getCurrentPages } from '@tarojs/taro'

interface ReturnRouter<T> extends Partial<Omit<RouterInfo, 'params'>> {
  params: T;
}
export const useRouter = <T extends Record<string, string>>(dynamic?: boolean): ReturnRouter<T> => {
  const defaultParams = { params: {} as T };
  try {
    const taroParams = useTaroRouter(dynamic);
    if (!taroParams) {
      const pages = getCurrentPages();
      const page = pages && pages[pages?.length - 1];
      const options = page?.$taroParams || page?.options;
      if (options) {
        return { params: options } as ReturnRouter<T>;
      }
    }
    return taroParams as ReturnRouter<T>;
  } catch (err) {
    return defaultParams;
  }
};
BabelRouterTaroTypeErroruseRouter
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.