Giter Site home page Giter Site logo

tslint-react-hooks's Introduction

TSLint Rules of Hooks

Downloads badge Version badge License badge GitHub stars badge Build Status

Demo

A TSLint rule that enforces the Rules of Hooks for React hooks.

The rule is based on an ESLint plugin for react hooks.

Features

  • detects using React hooks inside potentially-conditional branches:
    • if statements
    • short-circuit conditional expressions (&&, ||)
    • ternary expressions
    • loops (while, for, do ... while)
    • functions that themselves are not custom hooks or components
  • detects using React hooks in spite of an early return
  • support for detecting hooks from namespaces other than React (e.g. MyHooks.useHook) (optional)

Installation

First, install the rule:

npm install tslint-react-hooks --save-dev

Then, enable the rule by modifying tslint.json:

{
  "extends": [
    // your other plugins...
    "tslint-react-hooks"
  ],
  "rules": {
    // your other rules...
    "react-hooks-nesting": "error"
  }
}

To use report rule violations as warnings intead of errors, set it to "warning".

Options

While the rule works fine out-of-the-box, it can be customized. To specify options, use the following syntax when modifying tslint.json:

{
  "extends": [
    // your other plugins...
    "tslint-react-hooks"
  ],
  "rules": {
    // your other rules...
    "react-hooks-nesting": {
      "severity": "error", // "error", "warning", "default" or "off"
      "options": {
        // options go here
      }
    }
  }
}

Available options

  • "detect-hooks-from-non-react-namespace" - when set to true, violations will be also reported hooks accessed from sources other than the React namespace (e.g. MyHooks.useHook will be treated as a hook).

    By default, only direct calls (e.g. useHook) or calls from React namespace (e.g. React.useState) are treated as hooks.

Have an idea for an option? Create a new issue.

Workarounds

For some arrow functions/function expressions, the rule has no way to determine whether those are a component, a hook, both of which could contain hook calls, or a regular function that should not contain hook calls.

const withHoc = <TProps extends object>(Component: ComponentType<TProps>) => (
  props: TProps,
) => {
  const [state] = useState();
  return <Component {...props} />;
};

The workaround in those cases is to use a named function expression:

const withHoc = <TProps extends object>(Component: ComponentType<TProps>) =>
  function WrappedComponent(props: TProps) {
    const [state] = useState();
    return <Component {...props} />;
  };

Naming the function like a component (in PascalCase) unambiguously lets the rule treat the function as a component.

False positives and not-covered cases

There are some cases that seem hard to analyze and may result in false positives or false negatives.

In such cases, disable the rule for a specific line using the following comment:

// tslint:disable:react-hooks-nesting
useEffect(() => {});

Looping over static arrays

The rule may report false positives, for example in:

function MyComponent() {
  const array = [1, 2, 3];

  array.forEach(value => {
    React.useEffect(() => console.log(value));
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [A hook cannot be used inside of another function]
  });
}

The useEffect hook will be called unconditionally and the call-order will be the same between renders.

Using renamed hooks (that do not start with use)

The rule only treats functions that start with use as hooks. Therefore, renaming the hook will result in avoiding the rule:

const renamedUseState = React.useState;

function MyComponent() {
  const [state, setState] = renamedUseState(0);
}

Unconditional nesting

Unconditional nesting, for example:

function MyComponent() {
  if (true) {
    const variableThatCannotBeLeaked = useContext(SomeContext);
    useEffect(() => {
      console.log(variableThatCannotBeLeaked);
    });
  }

  return <div>Text</div>;
}

is treated as conditional nesting. It seems hard to verify if the condition is in fact a constant, therefore such a situation will always result in a rule violation.

In situations where such an if statement was used to create an additional block scope, use the block scope directly:

function MyComponent() {
  {
    const variableThatCannotBeLeaked = useContext(SomeContext);
    useEffect(() => {
      console.log(variableThatCannotBeLeaked);
    });
  }

  return <div>Text</div>;
}

Development

After pulling the repository, make sure to run

npm install

The source code for the rule is located in the src directory.

For more information about the developing custom TSLint rules, take a look at TSLint's documentation.

Testing the rule

Run

npm run test

to compile the rule and run automatic TSLint tests.

They are located in the test directory.

Author

The author of this rule is Grzegorz Rozdzialik.

tslint-react-hooks's People

Contributors

dependabot[bot] avatar gelio avatar ruiconti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

tslint-react-hooks's Issues

Confused with rules

