Skip to content

asyncink/react-toolkit

Repository files navigation

React Toolkit

Toolkit for server-side rendered route based apps built with React and React Router

Install

npm install @rambler-tech/react-toolkit

or

yarn add @rambler-tech/react-toolkit

Usage

Base example

// src/routes.ts
import {lazy} from '@rambler-tech/react-toolkit/client'

const MainPage = lazy(() => import('./pages/main'))
const AboutPage = lazy(() => import('./pages/about'))

export const routes = [
  {
    path: '/',
    component: MainPage
  },
  {
    path: '/about',
    component: AboutPage
  }
]
// src/server.ts
import express from 'express'
import {renderToStream} from '@rambler-tech/react-toolkit/server'
import {routes} from './routes'

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST)

const server = express()

server
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    try {
      const {css: styles, js: scripts} = assets.client

      await renderToStream({
        req,
        res,
        routes,
        styles,
        scripts
      })
    } catch (error) {
      console.error(error)
    }
  })

export default server
// src/client.ts
import {hydrateFromStream} from '@rambler-tech/react-toolkit/client'
import {routes} from './routes'

hydrateFromStream({routes})

Data fetching

import React from 'react'
import {PageComponent} from '@rambler-tech/react-toolkit/client'
import {api} from './api'

export interface MainPageProps {
  someProp: any
}

const MainPage: PageComponent<MainPageProps> = ({someProp}) => (
  <div>
    <h1>Main page</h1>
    <p>{someProp}</p>
  </div>
)

MainPage.getInitialData = async () => {
  const {someProp} = await api.getSomeProp()

  return {someProp}
}

export default MainPage

Pages meta data

import React from 'react'
import {PageComponent} from '@rambler-tech/react-toolkit/client'

const MainPage: PageComponent = () => (
  <div>
    <h1>Main page</h1>
    <p>...</p>
  </div>
)

MainPage.getMetaData = () => ({
  title: 'Main page',
  description: '...'
})

export default MainPage

Redirects

import React from 'react'
import {PageComponent} from '@rambler-tech/react-toolkit/client'
import {api} from './api'

export interface MainPageProps {
  someProp: any
}

const MainPage: PageComponent<MainPageProps> = ({someProp}) => (
  <div>
    <h1>Main page</h1>
    <p>{someProp}</p>
  </div>
)

MainPage.getInitialData = async () => {
  const user = await api.getCurrentUser()

  if (!user) {
    return {
      redirect: '/login'
    }
  }
}

export default MainPage

Custom Layout

For adding your own providers, markup and styles on top of pages and routing

// src/layout.tsx
import React, {FC, ReactNode} from 'react'
import {ThemeProvider} from 'awesome-ui'
import styles from './styles.module.css'

export interface MyLayoutProps {
  children: ReactNode
}

export const MyLayout: FC<MyLayoutProps> = ({children}) => {
  return (
    <ThemeProvider>
      <main className={styles.main}>{children}</main>
    </ThemeProvider>
  )
}
// src/server.ts
import express from 'express'
import {renderToStream} from '@rambler-tech/react-toolkit/server'
import {routes} from './routes'
import {MyLayout} from './layout'

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST)

const server = express()

server
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    try {
      const {css: styles, js: scripts} = assets.client

      await renderToStream({
        req,
        res,
        routes,
        styles,
        scripts,
        Layout: MyLayout
      })
    } catch (error) {
      console.error(error)
    }
  })

export default server
// src/client.ts
import {hydrateFromStream} from '@rambler-tech/react-toolkit/client'
import {routes} from './routes'
import {MyLayout} from './layout'

hydrateFromStream({
  routes,
  Layout: MyLayout
})

Custom Document

For adding custom styles, scripts, meta tags and for more flexible customization of the entire document

// src/document.tsx
import React, {FC, ReactNode} from 'react'
import {Meta, Preloads, Styles, Scripts, State} from '@rambler-tech/react-toolkit/client'

export interface MyDocumentProps {
  children: ReactNode
}

export const MyDocument: FC<MyDocumentProps> = ({children}) => (
  <html lang="ru">
    <head>
      <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
      <meta charSet="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <Meta />
      <link rel="manifest" href="/manifest.json" />
      <Preloads />
      <link
        rel="preconnect"
        href="https://mc.yandex.ru"
        crossOrigin="anonymous"
      />
      <Styles />
    </head>
    <body>
      {children}
      <State />
      <Scripts />
      <script src="https://vp.rambler.ru/player/sdk.js" async />
    </body>
  </html>
)
// src/server.ts
import express from 'express'
import {renderToStream} from '@rambler-tech/react-toolkit/server'
import {routes} from './routes'
import {MyDocument} from './document'

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST)

const server = express()

server
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    try {
      const {css: styles, js: scripts} = assets.client

      await renderToStream({
        req,
        res,
        routes,
        styles,
        scripts,
        Document: MyDocument
      })
    } catch (error) {
      console.error(error)
    }
  })

export default server
// src/client.ts
import {hydrateFromStream} from '@rambler-tech/react-toolkit/client'
import {routes} from './routes'
import {MyDocument} from './document'

hydrateFromStream({
  routes,
  Document: MyDocument
})

Redux example

// src/server.ts
import express from 'express'
import {renderToStream} from '@rambler-tech/react-toolkit/server'
import {routes} from './routes'
import {MyLayout} from './layout'
import {createStore} from './store'

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST)

const server = express()

server
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    try {
      const {css: styles, js: scripts} = assets.client

      const store = createStore()

      await renderToStream({
        req,
        res,
        routes,
        styles,
        scripts,
        Layout: MyLayout,
        store
      })
    } catch (error) {
      console.error(error)
    }
  })

export default server
// src/client.ts
import {getState, hydrateFromStream} from '@rambler-tech/react-toolkit/client'
import {routes} from './routes'
import {MyLayout} from './layout'
import {createStore} from './store'

const initialState = getState('INITIAL_STATE')
const store = createStore(initialState)

hydrateFromStream({
  routes,
  Layout: MyLayout,
  store
})
// src/layout.tsx
import React, {FC, ReactNode} from 'react'
import {Provider} from 'react-redux'
import {useAppContext, State} from '@rambler-tech/react-toolkit/client'

export interface MyLayoutProps {
  children: ReactNode
}

export const MyLayout: FC<MyLayoutProps> = ({children}) => {
  const {store} = useAppContext()

  return (
    <>
      <Provider store={store}>{children}</Provider>
      <State name="INITIAL_STATE" state={store.getState()} />
    </>
  )
}
// src/pages/main.tsx
import React from 'react'
import {PageComponent} from '@rambler-tech/react-toolkit/client'
import {fetchInitialData} from './actions'

const MainPage: PageComponent = () => (
  <div>
    <h1>Main page</h1>
    <p>...</p>
  </div>
)

MainPage.getInitialData = async ({store}) => {
  await store.dispatch(fetchInitialData())
}

export default MainPage

License

MIT

About

Toolkit for server-side rendered route based apps built with React and React Router

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages