Giter Club home page Giter Club logo

eslint-plugin-react-refresh's Introduction

eslint-plugin-react-refresh npm

Validate that your components can safely be updated with fast refresh.

Limitations

⚠️ To avoid false positive, by default this plugin is only applied on tsx & jsx files. See options to run on JS files. ⚠️

The plugin rely on naming conventions (i.e. use PascalCase for components, camelCase for util functions). This is why there are some limitations:

  • export * are not supported and will be reported as an error
  • Anonymous function are not supported (i.e export default function() {})
  • Class components are not supported
  • All-uppercase function export is considered an error when not using direct named export (ex const CMS = () => <></>; export { CMS })

Installation

npm i -D eslint-plugin-react-refresh

Usage

{
  "plugins": ["react-refresh"],
  "rules": {
    "react-refresh/only-export-components": "warn"
  }
}

Flat config

import reactRefresh from "eslint-plugin-react-refresh";

export default [
  {
    // in main config for TSX/JSX source files
    plugins: {
      "react-refresh": reactRefresh,
    },
    rules: {
      "react-refresh/only-export-components": "warn",
    },
  },
];

Fail

export const foo = () => {};
export const Bar = () => <></>;
export default function () {}
export default compose()(MainComponent)
export * from "./foo";
const Tab = () => {};
export const tabs = [<Tab />, <Tab />];
const App = () => {};
createRoot(document.getElementById("root")).render(<App />);

Pass with allowConstantExport

export const CONSTANT = 3;
export const Foo = () => <></>;

Pass

export default function Foo() {
  return <></>;
}
const foo = () => {};
export const Bar = () => <></>;
import { App } from "./App";
createRoot(document.getElementById("root")).render(<App />);

Options

allowExportNames (v0.4.4)

If you use a framework that handles HMR of some specific exports, you can use this option to avoid warning for them.

Example for Remix:

{
  "react-refresh/only-export-components": [
    "warn",
    { "allowExportNames": ["meta", "links", "headers", "loader", "action"] }
  ]
}

allowConstantExport (v0.4.0)

Don't warn when a constant (string, number, boolean, templateLiteral) is exported aside one or more components.

