- Only 600B minified and gziped
- Simple hooks API
- TypeScript
- Can handle updates
- Simple cache
- Suspense on server side via
react-ssr-prepass
π
This is a NO BULLSHIT hook: just PLUG IT in your components, get ALL THE DATA you need (and some more) both CLIENT- and SERVER-side, HYDRATE that
bastardapp while SSRing like it's NO BIG DEAL, effortlessly PASS IT to the client and render THE SHIT out of it
$ npm i -S react-universal-data
$ yarn add react-universal-data
Requests data and preserves the result to the state.
type useFetchData<T> = (
// async function that can return any type of data
fetcher: (key: string, context: { isServer: boolean }) => Promise<T>,
// unique key that will be used for storing & hydrating data while SSR
key: string,
// use cached value for specified duration of time, by default it will be requested each time
ttl?: number
) => AsyncState<T>
β οΈ Thekey
must be unique for the whole application.
Returned object can be in 4 different forms β depending on the promise's state.
export type AsyncState<T> =
// initial
| { isReady: false; isLoading: false; error: null; result: undefined }
// fulfilled
| { isReady: true; isLoading: false; error: null; result: T }
// pending
| { isReady: boolean; isLoading: true; error: Error | null; result?: T }
// rejected
| { isReady: false; isLoading: false; error: Error; result?: T }
π Fetch a sample post via jsonplaceholder.typicode.com API
import React from 'react'
import { useFetchData } from 'react-universal-data'
const fetchPost = (id) =>
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((response) => response.json())
function Post({ id }) {
const { isReady, isLoading, result, error } = useFetchData(fetchPost, id)
if (isLoading) {
return <p>Loading...</p>
}
if (error) {
return <p>Oh no: {error.message}</p>
}
// You can depend on `isReady` flag to ensure data loaded correctly
if (isReady) {
return (
<article>
<h2>{result.title}</h2>
<p>{result.body}</p>
</article>
)
}
return null
}
As the hook depends on the fetcher
function identity to be stable, please, wrap it inside useCallback
or define it outside of the render function to prevent infinite updates.
import React, { useCallback } from 'react'
import { useFetchData } from 'react-universal-data'
function UserPosts({ userId }) {
const fetchPosts = useCallback(() => (
fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
.then((response) => response.json())
), [userId]) // will pereform update if value changed
const { result = [] } = useFetchData(fetchPosts, 'user-posts')
return (
<ul>
{result.map((post) => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
π Create a custom hook for it
import React, { useCallback } from 'react'
import { useFetchData } from 'react-universal-data'
function useFetchUserPosts(userId) {
return useFetchData(
useCallback(() => (
fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
.then((response) => response.json())
), [userId]),
'user-posts'
)
}
function UserPosts({ userId }) {
const { result = [] } = useFetchUserPosts(userId)
return (
<ul>
{result.map((post) => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
Handles useFetchData
on server side and gathers results for hydration in the browser.
type getInitialData = (element: JSX.Element) => Promise<[string, any][]>
// server.js
import React from 'react'
import { renderToString } from 'react-dom/server'
import { getInitialData } from 'react-universal-data'
import { App } from './App'
async function server(req, res) {
const element = <App />
const data = await getInitialData(element).catch((error) => /* handle error */)
const html = renderToString(
<html>
<body>
<div id='app'>{element}</div>
<script
dangerouslySetInnerHTML={{
__html: `window._ssr = ${JSON.stringify(data)};`,
}}
/>
<script src='/client.js' />
</body>
</html>
)
res.write('<!DOCTYPE html>')
res.write(html)
res.end()
}
Hydrates initial data gathered with getInitialData
before rendering the app in the browser.
type hydrateInitialData = (initial: [string, any][]) => void
// client.js
import React from 'react'
import ReactDOM from 'react-dom'
import { hydrateInitialData } from 'react-universal-data'
import { App } from './App'
hydrateInitialData(window._ssr || [])
ReactDOM.hydrate(<App />, document.getElementById('app'))
react-ssr-prepass
- server-side dependencyya-fetch
- a lightweight wrapper aroundfetch
MIT Β© John Grishin