Lekan Lawal
Lekan Lawal

Reputation: 1

Is it possible to use react hooks in a component that will be rendered with renderToString

I have a component that contains react-router-dom's Link but throws the error below when I pass the component in the react-dom renderToString

Uncaught Error: useHref() may be used only in the context of a <Router> component.

Can't I pass a component that has react hooks into renderToString functions?

import { renderToString } from 'react-dom/server';

  const html = renderToString(<Contact />);
  console.log(html);

Upvotes: 0

Views: 380

Answers (1)

Lin Du
Lin Du

Reputation: 102257

I guess you use the useHref() hook inside <Contact/> component. But useHref() hook must be used in a Router context, the error throws at line lib/hooks.tsx#L36. Let's see the source code of useHref() hook:

export function useHref(to: To): string {
  invariant(
    useInRouterContext(),
    // TODO: This error is probably because they somehow have 2 versions of the
    // router loaded. We can help them understand how to avoid that.
    `useHref() may be used only in the context of a <Router> component.`
  );

  let { basename, navigator } = React.useContext(NavigationContext);
  let { hash, pathname, search } = useResolvedPath(to);

  let joinedPathname = pathname;
  if (basename !== "/") {
    let toPathname = getToPathname(to);
    let endsWithSlash = toPathname != null && toPathname.endsWith("/");
    joinedPathname =
      pathname === "/"
        ? basename + (endsWithSlash ? "/" : "")
        : joinPaths([basename, pathname]);
  }

  return navigator.createHref({ pathname: joinedPathname, search, hash });
}

We can use <StaticRouter> to do the SSR, StaticRouter component uses Router component underly, Router uses NavigationContext.Provider and LocationContext.Provider components to provide the context value to its children components, then you can use useHref() hook in these children components.

Here is a working example:

import React from 'react';
import { renderToString } from 'react-dom/server';
import { describe, it } from "@jest/globals";
import { Routes, Route, useHref } from 'react-router-dom';
import { StaticRouter } from 'react-router-dom/server';

const Contact = ({ to }) => {
  const href = useHref(to)
  return <pre>{href}</pre>
}

describe('76209110', () => {
  it('should pass', () => {
    const html = renderToString(
      <StaticRouter location={'/courses'}>
        <Routes>
          <Route
            path="courses"
            element={<Contact to="advanced-react" />}
          />
        </Routes>
      </StaticRouter>
    )
    console.log('html: ', html)
  })
})

Test result:

  console.log
    html:  <pre>/courses/advanced-react</pre>

      at Object.<anonymous> (stackoverflow/76209110/index.test.tsx:24:13)

 PASS  stackoverflow/76209110/index.test.tsx
  76209110
    ✓ should pass (66 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.42 s

package version:

"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.1",

Upvotes: 0

Related Questions