├── .eslintrc.js
├── .github
└── workflows
│ ├── publish.yml
│ └── verify.yml
├── .gitignore
├── CHANGELOG.md
├── README.md
├── babel.config.js
├── index.js
├── jest.config.js
├── package.json
├── src
├── __tests__
│ └── index.test.js
└── index.js
├── webpack.config.js
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'helloitsjoe',
3 | };
4 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 |
6 | jobs:
7 | publish:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-node@v3
12 | with:
13 | node-version: '16.16.x'
14 | registry-url: 'https://registry.npmjs.org'
15 | cache: yarn
16 | - run: yarn
17 | - run: npx helloitsjoe/release-toolkit publish
18 | env:
19 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/verify.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches-ignore:
4 | - main
5 |
6 | jobs:
7 | verify:
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | # Node 16.17 includes npm 8.15 which has a bug in npx
12 | node-version: [16.16.x, 18.x]
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: ${{ matrix.node-version }}
18 | cache: yarn
19 | - run: yarn
20 | - run: npx helloitsjoe/release-toolkit verify
21 | - run: yarn test
22 | - run: yarn lint
23 | - run: yarn coveralls
24 | env:
25 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | coverage
4 | dist
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [2.0.20](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.20) (2022-12-07)
2 |
3 | **Chore**
4 |
5 | - Bump decode-uri-component from 0.2.0 to 0.2.2
6 | - Bump loader-utils from 1.4.0 to 1.4.2
7 |
8 | ## [2.0.19](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.19) (2022-09-05)
9 |
10 | **Chore**
11 |
12 | - Include React 17 and 18 in `peerDependencies`
13 | - Remove `enzyme` and convert tests to React Testing Library
14 | - Upgrade dependencies
15 |
16 | ## [2.0.18](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.18) (2022-03-28)
17 |
18 | **Chore**
19 |
20 | Bump ansi-regex to 4.1.1
21 |
22 | ## [2.0.17](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.17) (2022-03-27)
23 |
24 | **Chore**
25 |
26 | Bump minimist to 1.2.6
27 |
28 | ## [2.0.16](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.16) (2021-09-26)
29 |
30 | **Chore**
31 |
32 | - Bump dependencies
33 |
34 | ## [2.0.15](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.15) (2021-05-30)
35 |
36 | **Chore**
37 |
38 | - Update deps
39 |
40 | ## [2.0.14](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.14) (2021-05-26)
41 |
42 | **Chore**
43 |
44 | - Use GitHub Actions
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-hooks-compose
2 |
3 | [](https://travis-ci.com/helloitsjoe/react-hooks-compose)
4 | [](https://coveralls.io/github/helloitsjoe/react-hooks-compose?branch=master)
5 | [](https://www.npmjs.com/package/react-hooks-compose)
6 |
7 | ## Installation
8 |
9 | ```
10 | npm i react-hooks-compose
11 | ```
12 |
13 | ## Why `react-hooks-compose`?
14 |
15 | `react-hooks-compose` provides an ergonomic way to decouple hooks from the components that use them.
16 |
17 | React Hooks are great. They encapsulate state logic and make it more reusable. But what if you have
18 | pure presentational components that you want to use with different state? What if you want to test
19 | your presentaional component in isolation?
20 |
21 | React Hooks invert the Container/Presenter pattern, putting the container inside the presenter. This
22 | makes it hard to use the same presentational component with different hooks, and clunky to test
23 | presentational components by themselves.
24 |
25 | One option:
26 |
27 | ```jsx
28 | import { Presenter } from './presenter';
29 | import { useCustomHook } from './hooks';
30 |
31 | const Wrapper = () => {
32 | const { foo, bar } = useCustomHook();
33 | return ;
34 | };
35 |
36 | export default Wrapper;
37 | ```
38 |
39 | This works fine, but you end up with an extra component just to connect the hook to the Presenter.
40 | If you want to test the presenter in isolation, you have to export it separately. there must be a
41 | better way!
42 |
43 | ## Basic Usage
44 |
45 | `composeHooks` passes values from hooks as props, and allows you to pass any other props as normal.
46 | This allows you to export the hook, stateful component, and purely presentational component
47 | separately.
48 |
49 | ```jsx
50 | import composeHooks from 'react-hooks-compose';
51 |
52 | const useForm = () => {
53 | const [name, setName] = useState('');
54 | const onChange = e => setName(e.target.value);
55 | return { name, onChange };
56 | };
57 |
58 | // Other props (in this case `icon`) can be passed in separately
59 | const FormPresenter = ({ name, onChange, icon }) => (
60 |
61 |
{icon}
62 |
Hello, {name}!
63 |
64 |
65 | );
66 |
67 | export default composeHooks({ useForm })(FormPresenter);
68 | ```
69 |
70 | You can think of `composeHooks` like `react-redux`'s `connect` HOC. For one thing, it creates an
71 | implicit container. You can think of the object passed into `composeHooks` as `mapHooksToProps`,
72 | similar to
73 | [the object form of `mapDispatchToProps`](https://daveceddia.com/redux-mapdispatchtoprops-object-form/).
74 |
75 | ### Compose multiple hooks:
76 |
77 | ```js
78 | const Presenter = ({ name, onChange, foo, bar, value }) => (
79 |
80 |
Hello, {name}!
81 |
Context value is {value}
82 |
83 | foo is {foo}, bar is {bar}
84 |
85 |
86 |
87 | );
88 |
89 | export default composeHooks({
90 | useForm,
91 | useFooBar,
92 | value: () => useContext(MyContext), // Usage with `useContext`
93 | })(FormPresenter);
94 | ```
95 |
96 | ### Usage with `useState`
97 |
98 | If you compose with `useState` directly (i.e. the prop is an array), the prop will remain an array
99 | and should be destructured before use:
100 |
101 | ```jsx
102 | const FormPresenter = ({ nameState: [name, setName] }) => (
103 |