Giter Club home page Giter Club logo

jsx-control-statements's Introduction

JSX Control Statements

Build Status Coverage Status npm version

JSX-Control-Statements is a Babel plugin that extends JSX to add basic control statements: conditionals and loops. It does so by transforming component-like control statements to their JavaScript counterparts - e.g. <If condition={condition()}>Hello World!</If> becomes condition() ? 'Hello World!' : null.

Developers coming to React from using JavaScript templating libraries like Handlebars are often surprised that there's no built-in looping or conditional syntax. This is by design - JSX is not a templating library, it's declarative syntactic sugar over functional JavaScript expressions. JSX Control Statements follows the same principle - it provides a component-like syntax that keeps your render functions neat and readable, but desugars into clean, readable JavaScript.

The only dependency JSX-Control-Statements relies upon is Babel. It is compatible with React and React Native.

☠️ Beware: This is a Babel plugin. It changes your code to other code - this means that some tooling that looks at your code (e.g. static analysis, typescript) is likely to not work. This plugin dates back to when JSX was daring and Javascript was more like playdough than meccano - if you want to stay on the well-trodden path stick with writing && and map.

Table of Contents

A Note on Transformation and Alternative Solutions

It appears to be pretty easy to implement conditionals as React component, which is underlined by the amount of libraries which have taken this approach. However, all of them suffer from the same major caveat: A React component will always evaluate all of its properties including the component body. Hence the following example will fail for those libraries:

<IfComponent condition={item}>{item.title}</IfComponent>

The error will be "Cannot read property 'title' of undefined", because React will evaluate the body of the custom component and pass it as "children" property to it. The only workaround is to force React into lazy evaluation by wrapping the statement in a function.

This is the reason why conditionals must be implemented in pure JS. JSX-Control-Statements only adds the syntactic sugar to write conditionals as component, while it transforms this "component" to a pure JS expression.

See Alternative Solutions for a more detailed comparison and pure JS solutions.

Installation

As a prerequisite you need to have Babel installed and configured in your project.

Install via npm:

  npm install --save-dev babel-plugin-jsx-control-statements

Then you only need to specify JSX-Control-Statements as Babel plugin, which you would typically do in your .babelrc.

{
  ...
  "plugins": ["jsx-control-statements"]
}

If you use the transform-react-inline-elements plugin, place it after jsx-control-statements:

{
  ...
  "plugins": ["jsx-control-statements", "transform-react-inline-elements"]
}

Babel can be used and configured in many different ways, so use this guide to pick a configuration which fits your setup.

Syntax

If Tag

Used to express the most simple conditional logic.

// simple
<If condition={ true }>
  <span>IfBlock</span>
</If>

// using multiple child elements and / or expressions
<If condition={ true }>
  one
  { "two" }
  <span>three</span>
  <span>four</span>
</If>

<If>

The body of the if statement only gets evaluated if condition is true.

Prop Name Prop Type Required
condition boolean

<Else /> (deprecated)

The else element has no properties and demarcates the else branch.

This element is deprecated, since it's bad JSX/XML semantics and breaks auto-formatting. Please use <Choose> instead.

Transformation

If statements transform to the ternary operator:

// before transformation
<If condition={test}>
  <span>Truth</span>
</If>;

// after transformation
{
  test ? <span>Truth</span> : null;
}

Choose Tag

This is an alternative syntax for more complex conditional statements. Its a equivalent of switch statement for jsx. The syntax itself is XMLish and conforms by and large to JSTL or XSLT (the attribute is called condition instead of test):

<Choose>
  <When condition={ test1 }>
    <span>IfBlock</span>
  </When>
  <When condition={ test2 }>
    <span>ElseIfBlock</span>
    <span>Another ElseIfBlock</span>
    <span>...</span>
  </When>
  <Otherwise>
    <span>ElseBlock</span>
  </Otherwise>
</Choose>

// default block is optional; minimal example:
<Choose>
  <When condition={true}>
    <span>IfBlock</span>
  </When>
</Choose>

<Choose>

Acts as a simple container and only allows for <When> and <Otherwise> as children. Each <Choose> statement requires at least one <When> block but may contain as many as desired. The <Otherwise> block is optional.

<When>

Analog to <If>.

Prop Name Prop Type Required
condition boolean

