Compare commits
2 commits
main
...
@cartogram
Author | SHA1 | Date | |
---|---|---|---|
90efe91c8c | |||
84ff40ca64 |
|
@ -5,7 +5,9 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
<!-- ## Unreleased -->
|
||||
## Unreleased
|
||||
|
||||
- New rule `hydrogen/no-effect-in-server-components`. This rule prevents using `useEffect` and `useLayoutEffect` in non-client components.
|
||||
|
||||
## 0.6.2 - 2021-11-10
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ export default {
|
|||
plugins: ['hydrogen'],
|
||||
rules: {
|
||||
'hydrogen/no-state-in-server-components': 'error',
|
||||
'hydrogen/no-effects-in-server-components': 'error',
|
||||
'hydrogen/prefer-image-component': 'error',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import {noStateInServerComponents} from './no-state-in-server-components';
|
||||
import {noEffectsInServerComponents} from './no-effects-in-server-components';
|
||||
import {preferImageComponent} from './prefer-image-component';
|
||||
|
||||
export const rules: {[key: string]: any} = {
|
||||
'no-effects-in-server-components': noEffectsInServerComponents,
|
||||
'no-state-in-server-components': noStateInServerComponents,
|
||||
'prefer-image-component': preferImageComponent,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Prevents `useEffect` and `useLayoutEffect` in React Server Components (`hydrogen/no-effects-in-server-components`)
|
||||
|
||||
The `useEffect` and `useLayoutEffect` lifecycle hooks do not function as expected in React Server Components because Server Components execute only once per request on the server.
|
||||
|
||||
## Rule Details
|
||||
|
||||
This rule prevents using these hooks in files that do not end with the `.client` suffix.
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
```tsx
|
||||
// MyServerComponent.server.jsx
|
||||
|
||||
function MyServerComponent() {
|
||||
const [state, setState] = useState();
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// MyServerComponent.jsx
|
||||
|
||||
function MyServerComponent() {
|
||||
const [state, setState] = useState();
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule:
|
||||
|
||||
```tsx
|
||||
// MyClientComponent.client.jsx
|
||||
|
||||
function MyClientComponent() {
|
||||
const [state, setState] = useState();
|
||||
return null;
|
||||
}
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
## Rule Details
|
||||
|
||||
This rule prevents using these hooks in files that do not end with the `.client` suffix.
|
|
@ -0,0 +1,3 @@
|
|||
# Prevents `useEffect` and `useLayoutEffect` in React Server Components (`hydrogen/no-effects-in-server-components`)
|
||||
|
||||
The `useEffect` and `useLayoutEffect` lifecycle hooks do not function as expected in React Server Components because Server Components execute only once per request on the server.
|
|
@ -0,0 +1,12 @@
|
|||
// Examples of **incorrect** code for this rule:
|
||||
|
||||
// MyComponent.jsx or MyComponent.server.jsx
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function MyNonClientComponent() {
|
||||
useEffect(() => {
|
||||
// code inside this useEffect will not execute as expected
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Examples of **correct** code for this rule:
|
||||
|
||||
// MyClientComponent.client.jsx
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function MyClientComponent() {
|
||||
useEffect(() => {
|
||||
// in client components, this code will execute as expected
|
||||
});
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export {noEffectsInServerComponents} from './no-effects-in-server-components';
|
|
@ -0,0 +1,40 @@
|
|||
import {AST_NODE_TYPES} from '@typescript-eslint/types';
|
||||
|
||||
import {createRule, isClientComponent} from '../../utilities';
|
||||
|
||||
const BANNED_HOOKS = ['useEffect', 'useLayoutEffect'];
|
||||
|
||||
export const noEffectsInServerComponents = createRule({
|
||||
name: `hydrogen/${__dirname}`,
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'Prevents `useEffect` and `useLayoutEffect` in React Server Components',
|
||||
category: 'Possible Errors',
|
||||
recommended: 'error',
|
||||
},
|
||||
messages: {
|
||||
noEffectsInServerComponents: `Do not use {{hook}} in React Server Components.`,
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create: function (context) {
|
||||
return {
|
||||
CallExpression(node) {
|
||||
if (
|
||||
!isClientComponent(context.getFilename()) &&
|
||||
node.callee.type === AST_NODE_TYPES.Identifier &&
|
||||
BANNED_HOOKS.includes(node.callee.name)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
data: {hook: node.callee.name},
|
||||
messageId: 'noEffectsInServerComponents',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,124 @@
|
|||
import {TSESLint} from '@typescript-eslint/experimental-utils';
|
||||
import {AST_NODE_TYPES} from '@typescript-eslint/types';
|
||||
import {noEffectsInServerComponents} from '../no-effects-in-server-components';
|
||||
import dedent from 'dedent';
|
||||
|
||||
const ruleTester = new TSESLint.RuleTester({
|
||||
parser: require.resolve('@typescript-eslint/parser'),
|
||||
});
|
||||
|
||||
function error(hookName: string) {
|
||||
return {
|
||||
type: AST_NODE_TYPES.CallExpression,
|
||||
data: {hook: hookName},
|
||||
messageId: 'noEffectsInServerComponents' as 'noEffectsInServerComponents',
|
||||
};
|
||||
}
|
||||
|
||||
ruleTester.run(
|
||||
'hydrogen/no-effects-in-server-components',
|
||||
noEffectsInServerComponents,
|
||||
{
|
||||
valid: [
|
||||
{
|
||||
code: dedent`
|
||||
function ClientComponent() {
|
||||
useEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
filename: 'ClientComponent.client.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ClientComponent() {
|
||||
useLayoutEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
filename: 'ClientComponent.client.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
filename: 'ServerComponent.server.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
const {foo} = useBar();
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
filename: 'ServerComponent.server.tsx',
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
useEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
errors: [error('useEffect')],
|
||||
filename: 'ServerComponent.server.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
useLayoutEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
errors: [error('useLayoutEffect')],
|
||||
filename: 'ServerComponent.server.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
useEffect(() => {});
|
||||
useLayoutEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
errors: [error('useEffect'), error('useLayoutEffect')],
|
||||
filename: 'ServerComponent.server.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
useEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
errors: [error('useEffect')],
|
||||
filename: 'ServerComponent.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
useEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
errors: [error('useEffect')],
|
||||
filename: 'ServerComponent.tsx',
|
||||
},
|
||||
{
|
||||
code: dedent`
|
||||
function ServerComponent() {
|
||||
useLayoutEffect(() => {});
|
||||
useEffect(() => {});
|
||||
return null;
|
||||
}
|
||||
`,
|
||||
errors: [error('useLayoutEffect'), error('useEffect')],
|
||||
filename: 'ServerComponent.tsx',
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
|
@ -1,10 +1,10 @@
|
|||
# Prevents `useState` and `useReducer` in React Server Components (`hydrogen/no-state-in-server-components`)
|
||||
|
||||
The `useState` and `useReducer` state handling hooks do not function as expected in React Server Components because they execute once per request on the server.
|
||||
The `useState` and `useReducer` state handling hooks do not function as expected in React Server Components because Server Components execute only once per request on the server.
|
||||
|
||||
## Rule Details
|
||||
|
||||
This rule prevents using these hooks in files that do not end with the `.client` suffix that denotes a React Component that does not run on the server.
|
||||
This rule prevents using these hooks in files that do not end with the `.client` suffix.
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
## Rule Details
|
||||
|
||||
This rule prevents using these hooks in files that do not end with the `.client` suffix that denotes a React Component that does not run on the server.
|
||||
This rule prevents using these hooks in files that do not end with the `.client` suffix.
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# Prevents `useState` and `useReducer` in React Server Components (`hydrogen/no-state-in-server-components`)
|
||||
|
||||
The `useState` and `useReducer` state handling hooks do not function as expected in React Server Components because they execute once per request on the server.
|
||||
The `useState` and `useReducer` state handling hooks do not function as expected in React Server Components because Server Components execute only once per request on the server.
|
||||
|
|
|
@ -15,7 +15,7 @@ export const noStateInServerComponents = createRule({
|
|||
recommended: 'error',
|
||||
},
|
||||
messages: {
|
||||
noStateInServerComponents: `Do not use {{hook}} in React Server Components. These components only run once and therefore cannot handle state like traditional client components.`,
|
||||
noStateInServerComponents: `Do not use {{hook}} in React Server Components.`,
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue