eslint-plugin-use-encapsulation
Version:
An ESLint rule to encourage using custom hook abstractions
216 lines (173 loc) • 4.94 kB
Markdown
not allow using the hooks provided by the React library directly inside a component. They can only be used by custom hooks, encouraging the use of custom hooks in your components. The abstraction of a custom hook follows the "useEncapsulation" pattern for React Hooks: [https://kyleshevlin.com/use-encapsulation](https://kyleshevlin.com/use-encapsulation)
Install the plugin: `npm install --save-dev eslint-plugin-use-encapsulation` or `yarn add -D eslint-plugin-use-encapsulation`.
```js
import eslintPluginUseEncapsulation from 'eslint-plugin-use-encapsulation'
export default [
{
plugins: {
'use-encapsulation': eslintPluginUseEncapsulation,
},
rules: {
'use-encapsulation/prefer-custom-hooks': [
'error',
{
allow: ['useMemo'], // optional
block: ['useMyCustomHook'], // optional
},
],
},
},
]
```
```json
{
"plugins": ["use-encapsulation"],
"rules": {
"use-encapsulation/prefer-custom-hooks": [
"error",
{
"allow": ["useMemo"], // optional
"block": ["useMyCustomHook"] // optional
}
]
}
}
```
Here we are using React Hooks directly inside a component with no custom hook abstraction.
```jsx
function Counter() {
const [state, setState] = React.useState(0)
const inc = React.useCallback(() => {
setState(s => s + 1)
})
const dec = React.useCallback(() => {
setState(s => s - 1)
})
const reset = React.useCallback(() => {
setState(0)
})
return (
<div>
<div>Count: {state}</div>
<div>
<button type="button" onClick={inc}>
+
</button>
<button type="button" onClick={dec}>
-
</button>
<button type="button" onClick={reset}>
reset
</button>
</div>
</div>
)
}
```
Here we abstract the functionality into a custom hook, encapsulating the concerns of `state` and its `handlers` together.
```jsx
function useCounter(initialState = 0) {
const [state, setState] = React.useState(initialState)
const handlers = React.useMemo(
() => ({
inc: () => {
setState(s => s + 1)
},
dec: () => {
setState(s => s - 1)
},
reset: () => {
setState(initialState)
},
}),
[ ]
)
return [state, handlers]
}
function Counter() {
const [state, { inc, dec, reset }] = useCounter()
return (
<div>
<div>Count: {state}</div>
<div>
<button type="button" onClick={inc}>
+
</button>
<button type="button" onClick={dec}>
-
</button>
<button type="button" onClick={reset}>
reset
</button>
</div>
</div>
)
}
```
```javascript
function MyComponent() {
React.useEffect(() => {})
return null
}
function MyComponent() {
useEffect(() => {})
return null
}
const MyComponent = () => {
React.useEffect(() => {})
return null
}
const MyComponent = () => {
useEffect(() => {})
return null
}
```
```javascript
function useMyCustomHook() { React.useEffect(() => {})) }
function useMyCustomHook() { useEffect(() => {})) }
const useMyCustomHook = () => { React.useEffect(() => {})) }
const useMyCustomHook = () => { useEffect(() => {})) }
function MyComponent() { useMyCustomHook(); return null }
const MyComponent = () => { useMyCustomHook(); return null }
```
There are two options for `prefer-custom-hooks`: an `allow` list, and a `block` list.
While it is not recommended, the `allow` list is an array of React hooks that will be exempted from triggering the rule. For example, you may want to allow `useMemo` to be used directly in components. You can set that up like so:
```json
{
"plugins": ["use-encapsulation"],
"rules": {
"use-encapsulation/prefer-custom-hooks": ["error", { "allow": ["useMemo"] }]
}
}
```
It is recommended that you use the `allow` option sparingly. It is likely wiser to use the occasional `eslint-disable` than to allow a particular hook throughout your project.
On the other hand, the `block` list is an array of additional custom hooks that you would like to prevent from being used directly in a component. Perhaps you have a custom hook that really should be encapsulated with other hooks. Add it to the block list like so:
```json
{
"plugins": ["use-encapsulation"],
"rules": {
"use-encapsulation/prefer-custom-hooks": [
"error",
{ "block": ["useMyCustomHook"] }
]
}
}
```
I discuss this concept in depth in my [useEncapsulation](https://kyleshevlin.com/use-encapsulation) blog post.
This ESLint plugin contains a single rule:
- `prefer-custom-hooks`
This rule does