https://github.com/vydia/react-loading-switch
React component API for easily composing the render logic surrounding react-apollo data fetching, loading, and error handling.
https://github.com/vydia/react-loading-switch
Last synced: about 1 year ago
JSON representation
React component API for easily composing the render logic surrounding react-apollo data fetching, loading, and error handling.
- Host: GitHub
- URL: https://github.com/vydia/react-loading-switch
- Owner: Vydia
- License: mit
- Created: 2018-04-23T02:27:06.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2018-05-06T14:53:52.000Z (about 8 years ago)
- Last Synced: 2024-04-26T10:44:55.765Z (about 2 years ago)
- Language: JavaScript
- Size: 82 KB
- Stars: 7
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
react-loading-switch πΆ
==
React component API for easily composing the render logic surrounding react-apollo data fetching, loading, and error handling.
Compatible with React, React Native, React Web, React anything!
Getting Started
--
```shell
npm i --save react-loading-switch
```
Why?
--
### Data-related conditional rendering code mucking up our render functions
In our experience, re-writing identical or similar logic in every component can lead to problems β
- Multiple programming styles result in different-looking code.
- Difficult to digest at a glance.
- Easy to make a mistake if hard-coding everywhere.
- These problems grow as the codebase grows.
- Wasted brain cycles thinking about it, writing it, reviewing it.
#### Say goodbye to `if (loading)` and `if (error)` π
With react-loading-switch, we won't need this:
```js
const Puppy = ({ loading, error, puppy }) => {
if (error) {
return
}
if (!puppy) {
if (loading) {
return
}
return
}
return (
{ `Finally the puppy is here! ${puppy.id}` }
)
}
```
We won't need this:
```js
const Puppy = ({ loading, error, puppy }) => {
if (loading) return
if (error) return
return { `Finally the puppy is here! ${puppy.id}` }
}
```
### Instead, compose this logic with `react-loading-switch` β
- Consistent JSX component API.
- Easy to digest at a glance.
- Extensible & Functional
- Optionally centralize a shared configuration across many components.
- It's just a react component. Wrap it with some default props and export.
#### Hello `` π»
This example uses all available props, but in practice it gets cleaner:
```js
import LoadingSwitch from 'react-loading-switch'
const Puppy = ({ loading, error, puppy }) => (
new Error('Missing puppy data!')}
loading={loading}
renderError={(error) => }
renderLoading={() => }
require={puppy}
>
{ () => (
{ `The puppy data is here! ${puppy.id}` }
) }
)
```
### DRY it up by wrapping with some default props π€
Share identical behavior across similar components π©βπ¦βπ¦
```js
import LoadingSwitch from 'react-loading-switch'
export const PuppyLoadingSwitch = (props) => (
new Error('Could not find puppy!')}
renderLoading={() =>
Loading puppies...
}
renderError={(error) => Error: {error.message}
}
{...props}
/>
)
```
#### Use `` in every component that shares this logic
Now we're talkin' π
```js
import PuppyLoadingSwitch from '../PuppyLoadingSwitch'
const Puppy = ({ loading, error, puppy }) => (
{ () => (
{ `The puppy data is here! ${puppy.id}` }
) }
)
```
You can use one LoadingSwitch component for your entire application, or you can
use different LoadingSwitches in different areas. It's up to you!
### The function-child prop / child-render prop receives the value of `require`
This optional feature allows us to avoid long property lookup chains in JSX.
Compare the below to the above. Notice the lack of `data.puppy.whatever`
```js
const PuppyBirthday = ({ loading, error, data}) => (
{ ({ name, birthday }) => (
{ `${name}'s birthday is ${birthday}!` }
) }
)
```
### With React-Apollo `` components
```js
import PuppyLoadingSwitch from '../PuppyLoadingSwitch'
import { Query } from 'react-apollo'
const GET_PUPPY = gql`
query puppy($puppyId: ID!) {
puppy(id: $puppyId) {
id
name
birthday
}
}
`;
const PuppyBirthday = ({ puppyId }) => (
{({ loading, error, data}) => (
{ ({ name, birthday }) => (
{ `${name}'s birthday is ${birthday}!` }
) }
)}
)
```
### Versitile `require` prop uses JavaScript truthy/falsey checking.
Falsey in JavaScript: `false || null || undefined || 0 || '' || NaN`
```js
const Puppy = ({ loading, error, someData, moreData }) => (
{ () => (
{ moreData.foo.name }
) }
)
```
API
--
See the [test/](test/) directory in this repo for detailed snapshot tests that cover the whole API.
React-Apollo fetch policies
--
Most of the [React-Apollo example apps](https://github.com/apollographql/react-apollo/blob/d57f25237c69f78a7e52b586d2303844baf2d4e0/examples/ssr/imports/app.js#L25-L43) use [this pattern](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-loading), where loading takes precedence:
```js
export const Character = withCharacter(({ loading, hero, error }) => {
if (loading) return
Loading;
// ...
```
### `fetchPolicy: 'cache-and-network'` conflicts with the above
Excerpt from the [`apollo-client` README](https://github.com/apollographql/apollo-client/blob/9ebbb61fb1061f56edf0bedb965c9954f75afead/docs/source/api/react-apollo.md#dataloading)
> However, just because `data.loading` is true it does not mean that you wonβt have data. For instance, if you already have `data.todos`, but you want to get the latest todos from your API `data.loading` might be true, but you will still have the todos from your previous request.
tl;dr we might still want to render the data we have, even if `loading === true`.
### ReactLoadingSwitch considers `data` before `loading`
As long as there is no `error`, and `require` is truthy, it renders its `children`; even if `loading === true`. Now we can safely use the `cache-and-network` fetch-policy with no chance of seeing a loading state when we have data we could be rendering.
From [`src/LoadingSwitch.js`](https://github.com/Vydia/react-loading-switch/blob/0e87d845f129cba525d44c4162d4b2305a2826fd/src/LoadingSwitch.js#L55-L67)
```js
if (error) {
return renderError(error)
}
if (!require) {
if (loading) {
return renderLoading()
}
if (errorWhenMissing) {
return renderError(typeof errorWhenMissing === 'function' ? errorWhenMissing() : errorWhenMissing)
}
}
return children(require)
```
#### However, it's easy to revert to the classic example behavior
In this example, `renderLoading` will be rendered if `loading` is truthy, even if we have some other data:
```js
require={!loading && puppy}
```
Now when `loading` is truthy `require` evaluates falsey.
```js
import PuppyLoadingSwitch from '../PuppyLoadingSwitch'
const Puppy = ({ loading, error, puppy }) => (
{ () => (
{ `We are not loading and the puppy data is here! ${puppy.id}` }
) }
)
```
Or, if we only care about `loading` and `error`, we don't need to check for data presence:
```js
import PuppyLoadingSwitch from '../PuppyLoadingSwitch'
const Puppy = ({ loading, error, puppy }) => (
{ () => (
{ `We are not loading! ${puppy.id}` }
) }
)
```