-
-
Notifications
You must be signed in to change notification settings - Fork 153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add require-readonly-react-props rule #400
Conversation
I'm gonna test this PR against our codebase (~180K LOC with ~65% of it flowified) and report back if there are any issues |
@gajus I've fixed few more edge cases and this now runs successfully in our codebase. Would love to have this tested by someone else. Let me know if there's anything else to do here before this could be merged. |
Is there a reason for this? I almost always use |
@gajus afaik, there's no difference. I didn't want to go down that route as it seemed too complex but now that I think about it... I can just check all top-level object attributes and determine easily if they're all "read-only". I'll update the PR with the fix and tests. Here's something else I just found. Apparently, spreading invalidates immutability of the entire shape 🤦🏻♂️ I guess we can bail out if we encounter spreading, so: type Foo = {| +foo: string |} // read-only
type Bar = $ReadOnly<{| foo: string |}> // read-only
type Baz = {| +foo: string, ...Bar |} // NOT read-only |
TIL that this is not an error: type Bar = {| +bar: string |}
type Baz = {| +foo: string, ...Bar |}
let baz: Baz = {foo: 'foo', bar: 'baz'};
baz.foo = 'test';
baz.bar = 'test'; My guess that it is related to facebook/flow#7298. |
@gajus made covariant notation work |
@gajus any chance to get this merged? 🙏 |
🎉 This PR is included in version 3.9.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Hello @kangax! 👋 I tried this rule in our monorepo and I found a few cases where this rule fails but it probably should not:
export function MonoText(props: $FlowFixMe) {
return <Text ... />;
}
import type { AppQueryResponse } from './__generated__/AppQuery.graphql';
// note that AppQueryResponse is actually exact and readonly
function renderQueryRendererResponse(props: AppQueryResponse) {
return <div ... />
}
type CommonProps = {|
+query: GraphQLTaggedNode,
+clientID?: string, // Identification of the current client (X-Client header basically).
+environment?: Environment,
+cacheConfig?: {|
+force?: ?boolean,
+poll?: ?number,
|},
+variables?: Variables,
|};
// ...
type Props =
| {|
...CommonProps,
+onSystemError?: ({ error: Error, retry: ?() => void }) => React$Node,
+onLoading?: () => React$Node,
+onResponse: RendererProps => React$Node,
|}
| {|
...CommonProps,
+render: ReadyState => React$Node,
|};
export default function QueryRenderer(props: Props) {
// ...
}
export default class HomeScreen extends React.Component<{||}> {
// ...
}
I hope this will help you to make some additional adjustments and improvements. Cheers 🍻 |
This version adds a new rule but it's currently quite broken so it's disabled, see: gajus/eslint-plugin-flowtype#400 (comment) kiwicom-source-id: b9da94aaf76900ed0594eaafa469ca0598d5042e
* Add requireReadOnlyReactProps rule * Update messages * Change naming * Fix imports * Fix linter * Fix some edge cases * Fix few more edge cases * Make covariant notation work * Update docs
Hey @mrtnzlml, thanks for these.
I'll make a PR shortly to fix this.
we have no way of knowing if imported props are read-only, this is documented in the rule description
I'd love to see more examples here because in this case you're spreading an object, which makes a "parent" shape mutable (likely a Flow bug)
Hm, I guess we should fix this only if an empty object is both exact and empty (and is essentially immutable) |
I hope you'll reconsider the error. Clearly, it's false positive and it essentially means we'll never enable this rule because it's very common. That would be a pity. I think a better strategy would be to be silent if you are not sure instead of throwing this:
Yes please, this is exactly how we are using it: to say "this component doesn't accept props". |
Having a React component with no props/state defined causes a crash: import { Component } from 'react';
class TestComponent extends Component {
render() {
return <div>Hello</div>;
}
} Error:
ESLint config: {
"rules": {
"flowtype/require-readonly-react-props": "error"
},
"parser": "babel-eslint",
"plugins": ["flowtype"]
} |
Make the last few Props types [1] readonly. This is done manually. The few remaining Props types fall largely into two categories: - types defined using `$Diff`; and - nonempty exact types defined inline. The former have been manually wrapped with $ReadOnly<>. The latter have had their entries prefixed with `+`, which for exact types has essentially the same effect. (See [the flow documentation] and [the original PR].) [1] Or at least, the last few Props types detected by `eslint-plugin-flowtype`. [the flow documentation]: https://flow.org/en/docs/types/interfaces/#toc-covariant-read-only-properties-on-interfaces [the original PR]: gajus/eslint-plugin-flowtype#400
Make the last few Props types [1] readonly. This is done manually. The few remaining Props types fall largely into two categories: - types defined using `$Diff`; and - nonempty exact types defined inline. The former have been manually wrapped with $ReadOnly<>. The latter have had their entries prefixed with `+`, which for exact types has essentially the same effect. (See, e.g., the Flow documentation [2] and the original PR [3].) [1] Or at least, the last few Props types detected by `eslint-plugin-flowtype`. [2] https://flow.org/en/docs/types/interfaces/#toc-covariant-read-only-properties-on-interfaces [3] gajus/eslint-plugin-flowtype#400
Make the last few Props types [1] readonly. This is done manually. The few remaining Props types fall largely into two categories: - types defined using `$Diff`; and - nonempty exact types defined inline. The former have been manually wrapped with `$ReadOnly`. The latter have had their entries prefixed with `+`, which for exact types has essentially the same effect. (See, e.g., the Flow documentation [2] and the original PR [3].) [1] Or at least, the last few Props types detected by `eslint-plugin-flowtype`. [2] https://flow.org/en/docs/types/interfaces/#toc-covariant-read-only-properties-on-interfaces [3] gajus/eslint-plugin-flowtype#400
Make the last few Props types [1] readonly. This is done manually. The few remaining Props types fall largely into two categories: - types defined using `$Diff`; and - nonempty exact types defined inline. The former have been manually wrapped with `$ReadOnly`. The latter have had their entries prefixed with `+`, which for exact types has essentially the same effect. (See, e.g., the Flow documentation [2] and the original PR [3].) [1] Or at least, the last few Props types detected by `eslint-plugin-flowtype`. [2] https://flow.org/en/docs/types/interfaces/#toc-covariant-read-only-properties-on-interfaces [3] gajus/eslint-plugin-flowtype#400
Make the last few Props types [1] readonly. This is done manually. The few remaining Props types fall largely into two categories: - types defined using `$Diff`; and - nonempty exact types defined inline. The former have been manually wrapped with `$ReadOnly`. The latter have had their entries prefixed with `+`, which for exact types has essentially the same effect. (See, e.g., the Flow documentation [2] and the original PR [3].) The one nonempty _inexact_ type has been tightened to exactitude, reducing it to a previously solved case. [1] Or at least, the last few Props types detected by `eslint-plugin-flowtype`. [2] https://flow.org/en/docs/types/interfaces/#toc-covariant-read-only-properties-on-interfaces https://flow.org/en/docs/types/utilities/#toc-readonly [3] gajus/eslint-plugin-flowtype#400
Closes #397
require-readonly-react-props
This rule validates that React props are marked as $ReadOnly. React props are immutable and modifying them could lead to unexpected results (I've seen people try to do this in our codebase, not realising it doesn't work). Marking prop shapes as $ReadOnly avoids these issues.
The rule tries its best to work with both class and functional components. For class components, it does a fuzzy check for one of "Component", "PureComponent", "React.Component" and "React.PureComponent". It doesn't actually infer that those identifiers resolve to a proper
React.Component
object.For example, this will NOT be checked:
As a result, you can safely use other classes without getting warnings from this rule:
React's functional components are hard to detect statically. The way it's done here is by searching for JSX within a function. When present, a function is considered a React component:
The rule only works for locally defined props that are marked with a
$ReadOnly
. It doesn't work with imported props or props marked as read-only via covariant notation: