Skip to content

Commit e445834

Browse files
committed
Merging async-data into this package
1 parent 5400e2f commit e445834

9 files changed

Lines changed: 223 additions & 5 deletions

File tree

readme.md

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ This package includes different tools that support you with common tasks.
88
- [Usage](#usage)
99
- [Build & Publish](#build--publish)
1010
- [useInterval](#useinterval)
11+
- [Async Data](#async-data)
1112
- [Information](#information)
1213

1314
## Usage
1415

1516
First, you have to install the package:
1617

1718
```bash
18-
npm install @aboutbits/react-tooling
19+
npm install @aboutbits/react-toolbox
1920
```
2021

2122
Second, you can make use of the different tools.
@@ -29,9 +30,9 @@ The hook takes two parameters:
2930
- `callback`: The callback function that should be executed.
3031
- `delay`: The delay in milliseconds or null, if the interval should be paused.
3132

32-
```jsx
33+
```tsx
3334
import React, { useState } from 'react'
34-
import { useInterval } from '@aboutbits/react-tooling'
35+
import { useInterval } from '@aboutbits/react-toolbox'
3536

3637
const MyCommponent = () => {
3738
const [step, setStep] = useState(10)
@@ -44,6 +45,72 @@ const MyCommponent = () => {
4445
}
4546
```
4647

48+
### Async Data
49+
50+
This part includes a utility component, that can be used to render loading, success and error views based on async state.
51+
52+
```tsx
53+
import React, { useEffect } from 'react'
54+
import { AsyncView } from '@aboutbits/react-toolbox'
55+
56+
type Data = {
57+
greeting: string
58+
}
59+
60+
type Error = {
61+
message: string
62+
}
63+
64+
const MyCommponent = () => {
65+
const [data, setData] = useState<Data | undefined>()
66+
const [error, setError] = useState<Error | undefined>()
67+
68+
useEffect(() => {
69+
fetch('https://jsonplaceholder.typicode.com/todos/1')
70+
.then(response => setData(response.json()))
71+
.catch(error => setError(error))
72+
})
73+
74+
return (
75+
<AsyncView
76+
data={data}
77+
error={error}
78+
renderLoading={<div>Loading</div>}
79+
renderSuccess={(data) => <div>{data.greeting}</div>}
80+
renderError={(error) => <div>{error.message}</div>} />
81+
);
82+
}
83+
```
84+
85+
And using SWR:
86+
87+
```tsx
88+
import React, { useEffect } from 'react'
89+
import { useSWR } from 'swr'
90+
import { AsyncView } from '@aboutbits/react-toolbox'
91+
92+
type Data = {
93+
greeting: string
94+
}
95+
96+
type Error = {
97+
message: string
98+
}
99+
100+
const MyCommponent = () => {
101+
const { data, error } = useSWR('https://jsonplaceholder.typicode.com/todos/1')
102+
103+
return (
104+
<AsyncView
105+
data={data}
106+
error={error}
107+
renderLoading={'Loading'}
108+
renderSuccess={'Success'}
109+
renderError={'Error'} />
110+
);
111+
}
112+
```
113+
47114
## Build & Publish
48115

49116
To publish the package commit all changes and push them to main. Then run one of the following commands locally:
@@ -64,6 +131,7 @@ For support, please contact [info@aboutbits.it](mailto:info@aboutbits.it).
64131

65132
### Credits
66133

134+
- [Martin Malfertheiner](https://github.com/mmalfertheiner)
67135
- [Alex Lanz](https://github.com/alexlanz)
68136
- [All Contributors](../../contributors)
69137

src/async-data/AsyncView.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react'
2+
import { isFunction } from '../util'
3+
import { AsyncState } from '../index'
4+
import { getAsyncState } from './asyncState'
5+
6+
type LoadingFunction = () => React.ReactElement<any, any> | null
7+
type SuccessFunction<Data> = (data: Data) => React.ReactElement<any, any> | null
8+
type ErrorFunction<Error> = (
9+
error: Error
10+
) => React.ReactElement<any, any> | null
11+
12+
type Props<Data, Error> = {
13+
data?: Data
14+
error?: Error
15+
renderLoading?: React.ReactNode | LoadingFunction
16+
renderSuccess: React.ReactNode | SuccessFunction<Data>
17+
renderError?: React.ReactNode | ErrorFunction<Error>
18+
}
19+
20+
const AsyncView = <Data, Error>(
21+
props: Props<Data, Error>
22+
): React.ReactElement<any, any> | null => {
23+
const {
24+
data,
25+
error,
26+
renderLoading = null,
27+
renderSuccess,
28+
renderError = null,
29+
} = props
30+
const asyncState = getAsyncState(data, error)
31+
32+
switch (asyncState) {
33+
case AsyncState.FETCHING:
34+
return isFunction(renderLoading) ? renderLoading() : <>{renderLoading}</>
35+
case AsyncState.FINISHED_WITH_SUCCESS:
36+
return isFunction(renderSuccess) ? (
37+
renderSuccess(data as Data)
38+
) : (
39+
<>{renderSuccess}</>
40+
)
41+
case AsyncState.FINISHED_WITH_ERROR:
42+
return isFunction(renderError) ? renderError(error) : <>{renderError}</>
43+
}
44+
}
45+
46+
export { AsyncView }
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react'
2+
import { render, RenderResult } from '@testing-library/react'
3+
import { AsyncView } from '../AsyncView'
4+
5+
type Data = {
6+
greeting: string
7+
}
8+
9+
type Error = {
10+
message: string
11+
}
12+
13+
const Loading: React.FC = () => {
14+
return <>Loading</>
15+
}
16+
17+
const Success: React.FC<{ data: Data }> = ({ data }) => {
18+
return <>{data.greeting}</>
19+
}
20+
21+
const Error: React.FC<{ error: Error }> = ({ error }) => {
22+
return <>{error.message}</>
23+
}
24+
25+
function renderAsyncView(data?: Data, error?: Error): RenderResult {
26+
return render(
27+
<AsyncView<Data, Error>
28+
data={data}
29+
error={error}
30+
renderLoading={<Loading />}
31+
renderSuccess={(data) => <Success data={data} />}
32+
renderError={(error) => <Error error={error} />}
33+
/>
34+
)
35+
}
36+
37+
test('should render loading if asyncState is loading for the first time', function () {
38+
const { getByText } = renderAsyncView()
39+
expect(getByText(/loading/i)).toBeInTheDocument()
40+
})
41+
42+
test('should render success if asyncState is successful', function () {
43+
const { getByText } = renderAsyncView({ greeting: 'Hello' })
44+
expect(getByText(/hello/i)).toBeInTheDocument()
45+
})
46+
47+
test('should render error if asyncState is error', function () {
48+
const { getByText } = renderAsyncView(undefined, { message: 'Error' })
49+
expect(getByText(/error/i)).toBeInTheDocument()
50+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { AsyncState, getAsyncState } from '../asyncState'
2+
3+
test('given no data and no error, than it should return fetching state', function () {
4+
const result = getAsyncState(null, null)
5+
6+
expect(result).toBe(AsyncState.FETCHING)
7+
})
8+
9+
test('given data and no error, than it should return success state', function () {
10+
const data = { greeting: 'Hello World!' }
11+
const result = getAsyncState(data, null)
12+
13+
expect(result).toBe(AsyncState.FINISHED_WITH_SUCCESS)
14+
})
15+
16+
test('given no data, but an error, than it should return error state', function () {
17+
const result = getAsyncState(null, 'Error')
18+
19+
expect(result).toBe(AsyncState.FINISHED_WITH_ERROR)
20+
})
21+
22+
test('given data and an error, than it should return error state', function () {
23+
const result = getAsyncState('Some Data', 'Error')
24+
25+
expect(result).toBe(AsyncState.FINISHED_WITH_ERROR)
26+
})
27+
28+
test('given empty string as data, than it should return success state', function () {
29+
const result = getAsyncState('', null)
30+
31+
expect(result).toBe(AsyncState.FINISHED_WITH_SUCCESS)
32+
})

src/async-data/asyncState.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
enum AsyncState {
2+
FETCHING,
3+
FINISHED_WITH_ERROR,
4+
FINISHED_WITH_SUCCESS,
5+
}
6+
7+
const getAsyncState = (data: unknown, error: unknown): AsyncState => {
8+
if (error != null && error != undefined) {
9+
return AsyncState.FINISHED_WITH_ERROR
10+
} else if (data != null && data != undefined) {
11+
return AsyncState.FINISHED_WITH_SUCCESS
12+
} else {
13+
return AsyncState.FETCHING
14+
}
15+
}
16+
17+
export { AsyncState, getAsyncState }

src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
import { useInterval } from './useInterval'
1+
import { useInterval } from './useInterval/useInterval'
2+
import { AsyncState, getAsyncState } from './async-data/asyncState'
3+
import { AsyncView } from './async-data/AsyncView'
24

3-
export { useInterval }
5+
export { useInterval, AsyncState, getAsyncState, AsyncView }
File renamed without changes.

src/util.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// eslint-disable-next-line @typescript-eslint/ban-types
2+
export const isFunction = (obj: unknown): obj is Function =>
3+
typeof obj === 'function'

0 commit comments

Comments
 (0)