This should be enabled if the fast refresh implementation correctly handles this case (HMR when the constant doesn't change, propagate update to importers when the constant changes.). Vite supports it, PR welcome if you notice other integrations works well.

{
  "react-refresh/only-export-components": [
    "warn",
    { "allowConstantExport": true }
  ]
}

checkJS (v0.3.3)

If your using JSX inside .js files (which I don't recommend because it forces you to configure every tool you use to switch the parser), you can still use the plugin by enabling this option. To reduce the number of false positive, only files importing react are checked.

{
  "react-refresh/only-export-components": ["warn", { "checkJS": true }]
}

eslint-plugin-react-refresh's People

Contributors

abuabdillahi avatar adamschachne avatar arnaudbarre avatar dianasuvorova avatar janikga 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

eslint-plugin-react-refresh's Issues

False positives in v0.4.4

I'm getting the following error Fast refresh only works when a file only exports components. Move your component(s) to a separate file in a lot of .tsx files which only have one export.

Example code for triggering it:

const CONSTANT = 1

function Example(props: { prop1: any }) {
  return <div>Example: {CONSTANT}</div>
}

export const ExampleNode3 = {
  type: "node",
  key: "example-node3",
  contents: (props: any) => <Example prop1={props} />,
}

Result

$ eslint Example.tsx

  1:7   warning  Fast refresh only works when a file only exports components. Move your component(s) to a separate file  react-refresh/only-export-components
  3:10  warning  Fast refresh only works when a file only exports components. Move your component(s) to a separate file  react-refresh/only-export-components

Expected result:

  1. No warning for CONSTANT.
  2. No warning for Example. Only a warning for Example

Warning triggered for component using forwardRef

v0.41
I get this error when calling React.forwardRef on my component.

function CardContainer(props,ref) {
	return <div ref={ref}>Hello world!</div>;
}

export default forwardRef(CardContainer);

Allow constant exports doesn't work with objects.

I am looking to create some way where these constants would work.
If I am able to say these are immutable with as const or if the plugin would be able to infer that simply by a full capitalisation of a variable that would be nice. If I write const MY_CONSTANT = {} I am telling you 'I will not mutate this, it is a constant'.

There should be no warning below?
image

Warning is not appearing for enums

Hello,

I have an exported component with an exported enum in one file, but I do not get a ESLint warning. But I do get a warning if I add an exported string into file. Shouldn't the enum be highlighted with error as well?

Screenshot

image

Thanks

Bug: ts declaration types are not shipped with the package

When I am trying to use it in flat config I get this error:
Could not find a declaration file for module 'eslint-plugin-react-refresh'
But your package is build with typescript so you can just emit declaration files to the distributed files in order for others to use them.

Screenshot 2024-02-26 at 01 58 52

[BUG] Not working as intended

README suggests to do this:

{
  "plugins": ["react-refresh"],
  "rules": {
    "react-refresh/only-export-components": "warn"
  }
}

But the plugin is doing the wrong exports, infact i only got it working by doing:

{
  "plugins": ["only-export-components"],
  "rules": {
    "only-export-components/only-export-components": "warn"
  }
}

How to configure your plugin to work with a Vite+React project?

I found your repo from this message in my Vite+React project:

[vite] hmr invalidate /src/Hooks/UseFilter.jsx Could not Fast Refresh. Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports

Then I installed your plugin created the .eslintrc.json file at the root of my project, and copied/pasted your configuration there.

However, I still don't see anything when I save my react .jsx files. I see the old warnings.

I then installed eslint and eslint-plugin-react in addition to your plugin, but they changed nothing.

What should I do to see more details? What should I do to make your plugin part of every save?

Capitalized non-function not triggering rule

Camel case variables are flagged correctly:

export function Component() {}
export const aa = 'a' // <--- flagged

But the rule seems to think that pascal case variables, no matter what they are, are components:

export function Component() {}
export const Aa = 'a' // <- not flagged!

This is clearly not ideal - is this possible to fix?

Support for constant objects?

Regarding

{
  "react-refresh/only-export-components": [
    "warn",
    { "allowConstantExport": true }
  ]
}

Is there a way to also support objects? E.g.

export const documentProps = {
  // This title and description will override the defaults
  title: 'About SpaceX',
  description: 'Our mission is to explore the galaxy.'
}

Is `export default ` HOC pattern should be flagged?

We get this type of pattern flagged by the rule:

  const MainPageHeader = () => <HeaderWrapper/>
  export default withRouter(MainPageHeader);

error:

AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [
  {
    ruleId: 'only-export-components',
    severity: 1,
    message: 'Fast refresh only works when a file only export components. Move your component(s) to a separate file.',
    line: 2,
    column: 15,
    nodeType: 'Identifier',
    messageId: 'localComponents',
    endLine: 2,
    endColumn: 29
  }
]

while this one is valid from a react-refresh perspective:

      const MainPageHeader = () => <HeaderWrapper/>
      export const SubScreen =  withRouter(MainPageHeader);

Throwing error for typescript type exports.

I export all of the components, including the types, using the index.tsx file that is located in that folder.

When I export the component in the following way eslint-plugin-react-refresh is giving me warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components.

export { default } from './Applications'
export type { ApplicationsProps } from './Applications'

There is no warning, though, when I write it in a different way like I do below.

import Applications, { type ApplicationsProps } from './Applications'

export default Applications
export type { ApplicationsProps }

In my opinion, the plugin shouldn't issue any alerts if the types are exported.

Please add better description what it does

Hello,
I found this repo by accident, someone just used your library.

I took a look at readme and I completely don't know what this is all about.

Having only one sentence:

Validate that your components can safely be updated with fast refresh.

is probably not enough. As a new user I see some limitations, some options, but no answer for: why would a component be unsafe for fast refresh? It would be useful to indicate some sources describing the problem in the first place. Even explaining what a fast refresh is (in a form of external link, I don't suggest to put a encyclopedia in the README) could be useful.

Personally I'm not even sure if I need it, is this webpack specific feature, is my version of React using it? Previously we had hot module replacement, quick Googling suggests that fast refresh is replacing it. But I needed to scroll some results to get this information - Googling fast refresh react I get highlighted answer that Fast Refresh is a feature of React Native, which is probably a bad answer as this library is definitely not intented to be React Native only. But users may get a feeling this library is not useful for them if they are working with web.

Thanks

feature request: Allow lazy loading

Now, the rule raises errors with the following codes

const LazyComponent = React.lazy(
  () => import("./MyComponent"),
);
Fast refresh only works when a file only exports components. Move your component(s) to a separate file.eslint(react-refresh/only-export-components)

Can we modify to determine this as the correct code?

Question about potential problems with barrel files

Hello! This isn't really an issue. Just a question...

Screenshot 2024-02-15 at 8 51 32 PM

I've got a monorepo with a ui package that exports hooks, utils, and components.

We export this by doing a barrel file for all hooks, all utils, and components... then we do one package-level barrel-file that exports all the contents of the 3 other barrel files. That top-level barrel file is marked as main and types in the ui package's package.json. Then, I symlink the package as via installing it in other monorepo applications like:

"@scoped/ui": "workspace:*"

Am I potentially hurting fast refresh for the applications with this setup?

Consider all-uppercase exports as components

Why not consider uppercase exports as components? I for example have one like this:

export function SVG() {
  return <svg/>;
}

It's mostly stylistic, but a Svg looks suboptimal to me.

Another idea may be that such a component could just be automatically detected as one because it returns JSX content, e.g. don't purely rely on export name, but actually analyze the return value of the function.

Add option to suppress the rule on certain file patterns

I'm working with Storybook and on stories files a default export is always required.
I don't want to clutter every file with the eslint disable rule comment, so it would be awesome to have an option to disable the rule for defined file patterns (eg. *.stories.tsx)

Add an option to not warn on const export

Thanks for you job! I test on vite based project, when I create a button.tsx file, contains:

import React from 'react'

export const CONSTANT = 12
export const Foo = () => {
  window.document.title = '6'
}

export default function Button() {
  const [count, setCount] = React.useState(0)
  return <div onClick={() => setCount(1)}>{count}1</div>
}

When I edit this file, it's safety hmr update. Do I miss something?

Possible false positive/negative

Given the following code...

// --> I see an error here <--
const MenuItemLinkRoot = styled(MenuItem, {
  name: 'MenuItemLink',
  slot: 'Root',
  overridesResolver: (props, styles) => styles.root,
})<MenuItemLinkProps>``

const useUtilityClasses = (ownerState: Partial<MenuItemLinkProps>) => {
  const slots = {
    root: ['root'],
  }

  return unstable_composeClasses(slots, getMenuItemLinkUtilityClass, ownerState.classes)
}


// --> and here <--
const MenuItemLink = (props: MenuItemLinkProps) => {
  const { className, classes, ...rootProps } = props

  const slotClasses = useUtilityClasses({ classes })

  const routeMatch = useRouteMatch(props.to as string)

  return (
    <MenuItemLinkRoot
      {...rootProps}
      className={classnames(slotClasses.root, className)}
      selected={props.to === routeMatch?.path}
    />
  )
}

const _MenuItemLink = React.memo(MenuItemLink) as typeof MenuItemLink

export default _MenuItemLink

So as marked by the comments above, I see the error Fast refresh only works when a file only exports components. Move your component(s) to a separate file.

As I am only exporting one component from this file, this seem incorrect.

I would guess this is to do with the leading _ on the default export.

If I change the code so that: -

  • MenuItemLink becomes _MenuItemLink
  • _MenuItemLink becomes MenuItemLink

(so that the exported name starts with a capital, and the non-exported component startds with a _)

then it works without error.

Check for default export naming to be PascalCase

Hello,

When we are using Vite with react-refresh, hot-reloading stops working if we so much as export a component without pascal case naming. If I'm not wrong, this package seems like the best place to enforce a rule to always export from *.jsx files with the correct naming style.

Example without hot reloading:

const myComponent  = () => { ... }

export default myComponent; // Camel case export _not_ pascal case

Working example with hot reloading:

const MyComponent = () => { ... }

export default MyComponent; // Not the pascal-casing

We are able to replicate the issue consistently in our project, but haven't found an ESLint rule that will catch this mistake. Is this already possible? Or would it be a simple enough fix to include it?

Thanks

Possible false-positive with memo default export

import {memo} from "react";

export default memo(function Foo() {
  return <div/>;
})

Results in

  4:16  error  Fast refresh can't handle anonymous components. Add a name to your export  react-refresh/only-export-components

As far as I'm aware this is still a named component because of the function name and the module is asking for something impossible because a default export can not be named.

(The reason this can not be changed to a named export is because lazy requires a default export).

Correct use with Context API and Vite - docs missing?

I am using the latest Vite for building a React app in which I have a PostsContext.jsx file that exports a custom context provider (component) PostsProvider and custom hook usePosts as named exports:

export {PostsProvider, usePosts}

So this react-refresh eslint plugin immediately warns about exporting multiple values (component and hook in this case).

warning  Fast refresh only works when a file only exports components.
Use a new file to share constants or functions between components

Unfortunately I cannot move each export to a separate file, it defeats the purpose of having a custom module PostsContext.jsx created specifically as container for all things related to this PostsContext.

I would like for the warning to stop bugging me during development, but I don't want the HMR / Fast Refresh feature to break to the point of crashing as per jsx-eslint/eslint-plugin-react#3176

The relevant parts in .eslintrc.cjs config file look like this:

plugins: ['react-refresh'],
rules: {
    'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true }
    ]
}

Is there an explanation for what I'm missing here? eslint-plugin-react-refresh doesn't document turning on/off warnings or errors and does not give detailed explanations on proper usage. Can you please let me know how to handle this issue? Merci beaucoup!

Question: Configuration to allow different file extensions to be checked?

We are looking to apply this lint rule to our codebase. Although we follow different file extension policies for React.
So we need to allow the rule to parse '.js' files as well.

Would you be open to a contribution that would introduce an option to configure this rule to override parsable file extensions?
The default can still be tsx & jsx?

Thanks,
Diana

False positive on `export { X as default }`

Hello,

The following code produces a warning:

export { App as default };

const App = () => <>Test</>;

warning Fast refresh only works when a file only exports components. Move your component(s) to a separate file react-refresh/only-export-components

I am using Vite with the default config and fast refresh is working properly:

[vite] hmr update /src/App.tsx (x3)

I can tell this syntax is not in your test suite, so it's certainly not a bug.

I noticed your plugin is not the only one to choke at this syntax. Storybook does not like it when I export a story's meta object this way. I wonder why that is.

I like to have my exports at the top of the file as well as to use the arrow notation. This syntax is the only valid way to do achieve it.

Thank you for your work !

Support for styled components

Do we need some special treatment for styled components? At least I'm getting an unexpected error Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components for styled components like AnotherReactContainer in this example:

import styled from "styled-components";

export const SomeReactComponent = () => (<div>Test</div>);

export const AnotherReactContainer = styled.div`
  padding-bottom: 6px;
`;

Consider not warning on `export *` when re-exported file is passing

I think a common pattern in a library is to re-export everything in a index.jsx for convenience, for example:

export * from "./Buttons.jsx";

Currently, this plugin will error on every such line even thought the re-exported files themselves pass. Is this really an issue with the HMR or a linter bug?

Warn exports instead of components when using only components

It seems like object export isn't correctly handled. In the following simplified example, NameInput has a warning, but it should actually be SomeInternalInput and SomeInternalForm OR TitleSection that had an error.

const SomeInternalInput = () => <div><NameInput /></div>
const SomeInternalForm = () => <div />

// Shows a warning, even though it isn't exported
const NameInput = styled(Input)`
  font-weight: 500;
`;

export const TitleSection = {
  Input: SomeInternalInput,
  Form: SomeInternalForm,
};

Lazy Loading react component get some warning

I am building a Vite react ts app. I wanted to code split my components by using React.lazy
I tried import the component like so

import { lazy } from "react";
const Login = lazy(() => import("./Login.tsx"));

Is this supported? or am I doing something wrong

How to use it with React.memo ?

When using memo, it needs to be packaged many times. It is too verbose. Can it be optimized?

export default InternalDemo = memo(()=> <></>) 
const InternalDemo = ()=> <></>

// ❌
export default memo(InternalDemo) 

// ✅
const Demo =  memo(InternalDemo) 
export default Demo

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.