Compare commits

...

2 commits

Author SHA1 Message Date
Matt Seccafien 90efe91c8c fix: docs/changelog 2021-11-11 10:54:17 +01:00
Matt Seccafien 84ff40ca64 feat: eslint rule no effects on server 2021-11-11 10:51:16 +01:00
15 changed files with 243 additions and 6 deletions

View file

@ -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

View file

@ -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',
},
};

View file

@ -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,
};

View file

@ -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;
}
```

View file

@ -0,0 +1,3 @@
## Rule Details
This rule prevents using these hooks in files that do not end with the `.client` suffix.

View file

@ -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.

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1 @@
export {noEffectsInServerComponents} from './no-effects-in-server-components';

View file

@ -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',
});
}
},
};
},
});

View file

@ -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',
},
],
}
);

View file

@ -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:

View file

@ -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.

View file

@ -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.

View file

@ -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: [],
},