diff --git a/README.md b/README.md
index 4e000f5989..d8219c6a06 100644
--- a/README.md
+++ b/README.md
@@ -155,6 +155,7 @@ Enable the rules that you would like to use.
* [react/jsx-no-bind](docs/rules/jsx-no-bind.md): Prevent usage of `.bind()` and arrow functions in JSX props
* [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md): Prevent comments from being inserted as text nodes
* [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
+* [react/jsx-no-length-truthiness](docs/rules/jsx-no-length-truthiness.md): Prevent truthiness checks of `.length` that might render unwanted 0:s (fixable)
* [react/jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
* [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md): Prevent usage of unsafe `target='_blank'`
* [react/jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
diff --git a/docs/rules/jsx-no-length-truthiness.md b/docs/rules/jsx-no-length-truthiness.md
new file mode 100644
index 0000000000..0d328e8956
--- /dev/null
+++ b/docs/rules/jsx-no-length-truthiness.md
@@ -0,0 +1,25 @@
+# Prevent guarding with length truthiness in JSX (react/jsx-no-length-truthiness)
+
+Flags all instances of checking `.length` truthiness in JSX.
+
+**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
+
+## Rule Details
+
+It is a common pattern to render something or nothing using `&&` as a guard:
+
+```jsx
+
{showHeader && } ...
+```
+
+A common mistake is to use array length naïvely for this:
+
+```jsx
+{posts.length && posts.map(p => ...)}
+```
+
+The above code will render a presumably unwanted `0` when the array is empty. This rule checks for such erroneous use, and fixes it to the correct boolean check:
+
+```jsx
+{posts.length > 0 && posts.map(p => ...)}
+```
diff --git a/index.js b/index.js
index 25faa607a6..8d31ed4c26 100644
--- a/index.js
+++ b/index.js
@@ -30,6 +30,7 @@ const allRules = {
'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'),
'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'),
+ 'jsx-no-length-truthiness': require('./lib/rules/jsx-no-length-truthiness'),
'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'),
diff --git a/lib/rules/jsx-no-length-truthiness.js b/lib/rules/jsx-no-length-truthiness.js
new file mode 100644
index 0000000000..ab1c359dcd
--- /dev/null
+++ b/lib/rules/jsx-no-length-truthiness.js
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview Prevent accidentally rendering zeroes by checking .length truthiness
+ * @author Carl Mäsak & David Waller
+ */
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const jsxUtils = require('../util/jsx');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const message = 'Don\'t check .length truthiness in JSX, might render 0';
+
+function findDangerousLengthMember(node) {
+ if (node.type === 'MemberExpression' && node.property.name === 'length') {
+ return node;
+ }
+ if (node.type === 'LogicalExpression') {
+ // we're interested in right side of OR:s and both side of AND:s
+ return (
+ findDangerousLengthMember(node.right) ||
+ (node.operator === '&&' && findDangerousLengthMember(node.left))
+ );
+ }
+ return null;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Prevent checking .length truthiness in JSX',
+ category: 'Possible Errors',
+ recommended: false,
+ url: docsUrl('jsx-no-length-truthiness')
+ },
+ schema: [],
+ fixable: true
+ },
+
+ create: context => ({
+ LogicalExpression(node) {
+ // We're only interested in AND:s, where we'll check for guards to the left
+ if (node.operator !== '&&') {
+ return;
+ }
+ // We know we're "rendering" if right side is JSX or we're inside JSX expression
+ if (
+ !jsxUtils.isJSX(node.right) &&
+ node.parent.type !== 'JSXExpressionContainer'
+ ) {
+ return;
+ }
+ // If left side doesn't check truthiness of .length then all is well
+ const dangerousLengthMember = findDangerousLengthMember(node.left);
+ if (!dangerousLengthMember) {
+ return;
+ }
+ context.report({
+ node,
+ message,
+ fix(fixer) {
+ return fixer.insertTextAfter(dangerousLengthMember, ' > 0');
+ }
+ });
+ }
+ })
+};
diff --git a/tests/lib/rules/jsx-no-length-truthiness.js b/tests/lib/rules/jsx-no-length-truthiness.js
new file mode 100644
index 0000000000..80080397af
--- /dev/null
+++ b/tests/lib/rules/jsx-no-length-truthiness.js
@@ -0,0 +1,81 @@
+/**
+ * @fileoverview Tests for jsx-no-length-truthiness
+ * @author Carl Mäsak & David Waller
+ */
+
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/jsx-no-length-truthiness');
+const RuleTester = require('eslint').RuleTester;
+
+const parserOptions = {
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true
+ }
+};
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({parserOptions});
+
+const expectedError = {
+ message: 'Don\'t check .length truthiness in JSX, might render 0'
+};
+
+ruleTester.run('jsx-no-length-truthiness', rule, {
+ valid: [
+ {code: 'arr.length > 0 && '},
+ {code: 'arr.length > 1 && '},
+ {code: 'arr.length && "foobar"'}, // ok since we can't tell if this is a JSX expression at all
+ {code: 'arr.longth && '},
+ {code: '(arr.length || flag) && '},
+ {code: 'Number of posts: {arr.length}'},
+ {code: 'Number of posts: {flag && arr.length}'},
+ {code: 'You have {arr.length || "no"} posts'}
+ ],
+ invalid: [
+ {
+ code: 'arr.length && ',
+ errors: [expectedError],
+ output: 'arr.length > 0 && '
+ },
+ {
+ code: 'arr.foo.length && ',
+ errors: [expectedError],
+ output: 'arr.foo.length > 0 && '
+ },
+ {
+ code: 'arr && arr.length && ',
+ errors: [expectedError],
+ output: 'arr && arr.length > 0 && '
+ },
+ {
+ code: 'arr && arr.length && somethingElse && ',
+ errors: [expectedError],
+ output: 'arr && arr.length > 0 && somethingElse && '
+ },
+ {
+ code: 'flag || arr.length && ',
+ errors: [expectedError],
+ output: 'flag || arr.length > 0 && '
+ },
+ {
+ code: '{arr.length && "you have posts!"}',
+ errors: [expectedError],
+ output: '{arr.length > 0 && "you have posts!"}'
+ },
+ {
+ code: '{flag && arr.length && "you have posts!"}',
+ errors: [expectedError],
+ output: '{flag && arr.length > 0 && "you have posts!"}'
+ }
+ ]
+});