@fleur/froute
TypeScript icon, indicating that this package has built-in type declarations

1.0.9 • Public • Published

CI latest BundleSize License npm

Froute

CHANGELOG

Framework independent Router for React.
Can use with both Fleur / Redux (redux-thunk).

With provides Next.js subset useRouter

yarn add @fleur/froute

Features

See all examples in this spec or examples

  • Library independent
    • Works with Redux and Fleur
  • Next.js's Router subset compatiblity (useRouter, withRouter)
  • Supports dynamic import without any code transformer
  • Supports Sever Side Rendering
    • Supports preload
    • ResponseCode and Redirect component
  • Custom route resolution (for i18n support)
  • URL Builder

API Overview

Hooks

  • useRouter - Next.js subset compat hooks
    • withRouter available
  • useFrouteRouter - useRouter superset (not compatible to Next.js's useRouter)
  • useRouteComponent
  • useBeforeRouteChange(listener: () => Promise<boolean | void> | boolean | void)
    • It can prevent routing returns Promise<false> | false
Deprecated APIs

The following hooks are deprecated. These features are available from useFrouteRouter.

  • useParams
  • useLocation
  • useNavigation
  • useUrlBuilder

Components

  • <Link href={string} />
  • <FrouteLink to={routeDef} params={object} query={object} /> - Type-safe routing
  • <ResponseCode status={number} />
  • <Redirect url={string} status={number = 302}

Getting started

Route definition:

export const routes = {
  index: routeOf('/').action({
    component: () => import('./pages/index'),
  }),
  user: routeOf('/users/:userId').action({
    component: () => import('./pages/user'),
    preload: (store: Store, params /* => inferred to { userId: string } */) =>
      Promise.all([ store.dispatch(fetchUser(param.userId)) ]),
  })
}

App:

import { useRouteComponent, ResponseCode } from '@fleur/froute'

export const App = () => {
  const { PageComponent } = useRouteComponent()

  return (
    <div>
      {PageComponent ? (
        <PageComponent /> 
      ) : (
        <ResponseCode status={404}>
          <NotFound />
        </ResponseCode>
      )}
    </div>
  )
}

User.tsx:

import { useRouter, buildPath } from '@fleur/froute'
import { routes, ResponseCode, Redirect } from './routes'

export default () => {
  const { query: { userId } } = useRouter()
  const user = useSelector(getUser(userId))

  if (!user) {
    return (
      <ResponseCode status={404}>
        <NotFound />
      </ResponseCode>
    )
  }

  if (user.suspended) {
    return (
      <Redirect status={301} url='/'>
        This account is suspended.
      </Redirect>
    )
  }
  
  return (
    <div>
      Hello, {user.name}!
      <br />
      <Link href={buildPath(routes.user, { userId: '2' })}>
        Show latest update friend
      </Link>
    </div>
  )
}

Server side:

import { createRouter } from '@fleur/froute'
import { routes } from './routes'

server.get("*", async (req, res, next) => {
  const router = createRouter(routes, {
    preloadContext: store
  })

  await router.navigate(req.url)
  await context.preloadCurrent();

  const content = ReactDOM.renderToString(
    <FrouteContext router={router}>
      <App />
    </FrouteContext>
  )

  // Handling redirect
  if (router.redirectTo) {
    res.redirect(router.statusCode, router.redirectTo)
  } else{
    res.status(router.statusCode)
  }
  
  const stream = ReactDOM.renderToNodeStream(
    <Html>
      {content}
    </Html>
  ).pipe(res)

  router.dispose()
})

Client side:

import { createRouter, FrouteContext } from '@fleur/froute'

domready(async () => {
  const router = createRouter(routes, {
    preloadContext: store,
  });

  await router.navigate(location.href)
  await router.preloadCurrent({ onlyComponentPreload: true })

  ReactDOM.render((
      <FrouteContext router={router}>
        <App />
      </FrouteContext>
    ),
    document.getElementById('root')
  )
})

Next.js compat status

  • Compat API via useRouter or withRouter
    • Compatible features
      • query, push(), replace(), prefetch(), back(), reload()
      • pathname is provided, but Froute's pathname is not adjust to file system route.
    • Any type check not provided from Next.js (Froute is provided, it's compat breaking)
  • Next.js specific functions not supported likes asPath, isFallback, basePath, locale, locales and defaultLocale
    • <Link /> only href props compatible but behaviour in-compatible.
      • Froute's Link has <a /> element. Next.js is not.
      • as, passHref, prefetch, replace, scroll, shallow is not supported currently.
    • pathname is return current location.pathname, not adjust to component file path base pathname.
    • router.push(), router.replace()
      • URL Object is does not support currentry
      • as argument is not supported
    • router.beforePopState is not supported
      • Use useBeforeRouteChange() hooks instead
    • router.events
      • Partially supported: routeChangeStart, routeChangeComplete, routeChangeError
        • Only url or err arguments.
        • Not implemented: err.cancelled and { shallow } flag.
      • Not implemented: beforeHistoryChange, hashChangeStart, hashChangeComplete

Why froute provides Next.js compat hooks?

It aims to migrate to Next.js from react-router or another router.

Froute's useRouter aims to provide a useRouter that is partially compatible with the Next.js useRouter, thereby guaranteeing an intermediate step in the migration of existing React Router-based applications to Next.js.

How to type-safe useRoute

Use this snippet in your app. (It's breaking to Type-level API compatibility from Next.js)

// Copy it in-your-app/useRouter.ts
import { useRouter as useNextCompatRouter } from '@fleur/froute'
export const useRouter: UseRouter = useNextCompatRouter

Usage:

// Route definition
const routes = {
  users: routeOf('/users/:id'),
}

// Typeing to `Routes`, it's free from circular dependency
export type Routes = typeof routes

// Component
import { useRouter } from './useRouter'
import { Routes } from './your-routes'

const Users = () => {
  const router = useRouter<typeof Routes['users']>()
  router.query.id // It infering to `string`.
}

Readme

Keywords

none

Package Sidebar

Install

npm i @fleur/froute

Weekly Downloads

229

Version

1.0.9

License

MIT

Unpacked Size

59 kB

Total Files

19

Last publish

Collaborators

  • ragg
  • geta6
  • fsubal1102
  • hanakla