Hi, thank you for your work. I'm confused with some rules.
My IDE report errors for some files and not on others, I have the same idea in two files, in the first file i don't have any errors, but in the second i have errors. My code is wrong or not?
Screen Shot 2019-04-24 at 11 50 42
Screen Shot 2019-04-24 at 11 49 59

Wrong message for anonymous functions

Consider the following scenario:

const Component = observer(function (props: IProps)) { /* Note that this is, in fact, an anonymous function */
    const { useHook } = props;
    const { myState } = useHook(); /* A hook cannot be used inside of another function */
    return <div>{myState}</div>
})

const WrappedComponent = function() {
    const [state, setState] = React.useState();
    React.useEffect((
        expensiveState.lazyImport().then(s => setState(s));
    ), []);
    
    const Wrapper = React.useCallback(() => state ? <Component {...state}>/> : <Loading/>, [state])
    return <Wrapper/>
}

ReactDOM.render(<WrappedComponent />, ...)

It is in fact a wrong function React component declaration, as React component functions must be named. However, I'm arguing that we should provide a meaningful and relevant error message, analogous to the eslint plugin's.

[Bug] Unable to enable "warning" mode

Hi,

I've introduced this library to one of my projects. However, I'm having an issue with my config` file. It successfully shows errors, but it does not warn. Therefore I am unable to successfully run my app with "warning" mode enabled, because it continues showing errors in warning mode.

In my tslint.json, I have the following:

  "extends": [
    "tslint-react-hooks",
  ],
   ...
  "rules": {
    "react-hooks-nesting": "warning",
   }

package versions:
"tslint-react-hooks": "2.1.1",
"react": "16.8.4",
"typescript": "3.1.5",

Any help would be greatly appreciated!

Support globbing import style

For commonly used utilities, I often use this style:

import * as A from '../animate';
// ...later
if (something) { return null; }
// ...later
const [sceneRef, sceneFade] = A.useAnim({
    anims: [A.anims.fadeOut, A.anim.slow],
});

In this case, A.useAnim is not recognized as a hook. Changing the import style to import { useAnim } from '../animate'; works, but I would prefer not to use this style everywhere, and wouldn't be able to guarantee that all other project members remember that hooks need to be imported in this way.

extend supported component decorators

Currently only React.forwardRef and React.memo are supported. If using Mobx-React, the observer HOC triggers this error and requires overrides in the file. It would be good if we could add observer to the allowed list.

Report error in nextjs file

This is a file in nextjs's pages folder,
paegs/index.tsx:

import Header from '../../components/header';
import axios from 'axios';
import { useEffect, useState } from 'react';

function main() {
  const [data, setData] = useState(null as any); // error here

  useEffect(() => { // error here 
    async function fetchData() {
      try {
        const resp = await axios.post('/xxx', { xxx });
        setData(resp.data);
      } catch (e) {
        console.error(e);
        setData({ error: e && e.toString() });
      }
    }

    fetchData();
  }, []);

  function renderData() {
    if (!data) {
      return 'no data';
    }
    const dataStr = JSON.stringify(data);
    return dataStr;
  }

  return (
    <main>
      <Header />
      {renderData()}
    </main>
  );
}

export default main;

Got an error: A hook cannot be used inside of another function (react-hooks-nesting)

independently-controllable rules

I'd like to disable A hook cannot be used inside of another function, but keep all the others (e.g. A hook cannot appear inside an if statement), but they seem to be all fall under the rule react-hooks-nesting.

Can rules be independently controlled?

shows as warning regardless of rules declaration

I have setup my tslint.json like this:

{
  "defaultSeverity": "error",
  "extends": ["tslint-config-airbnb", "tslint-react-hooks"],
  "jsRules": {},
  "rules": {
    "align": false,
    "import-name": false,
    "no-boolean-literal-compare": false,
    "no-this-assignment": false,
    "react-hooks-nesting": "error",
    "variable-name": false,
    "ter-indent": false,
    "ter-arrow-parens": false,
    "semicolon": false,
    "strict-boolean-expressions": false,
    "space-before-function-paren": false,
    "trailing-comma": [
      true,
      {
        "multiline": {
          "objects": "always",
          "arrays": "always"
        },
        "esSpecCompliant": true
      }
    ]
  },
  "rulesDirectory": []
}

but when I use hooks improperly it shows as a warning.

image

I would expect this to be underlined in red as an error.

Early return?

This should be error right?

    if (thing) {
        return;
    }

    const [state, setState] = useState(null);

Provide support for .tsx files

I'm using create-react-app with typescript, I have installed the tsling plugin and made all related stuff but it doesn't work with .tsx files.
If I change the file name to .ts it will identify the errors though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.