<Otherwise>

<Otherwise> has no attributes and demarcates the else branch of the conditional.

Transformation

This syntax desugars into a (sequence of) ternary operator(s).

// Before transformation
<Choose>
  <When condition={test1}>
    <span>IfBlock1</span>
  </When>
  <When condition={test2}>
    <span>IfBlock2</span>
  </When>
  <Otherwise>
    <span>ElseBlock</span>
  </Otherwise>
</Choose>;

// After transformation
{
  test1 ? (
    <span>IfBlock1</span>
  ) : test2 ? (
    <span>IfBlock2</span>
  ) : (
    <span>ElseBlock</span>
  );
}

For Tag

Define <For> like so:

  // you must provide the key attribute yourself
  <For each="item" of={ this.props.items }>
    <span key={ item.id }>{ item.title }</span>
  </For>

  // using the index as key attribute is not stable if the array changes
  <For each="item" index="idx" of={ [1,2,3] }>
    <span key={ idx }>{ item }</span>
    <span key={ idx + '_2' }>Static Text</span>
  </For>
Prop Name Prop Type Required description
of array or collection(Immutable) the array to iterate over. This can also be a collection (Immutable.js) or anything on which a function with the name map can be called
each string a reference to the current item of the array which can be used within the body as variable
index string a reference to the index of the current item which can be used within the body as variable

Note that a <For> cannot be at the root of a render() function in a React component, because then you'd potentially have multiple components without a parent to group them which isn't allowed. As with <If>, the same rules as using Array.map() apply - each element inside the loop should have a key attribute that uniquely identifies it.

For Tag - Alternative Syntax

For those using Typescript, the previous syntax introduces several issues with undefined variables. To deal with this issue, we introduce a following syntax, inspired by tsx-control-statements.

// before transformation
<For
  of={items}
  body={(item, index) => (
    <span key={item.id}>
      {index}. {item.title}
    </span>
  )}
/>;

// after transformation
{
  items.map(function(item, index) {
    <span key={item.id}>
      {index}. {item.title}
    </span>;
  });
}
Prop Name Prop Type Required description
of array or collection(Immutable) the array to iterate over. This can also be a collection (Immutable.js) or anything on which a function with the name map can be called
body map expression expression of the map statement

With Tag

Used to assign values to local variables:

// simple
<With foo={ 47 } bar={ 'test' }>
  <span>{ foo }</span>
  <span>{ bar }</span>
</With>

// nested
<With foo={ 47 }>
  <With bar={ 'test' }>
    <span>{ foo }</span>
    <span>{ bar }</span>
  </With>
</With>
Prop Name Prop Type Required description
any name any type assign prop value to a local variable named by prop name

You may assign multiple variables with a single <With> statement. The defined variable is available only within the <With> block.

Transformation

<With> statements transform to immediately-invoked function expressions:

// before transformation
<With foo={47}>
  <span>{foo}</span>
</With>;

// after transformation
{
  (function(foo) {
    return <span>{foo}</span>;
  }.call(this, 47));
}

Linting

ESLint

Since all control statements are transformed via Babel, no require or import calls are needed. This in turn (well, and some more cases) would lead to warnings or errors by ESLint about undefined variables.

But fortunately you can use this ESLint plugin for JSX-Control-Statements to lint your code.

FlowType

There's still not a perfect solution for FlowType given that it doesn't provide a lot of plugin functionality (at least not yet). Flow definitions are available in jsx-control-statements.latest.flow.js for Flow >= 0.53, or jsx-control-statements.flow.js (deprecated) for Flow < 0.53 - you can pick which file to use like this. These will stop the type checker complaining about statements being undeclared. As of now there's no neat way to make the Flow checker recognise each attributes in <For> loops as a variable - the best workaround for now is something like:

render() {
  declare var eachVariable: string;

  return (
    <For each="eachVariable" of={["hello", "world"]}>
      {eachVariable}
    </For>
  );
}

If you know of a better way to work around this please let us know!

Alternative Solutions

Pure JavaScript

Since everything will be compiled to JavaScript anyway, you might prefer to stick to pure JavaScript solutions.

Conditionals

Probably the most common way for simple conditionals is the use of the && operator:

// simple if
{
  test && <span>true</span>;
}

