@inventi/keep 🇩🇪

This library is a foundation stone for data loading in your React App.
- âś… API architecture agnostic (you can use REST, GraphQL or even smoke signals)
- âś… Hooks
- âś… All actions in redux
- âś… SSR fully supported
- âś… Typescript types included
Developer prerequisites
- Basic React, Redux and Redux Saga knowledge
The principle aka how it works
Basically in every app you can do two actions with data:
- ⬇️ load data
- ↪️ trigger an action, called "mutation" (name borrowed from GraphQL)
This library will give you:
- Redux actions/reducer/selectors for loading and mutations
- Hooks for connection your app with data and request loading in component
- Context Provider for client and server
This library doesn't include any support for data loading. Your job is to listen for redux actions and make API requests as sideffect!
Usage
useFetch(...args):
First argument
- key to your redux store
- will determine what you have to listen for in your sagas
Second, third, fourth ... args (beween first and last)
- whatever you want to pass, usually used to add payload
- will be included in redux action as a field "args"
Last argument
- array of dependencies, think of it the same way as React.useEffect's dependencies ... when should data be reloaded?
- eg. an empty array -
[ ] will result in loading data just once
Very last argument (optional)
- options object: { skip, onSkipClear }
Return value
- { isLoading, data, error, reload }
Example:
import { useFetch, useMutate } from '@inventi/keep'
const DogListContainer = (): JSX.Element => {
const { isLoading, data, error, reload } = useFetch('DogList', 'allDogs', ['name', 'image {publicUrl}'], [])
const { isLoading: isLoadingCats, data: catsData, error: errorCats } = useFetch('CatList', 'allCats', ['name', 'image {publicUrl}'], [])
const toDinosaur = useMutate('DogList', 'DogtoDinosaur', ['T-Rex'])
return <DogList isLoading={isLoading} list={data} error={error} onClick={toDinosaur} />
}
Extra:
- You can specify the type of data with generics -
useFetch<DataType>(...)
- You can also add extra options after dependencies argument -
useFetch<DataType>(... , [], {skip: true, onSkipClear: false})
skip - determines whether fetch should be skipped.
onSkipClear - whether data in Redux should be cleared after skip
Example:
import { useFetch, useMutate } from '@inventi/keep'
const ExtrasExample = (): JSX.Element => {
const { isLoading: receiptIsLoading, data: receiptData, error: receiptError } = useFetch<Receipt>(
'newInbound/receipt',
'receipt',
{ id: receiptId },
[],
{ skip: !receiptId, onSkipClear: true },
)
}
Redux middleware for making requests:
import { actions, actionTypes, FetchAction } from '@inventi/keep'
const middleware = (store: Store) => (next: (action: FetchAction) => void) => (action: FetchAction) => {
if (action.type === actionTypes.fetchStart) {
handleFetch(store.dispatch, action.promise, action.key, action.args)
}
if (action.type === actionTypes.mutateStart) {
handleMutation(store.dispatch, action.promise, action.key, action.args, action.payload)
}
return next(action)
}
Installation
This library:
yarn add @inventi/keep
Peer dependencies (necessary):
use-sse@^2.0.1 redux react-redux
App root for Client
import { createClientProvider } from '@inventi/keep'
import { Store } from 'redux'
import BareApp from './BareApp'
interface Props {
store: Store
}
const KeepClientProvider = createClientProvider()
function App({ store }: Props) {
return (
<KeepClientProvider>
<ReduxProvider store={store}>
<BareApp /> {/** this is your app */}
</ReduxProvider>
</KeepClientProvider>
)
}
export default App
Rendering on server
import { serverRender } from '@inventi/keep'
import express from 'express'
import App from './src/App'
server
.get('/*', async (req, res) => {
const store = createStore()
const render = (WrappedApp: ComponentType): string => {
return ReactDOMServer.renderToString(<WrappedApp />)
}
const [html, serverDataHtml] = await serverRender(render, RegisteredApp)
const reduxState = store.getState()
res.send(
`<!doctype html>
<html lang="">
<head>
${serverDataHtml}
<script>
window.env = ${serialize(envConfig)};
window.__REDUX_STATE__ = ${serialize(reduxState)};
</script>
...
API
import {
createClientProvider,
serverRender,
useFetch,
useMutate,
reducer,
actions,
actionTypes,
} from '@inventi/keep'
import { FetchAction } from '@inventi/keep'
Thanks
Big thanks belongs to Inventi for giving us a opportunity to develop part of this open-source package as side-effect of one secret project
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
License
MIT