Skip to content

Commit ab0943d

Browse files
committed
Support custom render prop naming conventions
1 parent 34d3728 commit ab0943d

File tree

3 files changed

+42
-11
lines changed

3 files changed

+42
-11
lines changed

Diff for: docs/rules/no-unstable-nested-components.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ function Component() {
123123
"react/no-unstable-nested-components": [
124124
"off" | "warn" | "error",
125125
{
126-
"allowAsProps": true | false
126+
"allowAsProps": true | false,
127+
"propNamePattern": string
127128
}
128129
]
129130
...
@@ -147,6 +148,16 @@ function Component() {
147148
}
148149
```
149150

151+
You can allow other render prop naming conventions by setting the `propNamePattern` option. By default this option is `"render*"`.
152+
153+
For example, if `propNamePattern` is set to `"*Renderer"` the following pattern is **not** considered warnings:
154+
155+
```jsx
156+
<Table
157+
rowRenderer={(rowData) => <Row data={rowData} />}
158+
/>
159+
```
160+
150161
## When Not To Use It
151162

152163
If you are not interested in preventing bugs related to re-creation of the nested components or do not care about optimization of virtual DOM.

Diff for: lib/rules/no-unstable-nested-components.js

+18-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
'use strict';
77

8+
const minimatch = require('minimatch');
89
const Components = require('../util/Components');
910
const docsUrl = require('../util/docsUrl');
1011
const astUtil = require('../util/ast');
@@ -32,12 +33,13 @@ function generateErrorMessageWithParentName(parentName) {
3233
}
3334

3435
/**
35-
* Check whether given text starts with `render`. Comparison is case-sensitive.
36+
* Check whether given text matches the pattern passed in.
3637
* @param {string} text Text to validate
38+
* @param {string} pattern Pattern to match against
3739
* @returns {boolean}
3840
*/
39-
function startsWithRender(text) {
40-
return typeof text === 'string' && text.startsWith('render');
41+
function propMatchesRenderPropPattern(text, pattern) {
42+
return typeof text === 'string' && minimatch(text, pattern);
4143
}
4244

4345
/**
@@ -165,15 +167,16 @@ function isReturnStatementOfHook(node, context) {
165167
* ```
166168
* @param {ASTNode} node The AST node
167169
* @param {Context} context eslint context
170+
* @param {string} propNamePattern a pattern to match render props against
168171
* @returns {boolean} True if component is declared inside a render prop, false if not
169172
*/
170-
function isComponentInRenderProp(node, context) {
173+
function isComponentInRenderProp(node, context, propNamePattern) {
171174
if (
172175
node
173176
&& node.parent
174177
&& node.parent.type === 'Property'
175178
&& node.parent.key
176-
&& startsWithRender(node.parent.key.name)
179+
&& propMatchesRenderPropPattern(node.parent.key.name, propNamePattern)
177180
) {
178181
return true;
179182
}
@@ -202,7 +205,7 @@ function isComponentInRenderProp(node, context) {
202205
const propName = jsxExpressionContainer.parent.name.name;
203206

204207
// Starts with render, e.g. <Component renderFooter={() => <div />} />
205-
if (startsWithRender(propName)) {
208+
if (propMatchesRenderPropPattern(propName, propNamePattern)) {
206209
return true;
207210
}
208211

@@ -222,16 +225,17 @@ function isComponentInRenderProp(node, context) {
222225
* <Component rows={ [{ render: () => <div /> }] } />
223226
* ```
224227
* @param {ASTNode} node The AST node
228+
* @param {string} propNamePattern The pattern to match render props against
225229
* @returns {boolean} True if component is declared inside a render property, false if not
226230
*/
227-
function isDirectValueOfRenderProperty(node) {
231+
function isDirectValueOfRenderProperty(node, propNamePattern) {
228232
return (
229233
node
230234
&& node.parent
231235
&& node.parent.type === 'Property'
232236
&& node.parent.key
233237
&& node.parent.key.type === 'Identifier'
234-
&& startsWithRender(node.parent.key.name)
238+
&& propMatchesRenderPropPattern(node.parent.key.name, propNamePattern)
235239
);
236240
}
237241

@@ -271,13 +275,17 @@ module.exports = {
271275
allowAsProps: {
272276
type: 'boolean',
273277
},
278+
propNamePattern: {
279+
type: 'string',
280+
},
274281
},
275282
additionalProperties: false,
276283
}],
277284
},
278285

279286
create: Components.detect((context, components, utils) => {
280287
const allowAsProps = context.options.some((option) => option && option.allowAsProps);
288+
const propNamePattern = (context.options[0] || {}).propNamePattern || 'render*';
281289

282290
/**
283291
* Check whether given node is declared inside class component's render block
@@ -412,7 +420,7 @@ module.exports = {
412420

413421
if (
414422
// Support allowAsProps option
415-
(isDeclaredInsideProps && (allowAsProps || isComponentInRenderProp(node, context)))
423+
(isDeclaredInsideProps && (allowAsProps || isComponentInRenderProp(node, context, propNamePattern)))
416424

417425
// Prevent reporting components created inside Array.map calls
418426
|| isMapCall(node)
@@ -422,7 +430,7 @@ module.exports = {
422430
|| isReturnStatementOfHook(node, context)
423431

424432
// Do not mark objects containing render methods
425-
|| isDirectValueOfRenderProperty(node)
433+
|| isDirectValueOfRenderProperty(node, propNamePattern)
426434

427435
// Prevent reporting nested class components twice
428436
|| isInsideRenderMethod(node)

Diff for: tests/lib/rules/no-unstable-nested-components.js

+12
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,18 @@ ruleTester.run('no-unstable-nested-components', rule, {
580580
allowAsProps: true,
581581
}],
582582
},
583+
{
584+
code: `
585+
function ParentComponent() {
586+
return <Table
587+
rowRenderer={(rowData) => <Row data={data} />}
588+
/>
589+
}
590+
`,
591+
options: [{
592+
propNamePattern: '*Renderer',
593+
}],
594+
},
583595
/* TODO These minor cases are currently falsely marked due to component detection
584596
{
585597
code: `

0 commit comments

Comments
 (0)