// additionally the else branch
{
  !test && <span>false</span>;
}

The ternary operator is probably more elegant for if / else conditionals:

// simple
{
  test ? <span>true</span> : <span>false</span>;
}

// with multiple children
{
  test ? (
    [<span key="1">one</span>, <span key="2">two</span>]
  ) : (
    <span>false</span>
  );
}

Another approach is to refactor your conditional into a function:

testFunc(condition){
  if(condition) {
    return <span>true</span>;
  }
  else {
    return <span>false</span>
  }
}

render() {
  return (
    <div>{ testFunc(test) }</div>
  )
}

Loops

Not many options here:

{
  items.map(function(item) {
    <span key={item.id}>{item.title}</span>;
  });
}

Comparison

Arguments pro JSX-Control-Statements in comparison to pure JS solutions:

  • More intuitive and easier to handle for designers and people with non-heavy JS background
  • JSX does not get fragmented by JS statements
  • Better readability and neatness, but that probably depends on you

Cons:

  • Penalty on build-time performance
  • Depends on Babel 6
  • Some Babel configuration

React Components

There are a reasonable amount of React components for conditionals (e.g. react-if, which inspired this in the first place), JSX-Control-Statements is the only approach we know of that avoids execution of all branches (see the intro section), and there seems to be no other component-based solution to looping - while it would be possible to make a component that renders everything in props.children for every element of an array, you'd have to access the members of the array in that component instead of the one that uses it.

For more discussion on If in React by the react team, have a look at reactjs/react-future#35.

To sum up:

  • Conditionals don't execute invalid paths
  • Loops with variable references to each element and index are made possible
  • No penalty on runtime performance
  • No import / require statements needed to use control statements
  • It works exactly as JSX is supposed to work: Plain syntactic sugar

Cons:

  • Depends on Babel 6
  • Some Babel configuration
  • Slightly longer build times
  • Requires an extra plugin to work with ESLint

For macros

If you are looking to use macros with it, take a look at jsx-control-statements-macros which is basically the extension of jsx-control-statements with macros support.

What about Typescript?

There's a version for that by @KonstantinSimeonov!

Major Versions

  • 4.x.x is a pure Babel plugin supporting Babel >= 7.
  • 3.x.x is a pure Babel plugin supporting Babel >= 6.
  • 2.x.x was a Babel plugin supporting Babel >= 6, and a set of JSTransform visitors.
  • 1.x.x was a Babel plugin supporting Babel <= 5, and a set of JSTransform visitors.

This used to support both JSTransform and Babel, but as JSTransform is no longer maintained support was dropped. You can find the code for the JSTransform version at https://github.com/AlexGilleran/jsx-control-statements-jstransform.

I Want to Contribute!

Yay! Please read the Contributor's Guide.

jsx-control-statements's People

Contributors

akilansengottaiyan avatar alexgilleran avatar bluelovers avatar dependabot[bot] avatar helarqjsc avatar justafish avatar martinmacko47 avatar nhducit avatar racingtadpole avatar realworldedits376w avatar spoldman avatar texttechne avatar tomitrescak avatar toncherami avatar xfields 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jsx-control-statements's Issues

Choose / Switch syntax instead of ElseIf and Else

Considering the nesting of <ElseIf> and <Else> they're just markers and cannot be indented automatically by IDE's. Correct indentation would look like this:

<If condition={x}>
  IfBlock
  <ElseIf condition={y} />
  ElseIfBlock
  <Else />
  ElseBlock
</If>

So what about a choose statement (JSP style):

<choose>
  <when condition={x}>
    IfBlock
  </when>
  <when condition={y}>
    ElseIfBlock
  </when>
  <otherwise>
    Else
  </otherwise>
</choose>

Or (misusing) switch style:

<switch>
  <case condition={x}>
    IfBlock
  </case>
  <case condition={y}>
    ElseIfBlock
  </case>
  <default>
    Else
  </default>
</choose>

Maybe we should reserve the use of the switch statement instead of "abusing" the syntax. On the other hand we could also allow for an optional parameter for switch which allows exactly that, e.g. expression.

<switch expression={x}>
  <case condition={12}>
    IfBlock
  </case>
  <case condition={"Never mind"}>
    ElseIfBlock
  </case>
  <default>
    Else
  </default>
