How to bind to and use a higher-order component in ReasonReact - interop

Let's say I have a higher-order component, something like the following trivial definition, exported from the JavaScript module ./hoc.js:
export const withStrong =
Component => props =>
<strong> <Component ...props/> </strong>
Assuming I have some component called HelloMessage, what is the equivalent of this piece of JavaScript:
import { withStrong } from './hoc.js';
const HelloMessage = ...
const StrongMessage = withStrong(HelloMessage);
ReactDOM.render(
<StrongMessage name="Joe" />,
document.getElementById('react-app')
);

TL;DR:
This should be the exact equivalent of the requested JavaScript snippet:
[#bs.module ./hoc.js]
external withStrong
: React.component('props) => React.component('props)
= "withStrong";
module HelloMessage = ...
module StrongMessage = {
include HelloMessage;
let make = withStrong(make);
};
ReactDOMRe.renderToElementWithId(
<StrongMessage name="Joe" />,
"react-app"
);
There'a also a runnable example on the Reason playground with a few adaptations made to work around not having a separate JavaScript file.
Explanation follows:
Binding
withStrong is just a function. It happens to be a function that accepts and returns a react component, which is a bit mysterious, but they're really just values like any other. We can just bind it like an ordinary function.
Even something as simple as this would work
[#bs.module ./hoc.js]
external withStrong : 'a => 'a = "withStrong";
assuming you always make sure to pass in a component. But it wouldn't be particularly safe as you can pass it anything else too, so let's try to use the type system as it should be used, restricting it to only accept react components.
The ReasonReact source code says components have the type component('props), so that's what we'll use.
[#bs.module ./hoc.js]
external withStrong
: React.component('props) => React.component('props)
= "withStrong";
Using the 'props type variable in both the argument and return type means we constrain them to be the same. That is, the returned component will have exactly the same props as the one passed in, which is exactly what we want in this case.
And that's really all there is to the binding itself. we can now use it like this:
let strongMessage = withStrong(HelloMessage.make);
Unfortunately this doesn't support JSX. To render strongMessage as is we'd have to write something like
React.createElementVariadic(strongMessage, { "name": "Joe" }, [||]);
Not great. So let's fix that.
JSX
<StrongMessage name="Joe" />
transforms to
React.createElementVariadic(
StrongMessage.make,
StrongMessage.makeProps(~name="Joe", ()),
[||]
);
So we need a StrongMessage module with two functions, make and makeProps that conform to what's expected by React.createElementVariadic. make is just the component itself, so that's simple enough. makeProps is a function that acccepts the props as labeled arguments terminated by unit (since the props may be optional) and returns a js object. This also happens to be exactly what [#bs.obj] does, which isn't in any way coincidental.
Putting this together then, we get:
module StrongMessage = {
let make = withStrong(HelloMessage.make);
[#bs.obj]
external makeProps
: (~name: string, unit) => {. "name" string }
= "";
}
And that's it! Yay!
Addendum: Shortcuts
Ok, so the makeProps function is a bit of an annoying mouthful. Fortunately in our case, where the props of the wrapped component is the same as the original, it's also unnecessary since StrongMessage.makeProps will be identical to HelloMessage.makeProps. Let's just steal that then! And now we have
module StrongMessage = {
let make = withStrong(HelloMessage.make);
let makeProps = HelloMessage.makeProps;
}
But we can do even better! By using include HelloMessage we can drop makeProps entirely (thanks to #bloodyowl, via #idkjs, for this one).
module StrongMessage = {
include HelloMessage;
let make = withStrong(make);
}
That's pretty nice, isn't it? This works because include HelloMessage will include all the exported definitions from HelloMessage such as makeProps, but also make and anything else. This is probably what you want when you wrap a component in this way, but beware that it imports and re-exports everything from the included module, in case that's not what you want.
Usage
Finally, once we have both the binding and JSX in order we can use it like this
ReactDOMRe.renderToElementWithId(
<StrongMessage name="Joe" />,
"react-app"
);

Related

Access JSON chunk exported from Gatsby Static Query

I have a React Component in a Gatsby app that is using the useStaticQuery hook to pull in data from the GraphQL layer. This component gets used in my application, but it also gets used as part of a JavaScript embed/widget that is created in a separate Webpack configuration.
I don't want the widget to depend on Gatsby, so I've shimmed the relevant bits of Gatsby, but I still need to pass in data to the shim I've created for useStaticQuery. I found that my Gatsby app is generating a file at public/static/d/2250905522.json that contains a perfect representation of the query data, and I'd like to use it like so:
// This file gets substituted when importing from `gatsby`
import queryResult from "../public/static/d/2250905522.json"
export const useStaticQuery = () => queryResult.data
export const graphql = () => {}
This works, but I haven't figured out where this is coming from or how to determine the file name in a way that is deterministic/stable. How is Gatsby determining this file name, and what internals might I use to do the same?
Edit: I found this routine in the Gatsby codebase that appears to be using staticQueryComponent.hash to determine the number. staticQueryComponent is being destructured from store.getState() where store is associated with Redux, but I'm still not sure where the hash is being determined yet.
Edit 2: Found another mention of this in the documentation here. It sounds like hash is a hash of the query itself, so this will change over time if the query changes (which is likely), so I'm still looking for the routine used to compute the hash.
Due to changes in the babel-plugin-remove-graphql-queries, coreyward's (awesome) answer should be updated to:
const { stripIgnoredCharacters } = require('graphql/utilities/stripIgnoredCharacters');
const murmurModule = require('babel-plugin-remove-graphql-queries/murmur');
const murmurhash = typeof murmurModule === 'function' ? murmurModule : murmurModule.murmurhash;
const GATSBY_HASH_SEED = 'abc';
function hashQuery(query) {
const result = murmurhash(stripIgnoredCharacters(query), GATSBY_HASH_SEED).toString();
return result;
}
module.exports = hashQuery;
The changes are:
fix the way murmurhash is imported. Credit to github user veloce, see: https://github.com/birkir/gatsby-source-graphql-universal/pull/16/files
Change to using stripIgnoredCharacters in order to match the updated way that gatsby internally hashes queries by first stripping whitespace and comment lines for efficiency.
Gatsby is using murmurhash with a seed of "abc" to calculate the hash of the full text of the query (including whitespace). This occurs in babel-plugin-remove-graphql-queries.
Since the reused components are isolated from Gatsby, the graphql tagged template literal can be shimmed in order to get the original query for hashing:
// webpack.config.js
module.exports = {
resolve: {
alias: {
gatsby: path.resolve(__dirname, "gatsby-shim.js"),
},
},
}
// gatsby-shim.js
import { murmurhash } from "babel-plugin-remove-graphql-queries/murmur"
import {
stripIgnoredCharacters,
} from "graphql/utilities/stripIgnoredCharacters"
const GATSBY_HASH_SEED = "abc"
const hashQuery = (query) =>
murmurhash(
stripIgnoredCharacters(query),
GATSBY_HASH_SEED
).toString()
export const graphql = query => hashQuery(query.raw[0])
This results in the query hash being passed into useStaticQuery, which can be shimmed similarly to retrieve the cached query from disk.
Also worth noting, newer versions of Gatsby store the StaticQuery result data in public/page-data/sq/d/[query hash].json.
If you're looking to do something similar, I've written up a much longer blog post about the details of this process and the solution I arrived at here.

Passing parameters from Command to Converter

I defined a new type of model element as a plug-in; let's refer to it as Foo. A Foo node in the model should translate to a section element in the view. So far, so good. I managed to do that by defining simple conversion rules. I also managed to define a new FooCommand that transforms (renames) selected blocks to Foo.
I got stuck trying to have attributes on those Foo model nodes be translated to attributes on the view elements (and vice-versa). Suppose Foos have an attribute named fooClass which should map to the view element's class attribute.
<Foo fooClass="green-foo"> should map to/from <section class="green-foo">
I can successfully receive parameters in FooCommand, but I can't seem to set them on the blocks being processed by the command:
execute(options = {}) {
const document = this.editor.document;
const fooClass = options.fooClass;
document.enqueueChanges(() => {
const batch = options.batch || document.batch();
const blocks = (options.selection || document.selection).getSelectedBlocks();
for (const block of blocks) {
if (!block.is('foo')) {
batch.rename(block, 'foo');
batch.setAttribute(block, 'fooClass', fooClass);
}
}
});
}
Below is the code for the init function in the Foo plugin, including the model→view and view→model conversions:
init() {
const editor = this.editor;
const doc = editor.document;
const data = editor.data;
const editing = editor.editing;
editor.commands.add('foo', new FooCommand(editor));
doc.schema.registerItem('foo', '$block');
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
const fooClass = modelElement.item.getAttribute('fooClass'));
return new ContainerElement('section', {'class': fooClass});
});
buildViewConverter().for(data.viewToModel)
.fromElement('section')
.toElement(viewElement => {
let classes = Array.from(viewElement.getClassNames());
let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
return modelElement;
});
}
When I try to run the command via
editor.execute('foo', { fooClass: 'green-foo' })
I can see that the green-foo value is available to FooCommand, but the modelElement in the model→view conversion, on the other hand, has no fooClass attribute.
I'm sure I'm missing the point here and misusing the APIs. I'd be really thankful if someone could shed some light on this issue. I can provide more details, as needed.
Follow-up after initial suggestions
Thanks to #Reinmar and #jodator for their suggestion regarding configuring the document schema to allow for the custom attribute. I really thought that would have taken care of it, but no. It may have been a necessary step anyway, but I'm still unable to get the attribute value from the model element during the model→view conversion.
First, let me add an important piece of information I had left out: the CKEditor5's version I'm working with is 1.0.0-alpha2. I am aware several of the APIs are bound to change, but I would still like to get things working with the present version.
Model→view conversion
If I understand it correctly, one can either pass a string or a function to the toElement call. A question about using the latter: what exactly are the parameters passed to the function? I assumed it would be the model element (node?) to be converted. Is that the case? If so, why is the attribute set on that node via batch.setAttribute (inside a document.enqueueChanges) not available when requested? Should it be?
A sequencing problem?
Additional testing seems to indicate there's some kind of order-of-execution issue happening. I've observed that, even though the attribute is not available when I first try to read it from the modelElement parameter, it will be so if I read it again later. Let me try to illustrate the situation below. First, I'll modify the conversion code to make it use some dummy value in case the attribute value is not available when read:
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
let viewElement = new ContainerElement('section');
viewElement.setAttribute('class', fooClass);
return viewElement;
});
Now I reload the page and execute the following instructions on the console:
c = Array.from(editor.document.getRoot().getChildren());
c[1].is('paragraph'); // true
// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
const batch = editor.document.batch();
batch.rename(c[1], 'foo');
batch.setAttribute(c[1], 'fooClass', 'green-foo');
return batch;
});
c[1].is('paragraph'); // false
c[1].is('foo'); // true
c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'
Even though it looks like the expected output is being produced, a glance at the generated view element shows the problem:
<section class="naught"/>
Lastly, even if I try to reset the fooClass attribute on the model element, the change is not reflected on the view element. Why is that? Shouldn't changes made via enqueueChanges cause the view to update?
Sorry for the very long post, but I'm trying to convey as many details as I can. Here's hoping someone will spot my mistake or misunderstanding of how the CKEditor 5's API actually works.
View not updating?
I turned to Document's events and experimented with the changesDone event. It successfully addresses the "timing" issue, as it consistently triggers only after all changes have been processed. Still, the problem of the view not updating in response to a change in the model remains. To make it clear, the model does change, but the view does not reflect that. Here is the call:
editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
To be 100% sure I wrote the whole feature myself. I use the 1.0.0-beta.1 API which is completely different than what you had.
Basically – it works. It isn't 100% correct yet, but I'll get to that.
How to convert an element+attribute pair?
The thing when implementing a feature which needs to convert element + attribute is that it requires handling the element and attribute conversion separately as they are treated separately by CKEditor 5.
Therefore, in the code below you'll find that I used elementToElement():
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
So a converter between model's <foo> element and view's <section> element. This is a two-way converter so it handles upcasting (view -> model) and downcasting (model -> view) conversion.
NOTE: It doesn't handle the attribute.
Theoretically, as the view property you could write a callback which would read the model element's attribute and create view element with this attribute set too. But that wouldn't work because such a configuration would only make sense in case of downcasting (model -> view). How could we use that callback to downcast a view structure?
NOTE: You can write converters for downcast and upcast pipelines separately (by using editor.conversion.for()), in which case you could really use callbacks. But it doesn't really make sense in this case.
The attribute may change independently!
The other problem is that let's say you wrote an element converter which sets the attribute at the same time. Tada, you load <section class=ohmy> and gets <foo class=ohmy> in your model.
But then... what if the attribute will change in the model?
In the downcast pipeline CKEditor 5 treats element changes separately from attribute changes. It fires them as separate events. So, when your FooCommand is executed on a heading it calls writer.rename() and we get the following events in DowncastDispatcher:
remove with <heading>
insert:section
But then the attribute is changed too (writer.setAttribute()), so we also get:
setAttibute:class:section
The elementToElement() conversion helper listens to insert:section event. So it's blind to setAttribute:class:selection.
Therefore, when you change the value of the attribute, you need the attributeToAttribute() conversion.
Sequencing
I didn't want to reply to your question before we released 1.0.0-beta.1 because 1.0.0-beta.1 brought the Differ.
Before 1.0.0-beta.1 all changes were converted immediately when they were applied. So, rename() would cause immediate remove and insert:section events. At this point, the element that you got in the latter one wouldn't have the class attribute set yet.
Thanks to the Differ we're able to start the conversion once all the changes are applied (after change() block is executed). This means that the insert:section event is fired once the model <foo> element has the class attribute set already. That's why you could write a callback-based converters... bur you shouldn't :D
The code
import { downcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
class FooCommand extends Command {
execute( options = {} ) {
const model = this.editor.model;
const fooClass = options.class;
model.change( writer => {
const blocks = model.document.selection.getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'foo' ) ) {
writer.rename( block, 'foo' );
writer.setAttribute( 'class', fooClass, block );
}
}
} );
}
}
class FooPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'foo', new FooCommand( editor ) );
editor.model.schema.register( 'foo', {
allowAttributes: 'class',
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
editor.conversion.for( 'upcast' ).add(
upcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
editor.conversion.for( 'downcast' ).add(
downcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
// This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((
// EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
// editor.conversion.attributeToAttribute( {
// model: {
// name: 'foo',
// key: 'class'
// },
// view: {
// name: 'section',
// key: 'class'
// }
// } );
}
}
This code works quite well, except the fact that it converts the class attribute on any possible element that has it. That's because I had to use very generic downcastAttributeToAttribute() and upcastAttributeToAttribute() converters because of a bug that I found (EDIT: it's fixed and will be available in 1.0.0-beta.2). The commented out piece of code is how you it should be defined if everything worked fine and it will work in 1.0.0-beta.2.
It's sad that we missed such a simple case, but that's mainly due to the fact that all our features... are much more complicated than this.

how to ignore ordering of reselect selectors when composing selectors

As I compose more selectors together, I'm finding that I'm reordering where the selectors are defined. For example,
export const selectNav = state => state.nav;
export const selectPage = state => state.page;
export const selectNavAndPage = createSelector([
selectNav,
selectPage,
], (nav, page) => {
});
export const selectFoo = state => state.foo;
export const selectNavAndPageAndFoo = createSelector([
selectNavAndPage,
selectFoo,
], (navAndPage, foo) => {
});
This is a simple example, but I could not define selectNavAndPage below selectNavAndPageAndFoo. As more selectors get composed and selectors of selectors get composed, then I need to make sure all the sub-selectors are defined at the top before I use them.
Is there some way to create these selectors such that ordering doesn't matter?
I was worried about the same problem and I created this npm module define-selectors. This is a module that delays the definition of the selector to solve the ordering of selector definition problem and adds other features to it. It has not been stable yet, but I will use it on my project to make it stable and be improved.
For more information please go to github page and read the README and source files.
I'm pretty sure this is just related to how the ES6 const keyword works. With const, variables do not exist until that line, so if you want to reference a const variable, you need to write that code after the variable declaration. With var, all variables are hosted to the top of the scope.
So, either use var so that you can reference things out of order, or continue using const and define each function in the correct order for usage and references.
If you don't mind a little extra typing, here is another approach which requires defining a 'cms' utility function that wraps the createSelector function and takes advantage of function hoisting:
import {createSelector} from 'reselect';
// create memoized selector
function cms(ctx, ...args) {
if (!ctx.selector) ctx.selector = createSelector(...args);
return ctx.selector;
}
// define the selectors out of order...
export function getBaz(state) {
return cms(
getBaz // the function itself as context
, getBar
, bar => bar.baz
)(state);
}
export function getBar(state) {
return cms(
getBar
, getFoo
, foo => foo.bar
)(state);
}
export function getFoo(state) {
return state.foo;
}
This is not as elegant as simply defining the selectors in order, but maybe someone else can take this idea and improve on it.

When to use asObservable() in rxjs?

I am wondering what is the use of asObservable:
As per docs:
An observable sequence that hides the identity of the
source sequence.
But why would you need to hide the sequence?
When to use Subject.prototype.asObservable()
The purpose of this is to prevent leaking the "observer side" of the Subject out of an API. Basically to prevent a leaky abstraction when you don't want people to be able to "next" into the resulting observable.
Example
(NOTE: This really isn't how you should make a data source like this into an Observable, instead you should use the new Observable constructor, See below).
const myAPI = {
getData: () => {
const subject = new Subject();
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subject.next({ type: 'message', data });
source.onOtherMessage = (data) => subject.next({ type: 'othermessage', data });
return subject.asObservable();
}
};
Now when someone gets the observable result from myAPI.getData() they can't next values in to the result:
const result = myAPI.getData();
result.next('LOL hax!'); // throws an error because `next` doesn't exist
You should usually be using new Observable(), though
In the example above, we're probably creating something we didn't mean to. For one, getData() isn't lazy like most observables, it's going to create the underlying data source SomeWeirdDataSource (and presumably some side effects) immediately. This also means if you retry or repeat the resulting observable, it's not going to work like you think it will.
It's better to encapsulate the creation of your data source within your observable like so:
const myAPI = {
getData: () => return new Observable(subscriber => {
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subscriber.next({ type: 'message', data });
source.onOtherMessage = (data) => subscriber.next({ type: 'othermessage', data });
return () => {
// Even better, now we can tear down the data source for cancellation!
source.destroy();
};
});
}
With the code above, any behavior, including making it "not lazy" can be composed on top of the observable using RxJS's existing operators.
A Subject can act both as an observer and an observable.
An Obervable has 2 methods.
subscribe
unsubscribe
Whenever you subscribe to an observable, you get an observer which has next, error and complete methods on it.
You'd need to hide the sequence because you don't want the stream source to be publicly available in every component. You can refer to #BenLesh's example, for the same.
P.S. : When I first-time came through Reactive Javascript, I was not able to understand asObservable. Because I had to make sure I understand the basics clearly and then go for asObservable. :)
In addition to this answer I would mention that in my opinion it depends on the language in use.
For untyped (or weakly typed) languages like JavaScript it might make sense to conceal the source object from the caller by creating a delegate object like asObservable() method does. Although if you think about it it won't prevent a caller from doing observable.source.next(...). So this technique doesn't prevent the Subject API from leaking, but it indeed makes it more hidden form the caller.
On the other hand, for strongly typed languages like TypeScript the method asObservable() doesn't seem to make much sense (if any).
Statically typed languages solve the API leakage problem by simply utilizing the type system (e.g. interfaces). For example, if your getData() method is defined as returning Observable<T> then you can safely return the original Subject, and the caller will get a compilation error if attempting to call getData().next() on it.
Think about this modified example:
let myAPI: { getData: () => Observable<any> }
myAPI = {
getData: () => {
const subject = new Subject()
// ... stuff ...
return subject
}
}
myAPI.getData().next() // <--- error TS2339: Property 'next' does not exist on type 'Observable<any>'
Of course, since it all compiles to JavaScript in the end of the day there might still be cases when you want to create a delegate. But my point is that the room for those cases is much smaller then when using vanilla JavaScript , and probably in majority of cases you don't need that method.
(Typescript Only) Use Types Instead of asObservable()
I like what Alex Vayda is saying about using types instead, so I'm going to add some additional information to clarify.
If you use asObservable(), then you are running the following code.
/**
* Creates a new Observable with this Subject as the source. You can do this
* to create customize Observer-side logic of the Subject and conceal it from
* code that uses the Observable.
* #return {Observable} Observable that the Subject casts to
*/
asObservable(): Observable<T> {
const observable = new Observable<T>();
(<any>observable).source = this;
return observable;
}
This is useful for Javascript, but not needed in Typescript. I'll explain why below.
Example
export class ExampleViewModel {
// I don't want the outside codeworld to be able to set this INPUT
// so I'm going to make it private. This means it's scoped to this class
// and only this class can set it.
private _exampleData = new BehaviorSubject<ExampleData>(null);
// I do however want the outside codeworld to be able to listen to
// this data source as an OUTPUT. Therefore, I make it public so that
// any code that has reference to this class can listen to this data
// source, but can't write to it because of a type cast.
// So I write this
public exampleData$ = this._exampleData as Observable<ExampleData>;
// and not this
// public exampleData$ = this._exampleData.asObservable();
}
Both do the samething, but one doesn't add additional code calls or memory allocation to your program.
❌this._exampleData.asObservable();❌
Requires additional memory allocation and computation at runtime.
✅this._exampleData as Observable<ExampleData>;✅
Handled by the type system and will NOT add additional code or memory allocation at runtime.
Conclusion
If your colleagues try this, referenceToExampleViewModel.exampleData$.next(new ExampleData());, then it will be caught at compile time and the type system won't let them, because exampleData$ has been casted to Observable<ExampleData> and is no longer of type BehaviorSubject<ExampleData>, but they will be able to listen (.subscribe()) to that source of data or extend it (.pipe()).
This is useful when you only want a particular service or class to be setting the source of information. It also helps with separating the input from the output, which makes debugging easier.

Which ReactJS syntax to use; React.createClass or ES6 extends?

I'm beginner of ReactJS. I learned and studied a lot of documents and ebooks on various websites. I realize there are two syntaxes for ReactJS. Example:
React.createClass({
displayName: 'Counter',
getDefaultProps: function(){
return {initialCount: 0};
},
getInitialState: function() {
return {count: this.props.initialCount}
},
propTypes: {initialCount: React.PropTypes.number},
tick() {
this.setState({count: this.state.count + 1});
},
render() {
return (
<div onClick={this.tick}>
Clicks: {this.state.count}
</div>
);
}
});
And this version is written by ES6:
class Counter extends React.Component {
static propTypes = {initialCount: React.PropTypes.number};
static defaultProps = {initialCount: 0};
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
state = {count: this.props.initialCount};
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div onClick={this.tick.bind(this)}>
Clicks: {this.state.count}
</div>
);
}
}
What is the better way to use ReactJS? But I found these libraries, application on github used to perform a lot ES6.
The second approach is probably the correct one to adopt going forward as Facebook have said they will ultimately deprecate the React.createClass approach.
From the React v0.13 release notes:
Our eventual goal is for ES6 classes to replace React.createClass completely, but until we have a replacement for current mixin use cases and support for class property initializers in the language, we don't plan to deprecate React.createClass
Personally I think the second approach also makes for easier to read code, but that is obviously a more subjective reason.
However, as stated above, it's important to note that the ES6 format does not support Mixins, so if you need a mixin you need to use the createClass format for that component.
This post "React.createClass versus extends React.Component" by Todd Motto has some good information on the difference between the two syntaxes. It's worth reading that for a discussion of how the this keyword behaves differently between the two syntaxes.
Edit: Dan Caragea's post below makes some excellent points that should definitely be considered too.
A third alternative...
There is a also a third way of defining a React component, called 'Stateless Functions' in the React Documentation and often called 'Stateless Functional Components' or 'Functional Stateless Components'. This is the example from the docs:
function HelloMessage(props) {
return <div>Hello {props.name}</div>;
}
Defining the component as a function means it is effectively created anew each time and so has no ongoing internal state. This makes the component easier to to reason about, and to test, as the component's behaviour will always be identical for a given set of properties (props), rather than potentially varying from run-to-run due the values of the internal state.
This approach works particularly well when using a separate State management approach such as Redux and ensures that Redux's time-travel will produce consistent results. Functional Stateless Components also make implementing features like undo/redo simpler.
I have done React.createClass for work and ES6 classes for my pet project. I do find the latter easier to read too but I often miss the simplicity/peace of mind I have with the former.
With the class based approach, do note that, technically, the statically defined propTypes and defaultProps are ES7, not ES6 - which might change until ES7 is finalized.
A pure ES6 approach would be to declare propTypes/defaultProps like
class Counter extends React.Component {
...
}
Counter.propTypes = {...};
Counter.defaultProps = {...};
You also have to remember to bind onClick in render (or any other method where you need to use this). It's almost certain you will forget to in some places. While with createClass all calls are auto-bound by React.
Another ES7 proposal could make things easier but you'd still need to remember to write it everywhere:
<div onClick={::this.tick}> which binds this to tick.
Of course, you'd have to opt in to stage 0 in babel config to make use of all these ES7 proposals.
About mixins...there are acceptable ways of using mixins with classes. A brilliant approach is mixWith.js but you could also try ES7 decorators, HOCs, even Object.assign() :)
At the end of the day, I feel that the class approach doesn't bring anything of real value and you could go the old and paved way of createClass until you have a good understanding of React. Then you can toy around with classes and ES6/7/100. It will be a long while before they deprecate createClass.
I started with React ES6 + staging style and it sounds nice when you say in React everything is a component. I like this class organization.
Because only methods can be defined inside ES6 class if you want to define properties in pure ES6 they have to be outside of the class. Like for instance in here:
export class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div onClick={this.tick.bind(this)}>
Clicks: {this.state.count}
</div>
);
}
}
Counter.propTypes = { initialCount: React.PropTypes.number };
Counter.defaultProps = { initialCount: 0 };
This is why in ES6 propTypes section is outside of the class definition.
But if you use ES7 you can define static and non-static properties without a problem inside of the class.
// ES7
export class Counter extends React.Component {
static propTypes = { initialCount: React.PropTypes.number };
static defaultProps = { initialCount: 0 };
state = { count: this.props.initialCount };
tick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div onClick={this.tick.bind(this)}>
Clicks: {this.state.count}
</div>
);
}
}
With ES7 you can use implicit binding tips for methods also as demonstrated in here:
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
The old pre React v0.13 style or ES5 style was like this.
In React.createClass all methods are bound this automatically.
This can be a little confusing for JavaScript developers because this is not native JavaScript behavior.
This is whe write:
class Counter extends React.Component {
constructor() {
super();
this.tick = this.tick.bind(this);
}
tick() {
...
}
...
}
or use some tricks to accomplish the same using property initializer syntax.
class Counter extends React.Component {
tick = () => {
...
}
...
}
Conclude
For the newcomers, I think the latest style is a better choice.
About Mixins
As stated here:
ES6 launched without any mixin support. Therefore, there is no support for mixins when you use React with ES6 classes.
We also found numerous issues in codebases using mixins, and don't recommend using them in the new code.
This section exists only for the reference.

Resources