npm package: @inventi/keep
Description: [![@inventi/keep](https://img.shields.io/npm/v/@inventi/keep.svg)]() [![@inventi/keep](https://img.shields.io/npm/dw/@inventi/keep.svg)]()
Author: Adam Bisek
Latest version: 1.12.1

# @inventi/keep 🇩🇪
[![@inventi/keep](https://img.shields.io/npm/v/@inventi/keep.svg)]()
[![@inventi/keep](https://img.shields.io/npm/dw/@inventi/keep.svg)]()

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:**
```tsx
import { useFetch, useMutate } from '@inventi/keep'

const DogListContainer = (): JSX.Element => {
  // 1. arg - 'DogList' - redux key
  // 2. and 3. arg - 'allDogs', ['name', 'image {publicUrl}'] - will be included to redux action as a field "args"
  // 4. (last) arg - [] - loads data only on mount
  const { isLoading, data, error, reload } = useFetch('DogList', 'allDogs', ['name', 'image {publicUrl}'], [])

  // you can use useFetch multiple times
  const { isLoading: isLoadingCats, data: catsData, error: errorCats } = useFetch('CatList', 'allCats', ['name', 'image {publicUrl}'], [])
  // this function "toDinosaur" receives arbitrary object as a first argument.
  // it will be included to redux action as a field "payload"
  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:**
```tsx
import { useFetch, useMutate } from '@inventi/keep'

const ExtrasExample = (): JSX.Element => {

    // ...
    // other states and such
    // ...

    const { isLoading: receiptIsLoading, data: receiptData, error: receiptError } = useFetch<Receipt>(
    'newInbound/receipt',
    'receipt',
    { id: receiptId },
    [],
    { skip: !receiptId, onSkipClear: true },
  )
}

```


**Redux middleware for making requests:**
```tsx
import { actions, actionTypes, FetchAction } from '@inventi/keep'

const middleware = (store: Store) => (next: (action: FetchAction) => void) => (action: FetchAction) => {
  if (action.type === actionTypes.fetchStart) {
    // your fetch function
    handleFetch(store.dispatch, action.promise, action.key, action.args)
  }
  if (action.type === actionTypes.mutateStart) {
    // your fetch function
    handleMutation(store.dispatch, action.promise, action.key, action.args, action.payload)
  }
  return next(action)
}
```


## Installation

**This library:**
```bash
yarn add @inventi/keep
```

**Peer dependencies (necessary):**
```bash
use-sse@^2.0.1 redux react-redux
```

**App root for Client**
```tsx
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**
```ts
// example for express server
import { serverRender } from '@inventi/keep'
import express from 'express'
import App from './src/App' // your React app

server
  .get('/*', async (req, res) => {
    const store = createStore()
    // create render function that will be called by keep
    const render = (WrappedApp: ComponentType): string => {
      return ReactDOMServer.renderToString(<WrappedApp />)
    }
    // wait for data loading and rendering app
    // you'll get HTML and initial data 
    const [html, serverDataHtml] = await serverRender(render, RegisteredApp)
    const reduxState = store.getState() // grab the initial state from our Redux store

    res.send(
      `<!doctype html>
    <html lang="">
    <head>
        ${serverDataHtml}
        <script>
          window.env = ${serialize(envConfig)};
          window.__REDUX_STATE__ = ${serialize(reduxState)};
        </script>
        ...
```


## API
``` javascript

// library components
import { 
  createClientProvider,
  serverRender,
  useFetch,
  useMutate,
  reducer,
  actions,
  actionTypes,
} from '@inventi/keep'
// typescript types
import  { FetchAction } from '@inventi/keep'

```

## Thanks
Big thanks belongs to [Inventi](https://pip.pypa.io/en/stable/) 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](https://choosealicense.com/licenses/mit/)