</choose>

In any case I would tend to make otherwise / default optional, so that users are not forced to switch between if/else and switch syntax. Implementation-wise we would simply make use of the ternary operator. @AlexGilleran what do you think?

update babel

The version of babel that you have in your package.json is outdated. It uses a module called chokidar which requires the fsevents module which will not build correctly on node 4.0.0

Support eslint

I appreciate this module and its various connectors. I use it well in an isomorphic app builded with gulp/webpack/babel.

The only problem I have is the missing support of the module by lint tools, here is the output with eslint enhance with the react plugin:

if.jsx
  74:9  error  'If' is not defined  react/jsx-no-undef

for.jsx
  51:11  error  'For' is not defined      react/jsx-no-undef
  53:19  error  "item" is not defined  no-undef
  54:23  error  "item" is not defined  no-undef

I can easily add an exception for If, Else and For but I can't skip the warning for the iterator variable (<For each="item" ...).

Have you faced this problem before ? Is there an eslint plugin (existing or planned) ?

Better documentation

We should put some effort into making the documentation better:

  • Rewrite the introduction: Purpose of JSX, that it is not a dedicated template language and why control statements are a perfect fit to extend the language
  • Add a introductory section which explains pro and cons of JSX-Control-Statements, especially in comparison to other libraries which try to implement conditionals (see #36)
  • Cleaner API documentation for control statements. Use a table layout.
  • Add a section for alternative, pure JS approaches for conditionals

Should <Else /> be deprecated?

It made sense to me at the very start when I was just writing this for me, but at this point I think it's outlived its welcome. It's bad JSX/XML semantics, it breaks auto-formatting and we have a much nicer construct for this kind of thing in Choose/Otherwise now.

We obviously should keep If because it's useful for single-branch conditionals - I'm thinking maybe just drop Else from the documentation as we redo it, making extra sure to point out that Choose/When/Otherwise is equivalent to If/Else with cleaner semantics.

Export dummy components for <If /> ... etc

At the moment, I need to declare types as globals to use jsx-control-statements, e.g.

declare var If: any;

Otherwise, I will get Flow/ESLint error that If variable is not defined.

Instead, it would be nice if I could simply import a dummy component from jsx-control-statements, e.g.

import {If} from 'jsx-control-statements';

The plugin "jsx-control-statements/babel" didn't export a Plugin instance

Hi

Thanks for this nice solution! It really helps to clear up the code.
I have a bit problem though when deploying the solution with CI which fails on the following message:

The plugin "jsx-control-statements/babel" didn't export a Plugin instance

My config is following:

var babelSettings = {
  stage: 0,
  plugins: ['jsx-control-statements/babel']
};

module.exports = {
  entry: './entry',
  module: {
    loaders: [
      { test: /\.tsx?$/, loader: 'babel?' + JSON.stringify(babelSettings) + '!awesome-typescript', exclude: /node_modules|lib/ },
      { test: /\.jsx?$/, loader: 'babel', query: babelSettings, exclude: /node_modules/ },
      { test: /\.css$/, loader: 'style!css' },
      { test: /\.(png|jpe?g)(\?.*)?$/, loader: 'url?limit=8182' },
      { test: /\.(svg|ttf|woff|woff2|eot)(\?.*)?$/, loader: 'file' }
    ]
  }
};

Any idea what can be wrong? Thanks!

<else-if> support

I often find the need to create an else if. The syntax to do currently is rather ugly:

<If>
    // Condition
</Else>
    <If>
        // First else if
    </Else>
        <If>
            // Second else if
        </Else>
            // Else (first level)
        </If>
    </If
</If>

Can this be done with vanilla React?

Maybe it's possible to create If, For, etc, components in React, without needing to extend JSX? Foe example,

<For items={someArray}>
  <div>...</div>
</For>

Then the For component would see thie child, and clone it for each item passed in to the items prop. It'd work without JSX. I can imagine the code to make that happen.

What are the pros/cons of introducing a compile step versus runtime components?

Upgrading to 3.1.3 appears to be breaking nested <Choose>

This seems to occur when I put a <Choose> directly inside of a <When> when there are no other components. It may occur in many other cases as well. Reverting to 3.1.2 fixed my issue. I can provide the whole code file if requested via email.

Thanks :D

Stack:

build modulesModuleBuildError: Module build failed: TypeError: /ephemeral0/work/2a4d4e3236001935/src/transpile/components/Datasets/View/List/MenuCell.js: Cannot read property 'attributes' of undefined
   at exports.addKeyAttribute (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/util/ast.js:86:22)
   at Object.exports.getSanitizedExpressionForContent (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/util/ast.js:113:7)
   at /home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/chooseStatement.js:30:49
   at Array.reduceRight (native)
   at getBlocks (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/chooseStatement.js:17:25)
   at /home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/chooseStatement.js:65:18
   at PluginPass.visitor.JSXElement (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/index.js:19:26)
...

Code snippet:

<Choose>
    <When condition={ dataset.status === 'COMPELTE' }>
        <Choose>
            <When condition={ preferences.app_mode }>
                ...
            </When>
            <Otherwise>
                ...
            </Otherwise>
        </Choose>
    </When>
    <Otherwise>
        ...
    </Otherwise>
</Choose>

Support For over objects

I was expecting For to be able to iterate over objects and their keys/values, but it doesn't look like that's the case. Could you add support for this?

Make jsx-control-statements work with babel-6

Hi there,

I just love this package. It really improves readability of the jsx code when using control statements. However, the package does not seem to work anymore with babel-6.

Probably due to the babel.Transformer not being present any more. I will try to take a shot at fixing it. Any help would be greatly appreciated.

Mulitple child elements in "If statement" transformation?

Hi,

// before transformation
<If condition={ test }>
  <span>Truth</span>
</If>

// after transformation
{ test ? <span>Truth</span> : null }

How does below code look like after transformation ?
Do you wrap these multiple statements into div?

// before transformation.
// using multiple child elements and / or expressions
<If condition={ true }>
  one
  { "two" }
  <span>three</span>
  <span>four</span>
</If>

Choose-whens are not tested in sequential order

Eg.

    <Choose>
        <When condition={this.props.when1}>
            <span>WhenBlock1</span>
        </When>
        <When condition={this.props.when2}>
            <span>WhenBlock2</span>
        </When>
        <Otherwise>
            <span>OtherwiseBlock</span>
        </Otherwise>
    </Choose>
it('should render the first block when both conditions true', function () {
  var rendered = util.render(Fixture, {when1: true, when2: true});
  expect(rendered).to.match(util.matchTextWithinSpan('WhenBlock1'));
});

fails - it renders WhenBlock2.

Choose statement not working in Typescript

Thanks you or this amazing set of controls that improve the readability of the code. Yet, I have a bit issue since I am a Typescript developer and I cannot make the choose statement to work, since it does not belong to the basic set of jsx statements. The If and For were no issue, since they are in capitals, thus only custom functions. What is the reason behind having "choose" and "when" with small letters? Would it be possible to use both, "Choose" and "choose"? Thanks!

Improved If syntax including ElseIf support

Since I wasn't happy with the choose syntax - hey, we should do better than JSP's! - I thought some more about how to realize the expected syntax. As it turns out Babel is just too powerful to not allow for that:

<If condition={true}>
  ...
</If>
<ElseIf condition={false}>
  ...
</ElseIf>
<Else>
  ...
</Else>

So today, after a fruitful discussion with a colleague, I learned how to traverse paths within Babel to make this happen. I will open a PR soon.

multiple elements in if/else

There's a trick when using ternary expressions in JSX to have multiple adjacent elements by returning an array.

What I'd like to be able to see is for this...

<If condition={something}>
  <p>Hello</p>
  <p>World</p>
</If>

to get transpiled into this...

{something ?
  [
    <p>Hello</p>,
    <p>World</p>
  ]
  : ''
}

Of course it has the same issue as the <For> block (cannot be the root of a component) but I say this would be a worthwhile addition to the transform.

[Suggestion] add prefix "babel-plugin-" to project name

On Babel 7, plugins name on .babelrc became strict to single target: babel-plugin-{plugin-name}

{
  "plugins": [ ...
    "jsx-control-statements", // will find babel-plugin-jsx-control-statements only, and fails
    "module:jsx-control-statements", // will find jsx-control-statements correctly.
  ]
}

just adding installation manual for Babel 7 is simple solution,
but i think renaming jsx-control-statements to babel-plugin-jsx-control-statements is more better.
How do you think about it?

Missing tests

Two minor issues are not covered by the tests. Found them by analyzing istanbul's coverage report:

  • Testing thtat other JSXElement's are not affected by the transform
  • Using unknown attributes are not tested. We could raise a warning in dev mode. This could help in case an optional attribute gets misspelled. This applies only for <For> and index though

nested control statements

looks like the transformer is rigged to support only one level of If/For statements.

this will fail:

<If condition={true}>
  <div>
    <If condition={1}>
      <p>bork</p>
    </If>
  </div>
</If>

Allow for more than one child

Currently it is more a technical than a semantic restriction to allow only one child element within control statements.

We could give up error checking:

if (ifBlock.length > 1) {
  throwError(errors.MULTIPLE_CHILDREN, node, file);
} else if (ifBlock.length === 0) {
  throwError(errors.NO_CHILDREN, node, file);
} else {
  ifBlock = ifBlock[0];
}

Instead something along this lines could work:

ifBlock = ifBlock.length > 1 ?  t.ArrayExpression(ifBlock) : ifBlock[0];

Consequences:

  • In contrast to single elements sometimes additional <span>s are created, but that is how React works
  • keys are required: should they be injected into each element automatically or left to the user (quite unintuitive)?

Any way to make it work with ES7 annotations?

I'm using gulp for js compiling and my task looks like that:

gulp.task('react', function() {
    var b = browserify({
        entries: './src/application.jsx',
        extensions: ['.jsx'],
        debug: true,
        transform: [
          jsxControlStatementsify,
          babelify.configure({stage: 1}),
        ]
    });

    b.bundle().on('error', function(err) {
        $.notify().write(err.toString());
        this.emit("end");
    }).pipe(source('application.js'))
    .pipe(gulp.dest('js/'));
});

jsxControlStatementsify is copy-pasted directly from your documentation on connecting it with gulp. The problem is that I'm using annotations:

@reactMixin.decorate(Reflux.connect(configurationStore))
export default class Sidebar extends React.Component {

Which works perfectly fine with babel, but with your transformer I'm getting the following error:

[12:34:08] gulp-notify: [Gulp notification] while transforming somepath\components\sidebar.jsx:Error: Parse Error: Line 6: Unexpected token ILLEGAL

Is there are a way to make them work together?

Custom Map Function

I'm using https://github.com/rtfeldman/seamless-immutable lib for my data and it doesn't work with the native JS map function. It does work with libs like Ramda or lodash. I considered writing a wrapper component around <For> to extract my immutable structure into a JS array, but that would break some of the optimizations of immutable data.

Would you be open to a PR that either

  1. Uses one of these other lib map functions (which also happen to be more performant (https://jsperf.com/native-map-vs-lodash-map)
  2. Add a prop that takes a custom mapping function.

Thanks

Adjacent <Choose> blocks within same element cause duplicated key

Workaroundable by adding specific keys to the children of the <When> element - see https://github.com/TerriaJS/terriajs/pull/2063/files.

Generates this warning:

warning.js:36 Warning: flattenChildren(...): Encountered two children with the same key, `5:0:$[object Object]`. Child keys must be unique; when two children share a key, only the first child will be used.
    in div (created by MappablePreview)
    in div (created by MappablePreview)
    in div (created by MappablePreview)
    in MappablePreview (created by DataPreview)

For statement and if statement without curly braces

Hi, would you consider adding a flag, in which you specify, whether curly brackets should be rendered or not? Consider this situation:

<For each="item" index="index" of={this.props.visibleItems}>
      { this.renderPracticalItem(Collections.Practicals.findOne(item.practicalId), this.props.schedule._id) }
</For>

In the for loop I need to render elements in which I do not want to render a containing element. The way to do it now is a bit hacky:

<For each="item" index="index" of={this.props.visibleItems}>
      this.renderPracticalItem(Collections.Practicals.findOne(item.practicalId), this.props.schedule._id)
</For>

Remove usage of lodash

Lodash is only used a few times, and all the usages seem to be pretty simple use cases that could be replaced with native functions like Array.prototype.reduce and Array.prototype.forEach. There's no point adding the weight of the entire lodash library :)

Use "undefined" instead of null

Hi, when the ternary operator is created with the null a ? <b/> : null it creates havoc with some react approaches, such as with hot reloading or with react tools. Is it possible to somehow redefine the structure of the generated ternary statement to use undefined or use && if no else exists?

This seems to work fine: a ? <b/> : undefined

Examples of problems:

Release 3.1.0

First of all, since this release would only add new features, but doesn't break anything, I think it should be release version 3.1.0.

Todos

  • Fix #2
  • Documentation of <Choose>
  • Documentation of multiple elements and caveats
  • Create issues for ESLint-Plugin
  • Remove lodash

I think, we should ship 3.1.0 when the ESLint-Plugin is ready.

Drop support for jstransform?

Since jstransform is no longer actively maintained, do you plan to drop support for it?
Would be much easier to maintain and extend.

Trivial else (suggestion/enhancement)

I find I have a common pattern of

<Choose>
  <When condition={foo}>one-liner</When>
  <Otherwise>
    a lot of stuff
  </Otherwise>
</Choose>

It kind of makes me want to just use a ternary; the control statements are neither succinct or clear/intent-expressive.

The one-liner in question will be a marker for “field not present”, an icon, a component, and in some cases even a single space.

An alternative which I think would make sense is something like:

<SomeControlTag condition={!foo} someprop="one-liner">
  a lot of stuff
</SomeControlTag>

While having jsx in props looks weird, it's completely valid, and I've used it a couple of times with react-bootstrap stuff. I wouldn't recommend it for longer values, but it's fine for short stuff. So, you could in fact also have, someprop={<NotFound/>}.


Now, assuming this is a good idea at all, what does the actual tag and prop look like?

My first instinct was to add this to :

<If condition={!foo} else="one-liner">

The problem should be obvious though… having “else” before “then” feels really odd. But I don't know if that objection is strong enough to justify loading the namespace with a new tag that does mostly the same thing as .

Here's a weird alternative, I don't like but I'll write it down anyway:

<If condition={foo} then="one-liner" else>
  stuff
</If>

Anyway. Please discuss 😺

Destructing For

Are there plans to support destructing the each prop in the For control statement? For example something like this:

<For each="{ firstName, lastName }" index="i" of={this.people}>

Implement <Switch> statement

API example:

<switch expression={x}>
  <case value={42}>
    The Answer to the Ultimate Question of Life, the Universe, and Everything
  </case>
  <case value={41} fallThrough={true}>
    close, but ... 
  </case>
  <default>
    wrong!
  </default>
</choose>

By default every case breaks to prevent the common and well known pitfall of this statement and to improve usability. Opt-in for fall-through behavior by explicitly specifiying fallThrough={true} .

Default block is optional.

Valid JSX comments break <Choose>

When using JSX comments inside of a <Choose> block during compilation with babel/webpack an error is presented that only <When> and <Otherwise> are valid.

The error displayed is: Only <Otherwise> and <When> are allowed child elements for <Choose>!

The problem here is that these are not child elements, they are comments in JSX.
https://facebook.github.io/react/docs/jsx-in-depth.html#comments

Current Workaround
The workaround for this is to include them inside the markup for the <When> or <Otherwise>, but this results in poor form. The comment is for this specific block and it's condition - not it's containing elements. We are describing "this is the condition this block of code fulfills" rather than this is what these contain elements do.

Hopefully that is clear and we are enjoying using your control statements in our project so thank you for your work!

Nesting an If alongside an element causes key problems

If you have something like this:

<If condition={aCondition}>
    <span>Blah</span>

    <If condition={anotherCondition}>
        <span>Other Blah</span>
    </If>
</If>

It seems to cause

warning.js:46 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `FeatureInfoSection`. See https://fb.me/react-warning-keys for more information.

Goes away if you do

<If condition={aCondition}>
    <span>Blah</span>

    <If condition={anotherCondition}>
        <span key='otherblah'>Other Blah</span>
    </If>
</If>

Looks like it'll be an annoying fix :(

Looks like <Choose> doesn't work

Hi, this code works pretty well

<If condition={true}> 
  foo
</If>

But this not:

<Choose>
  <When condition={true}>
    bar
  </When>
</Choose>

Unfortunately, there is no any related logs in the console, it just redirects me to the main page of my react app.

Context loss in '<For>', 'this' in undefined

Consider this component

 <Choose>
        <When condition={tripsData.trips.length > 0}>
         {console.log('this', this)} //this is OK
          <For each="trip" of={tripsData.trips}>
            {console.log('this', this)} //this is lost here
            <Collapse isOpened key={`collapsed_ ${trip.id}`}>
              <div className={cx('trip-container')}>
                <Choose>
                  <When condition={trip.isOpen}>
                    <ExpandedTrip
                      cx={cx}
                      id={trip.id}
                      trip={trip}
                      isDebug={isDebug}
                      ticketCount={ticketCount}
                      onSelectSeat={this.onSelectSeat}
                      onTripClose={this.onTripClose}
                      firstStepActions={firstStepActions}
                      secondStepActions={secondStepActions}
                      buttonIsSpinning={buttonIsSpinning}
                    />
                  </When>
                  <Otherwise>
                    <CollapsedTrip
                      cx={cx}
                      id={trip.id}
                      isDebug={isDebug}
                      trip={trip}
                      onTripClick={this.onTripClick}
                    />
                  </Otherwise>
                </Choose>
              </div>
            </Collapse>,
          </For>
        </When>
        <Otherwise>
          <div className={cx('trip')}>
            Not found
          </div>
        </Otherwise>
      </Choose>

In second console.log context is lost.

Add linting

We should add linting to guarantee a consistent code style and get hints for common mistakes. Extending airbnb/eslint rules and adding own customizations is a common approach.

Can't use JSXExpressionContainer as a child

I'm using babel 6.2.1 with version 2.0.1 of the plugin. I'd like to do something along the lines of:

<If condition={foo}>
  {'foo'}
</If>

Or with any expression that can be enclosed in {}. But I get a nasty error instead:

Property consequent of ConditionalExpression expected node to be of a type ["Expression"] but instead got "JSXExpressionContainer" at Object.validate

With/let/def statement: define variables (suggestion/enhancement)

There's this one piece of code where I want to run a method once and bind its result locally. It looks more or less like this:

<For each="item" of={this.state.items}>
  (bunch of stuff)
  <Choose>
    <When condition={this.isExpanded(item)}>
      more stuff
    </When>
    <Otherwise>
      {((summary) => <Choose>
        <When condition={summary}>
          stuff that uses the summary variable
        </When>
        <Otherwise>
          also uses the variable
        </Otherwise>
      </Choose>)(this.getSummaryFields(item))}
    </Otherwise>
  </Choose>
</For>

Kind of makes my eyes bleed, and it's hard to see where the “variable” comes from.

(Sorry if the example is not as minimal as possible, but I wanted to illustrate why I want it to begin with; if it wasn't inside a <For> I could just define it as a const on top of the render method.)

Drawing from the ancient template languages of my young days…

    <Let summary={this.getSummaryFields(item)}>
      <Choose>
        <When condition={summary}>
          stuff that uses the summary variable
        </When>
        <Otherwise>
          also uses the variable
        </Otherwise>
      </Choose>
    </Let>

(Or you could call it <With> or <Def> or a number of other things. Not <Const> though, that would make it look like it's doing something completely different.)

A possible objection is that using prop names this way is iffy, and I guess some are already uncomfortable with the each and index props in <For>. An alternative, arguably more React-y:

<With const={{summary: this.getSummaryFields(item)}}>

It would expand of course to pretty much what I have on top, an in-place function, except no need for trickery with passing things as arguments.

// for clarity, allow me the poetic license of not expanding the other jsx
{(function() {
  const summary = this.getSummaryFields(item);
  return (
    <Choose>
      ...
    </Choose>
  );
})()}

Known issue: expanding it that way means it's limited to containing exactly one child.

FlowType Support

Just making this to keep track of it, I don't have a complete solution at this stage.

FlowType doesn't like JSX-Control-Statements in two ways - the first is that we're effectively using global types which is easily fixed with a type declaration:

// @flow

type ForType = ReactClass<{each: string, index: string, of: any}>;

declare var For: ForType;

But the problem is the "each" and "index" of the For won't be known to FlowType and it'll not only fail, but also not support type checking which sucks.

Best I have so far is to just declare those variables in the render function with the right type.

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.