|
1 | 1 | # react-use-fetch-factory |
2 | | -A factory pattern for react hooks to fetch data via redux and select via reselect |
| 2 | +A factory pattern for react hooks to abstract away your tedious data fetching and selecting |
| 3 | +logic. |
| 4 | + |
| 5 | +## Install |
| 6 | +``` |
| 7 | +npm i react-use-fetch-factory |
| 8 | +``` |
| 9 | +or |
| 10 | +``` |
| 11 | +yarn add react-use-fetch-factory |
| 12 | +``` |
| 13 | + |
| 14 | +## API |
| 15 | +The Hook takes three parameters. |
| 16 | +* `selector` The selector you are using to retrieve your data from the redux state. |
| 17 | +* `fetchActionCreator` An action creator that returns an action to trigger a fetch inside |
| 18 | +a saga, thunk, etc. |
| 19 | +* `emptyArrayAsFalsy` The Hook will check if there is data in the redux store. If there is |
| 20 | +data found, the fetch will not be executed. Only if the selector returns `null` or |
| 21 | +`undefined` the fetch action will be triggered. Setting this parameter to `true` will |
| 22 | +include an empty array as `falsy` and trigger the fetch action. |
| 23 | + |
| 24 | +## Usage |
| 25 | +The best usage is to build your own custom Hook which uses the `useFetchFactory` Hook, like so: |
| 26 | + |
| 27 | +```js |
| 28 | +import {useFetchFactory} from 'react-use-fetch-factory'; |
| 29 | +import {getProducts, fetchProducts} from '../ducks/products'; |
| 30 | + |
| 31 | +export default function useProducts(ids) { |
| 32 | + return useFetchFactory( |
| 33 | + getProducts, |
| 34 | + () => fetchProducts(ids), |
| 35 | + true |
| 36 | + ); |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +With you custom hook you can then import your selecting and fetching logic with one line: |
| 41 | +```jsx harmony |
| 42 | +import * as React from 'react'; |
| 43 | +import {useProducts} from '../hooks/fetching'; |
| 44 | + |
| 45 | +export function ProductsList(props) { |
| 46 | + const products = useProducts(props.productIds); |
| 47 | + |
| 48 | + return ( |
| 49 | + <List> |
| 50 | + {products.map((product) => { |
| 51 | + <ProductView key={product.id} {...product}/> |
| 52 | + })} |
| 53 | + </List> |
| 54 | + ); |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +Pretty neat, eh? |
| 59 | + |
| 60 | +## Motivation & How it works |
| 61 | +Hooks are a great way to make our components leaner. With the release of react-redux 7 we |
| 62 | +can finally use Hooks to provide our store to components. Previously we had to use `connect` to |
| 63 | +bind our components to the redux store which ended in a complicated boilerplate looking higher |
| 64 | +order component. This became particular annoying when dealing with selectors and data from |
| 65 | +the server, because all of this check, fetching and selecting usually happened inside the component |
| 66 | +code: |
| 67 | + |
| 68 | +```jsx harmony |
| 69 | +import * as React from 'react'; |
| 70 | +import {connect} from 'react-redux'; |
| 71 | +import {fetchTodos, toggleTodo, getTodosSelector} from '../ducks/todos'; |
| 72 | + |
| 73 | +class TodoApp extends React.Component { |
| 74 | + componentDidMount() { |
| 75 | + if(this.props.todos.length === 0) { |
| 76 | + this.props.fetchTodos(); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + render() { |
| 81 | + return ( |
| 82 | + <List> |
| 83 | + {this.props.todos.map((todo) => { |
| 84 | + <Todo key={todo.id} onToggle={() => toggleTodo(todo.id)}/> |
| 85 | + })} |
| 86 | + </List> |
| 87 | + ); |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +function mapStateToProps(state) { |
| 92 | + return { |
| 93 | + todos: getTodosSelector(state) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +function mapDispatchToProps() { |
| 98 | + return { |
| 99 | + fetchTodos, |
| 100 | + addTodo, |
| 101 | + toggleTodo, |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +export default connect( |
| 106 | + mapStateToProps, |
| 107 | + mapDispatchToProps |
| 108 | +)(TodoApp) |
| 109 | +``` |
| 110 | + |
| 111 | +This is a lot of code for just displaying a list of Todos. We want to check if there are |
| 112 | +todos when the component mounts and if not, we fetch them (e.g. via a saga, thunk or whatever you like) |
| 113 | +And not only is this a lot of code, but we'll also repeat ourselves over and over again the |
| 114 | +more components we have that just fetch some kind of data and display it. |
| 115 | + |
| 116 | +With hooks we can get leaner: |
| 117 | + |
| 118 | +```jsx harmony |
| 119 | +import React, {useEffect} from 'react'; |
| 120 | +import {useDispatch, useSelector} from 'react-redux'; |
| 121 | +import {fetchTodos, toggleTodo, getTodosSelector} from '../ducks/todos'; |
| 122 | + |
| 123 | +export function TodoApp() { |
| 124 | + const dispatch = useDispatch(); |
| 125 | + const todos = useSelector(getTodosSelector); |
| 126 | + |
| 127 | + useEffect(() => { |
| 128 | + if (todos.length === 0) { |
| 129 | + dispatch(fetchTodos()); |
| 130 | + } |
| 131 | + }, [dispatch, fetchTodos]); |
| 132 | + |
| 133 | + return ( |
| 134 | + <List> |
| 135 | + {todos.map((todo) => { |
| 136 | + <Todo key={todo.id} onToggle={() => dispatch(toggleTodo(todo.id))}/> |
| 137 | + })} |
| 138 | + </List> |
| 139 | + ); |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +Way cleaner. But we also will repeat ourselves in this case. The only thing that will change |
| 144 | +if we fetch different data is the selector, the dispatched fetching action and the ui |
| 145 | +representation. |
| 146 | +But wait. We can build our own Hooks! So let's abstract all this away: |
| 147 | + |
| 148 | +```jsx harmony |
| 149 | +import {useEffect} from 'react'; |
| 150 | +import {useDispatch, useSelector} from 'react-redux'; |
| 151 | + |
| 152 | +export default function useFetchFactory(selector, fetchActionCreator, emptyArrayAsFalsy) { |
| 153 | + const dispatch = useDispatch(); |
| 154 | + const data = useSelector(selector); |
| 155 | + |
| 156 | + useEffect(() => { |
| 157 | + if ( |
| 158 | + !data || |
| 159 | + (emptyArrayAsFalsy && data instanceof Array && data.length === 0) |
| 160 | + ) { |
| 161 | + dispatch(fetchActionCreator()); |
| 162 | + } |
| 163 | + }, [dispatch]); |
| 164 | + |
| 165 | + return data; |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +And there it is! Our own custom data fetching and selecting Hook! |
| 170 | + |
| 171 | +## Found an issue or bug? |
| 172 | +Please open a pull request or an issue and contribute. |
0 commit comments