Skip to content
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 guide on integrating with non-react code #9316

Merged
merged 42 commits into from Apr 26, 2017
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
675c0bb
Add guide on integrating with non-react code
wacii Apr 2, 2017
995b2ba
Capitalize guide title
wacii Apr 2, 2017
ec88f4b
Make links to other docs relative
wacii Apr 2, 2017
687edf3
Rephrase 'What it does do'
wacii Apr 2, 2017
9f2c2c7
Remove experimental syntax
wacii Apr 2, 2017
a3a1934
Capitalize Backbone
wacii Apr 2, 2017
236deef
Remove empty lifecycle method in generic jQuery example
wacii Apr 2, 2017
aee0690
Use shouldComponentUpdate() not componentWillUpdate()
wacii Apr 2, 2017
ba315b2
Prefer single quotes
wacii Apr 2, 2017
73f58b2
Add cleanup to generic jQuery example
wacii Apr 2, 2017
b0047ae
Capitalize React
wacii Apr 2, 2017
1077a1c
Generalize the section on Backbone Views
wacii Apr 3, 2017
8397119
Generalize the section on Backbone Models, a little
wacii Apr 3, 2017
5c2f363
Add introduction
wacii Apr 3, 2017
2c778e7
Adjust wording
wacii Apr 3, 2017
933fd5d
Simplify ref callbacks
wacii Apr 3, 2017
8f7e2bb
Fix typo in generic jQuery example
wacii Apr 3, 2017
2fb9b30
Fix typos in Backbone models in React components
wacii Apr 3, 2017
541002a
Fix more typos in Backbone models in React components
wacii Apr 3, 2017
00bb018
Add generic section on integrating with other view libraries
wacii Apr 3, 2017
e9bd247
Stress the benefits of an unchanging React element
wacii Apr 3, 2017
7811e88
Small changes to introduction
wacii Apr 4, 2017
cc92dc6
Add missing semicolon
wacii Apr 11, 2017
ac2134a
Revise generic jQuery wrapper section
wacii Apr 12, 2017
d52245f
Add usage example for Chosen wrapper
wacii Apr 12, 2017
931b051
Prevent Chosen wrapper from updating
wacii Apr 19, 2017
4acd3fd
Note that sharing the DOM with plugins is not recommended
wacii Apr 19, 2017
76f42e5
Mention how React is used at Facebook
wacii Apr 19, 2017
f1758eb
Mention React event system in template rendering section
wacii Apr 19, 2017
f0a4e02
Remove destructuring from function parameters
wacii Apr 19, 2017
6f6a9d2
Do not name React components Component
wacii Apr 19, 2017
4bf0523
Elaborate on unmountComponentAtNode()
wacii Apr 19, 2017
ccb18fc
Mention preference for unidirectional data flow
wacii Apr 19, 2017
8967b35
Rename backboneModelAdapter
wacii Apr 19, 2017
74a6463
Replace rest syntax
wacii Apr 19, 2017
d9bc886
Respond to updated model in connectToBackboneModel
wacii Apr 19, 2017
e502df1
Rewrite connectToBackboneModel example
wacii Apr 19, 2017
75b0abd
Rework connectToBackboneModel example
wacii Apr 20, 2017
38dd65c
Misc changes
wacii Apr 20, 2017
9d2354e
Misc changes
wacii Apr 21, 2017
8a643b3
Change wording
wacii Apr 21, 2017
92a1c37
Tweak some parts
gaearon Apr 26, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/_data/nav_docs.yml
Expand Up @@ -50,6 +50,8 @@
title: Web Components
- id: higher-order-components
title: Higher-Order Components
- id: integrating-with-other-libraries
title: Integrating with Other Libraries
- title: Reference
items:
- id: react-api
Expand Down
270 changes: 270 additions & 0 deletions docs/docs/integrating-with-other-libraries.md
@@ -0,0 +1,270 @@
---
id: integrating-with-other-libraries
title: Integrating with other libraries
permalink: docs/integrating-with-other-libraries.html
---

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a one-paragraph summary for this page? Something that says that React can be used in any application, and summarizing two methods below.

React can be used in any web application. It can be embedded in other applications and, with a little care, other applications can be embedded in React. This guide will examine some of the more common use cases, focusing on integration with jQuery and Backbone.

## Integrating with DOM Manipulation Plugins

React is unaware of changes made to the DOM outside of React. It determines updates based on its own internal representation, and if those updates are invalidated, React has no way to recover.

This does not mean it is impossible or even necessarily difficult to combine React with other ways of affecting the DOM, you just have to be mindful of what each are doing.

The easiest way to avoid conflicts is to prevent the React component from updating. This can be done explicitly by returning false from [`shouldComponentUpdate()`](/react/docs/react-component.html#shouldcomponentupdate), or by rendering elements that have no reason to change.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"that has no reason to change" is not really explained. Maybe add "like an empty <div />"?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I see that you explained it later, but generally people stumble where they're confused, and don't read past it because they're not sure if they'll be able to follow the rest. It's better to explain before rather than after the code.


```js
class SomePlugin extends React.Component {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We jump into this code example too fast. It's worth adding a sentence before, e.g. "For example, a component rendering a jQuery plugin could look like this:"

componentDidMount() {
this.$el = $(this.el);
this.$el.somePlugin();
}

componentWillUnmount() {
this.$el.somePlugin('destroy');
}

render() {
return <div ref={el => this.el = el} />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing semicolon at the end of the line

}
}
```
A [ref](/react/docs/refs-and-the-dom.html) is used to pass the underlying DOM element to the plugin. The `<div>` element has no properties or children, so React has no reason to update it, leaving the plugin free to update the DOM without conflicts.

The component still has to be unmounted, which provides one final opportunity for conflict. If the plugin does not provide a method for cleanup, you will probably have to provide your own, remembering to remove any event listeners the plugin registered to prevent memory leaks.

To demonstrate these concepts let's write a minimal wrapper for the plugin [Chosen](https://github.com/harvesthq/chosen), which augments `<select>` inputs.

Chosen does not render the initial select, so it is up to React to do so. Chosen hides the initial select and creates its own control, notifying the original of changes using jQuery. Because it is Chosen maintaining the state, it is easiest to implement the wrapper using an [uncontrolled component.](/react/docs/uncontrolled-components.html)

```js
class Chosen extends React.Component {
constructor(props) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work without shouldComponentUpdate() returning false? Maybe worth adding it for explicitness?

super(props);
this.handleUpdate = this.handleUpdate.bind(this);
}

handleUpdate(event) {
this.props.update(event.target.value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this.props.update? It looks like a React API but it’s not. Maybe worth calling this this.props.onChange for convention, and adding a desired API example before the code sample (e.g. "The component API we're aiming for is <Chosen onChange={this.handleChange} />".

}

componentDidMount() {
this.$el = $(this.el);
this.$el.chosen();
this.$el.on('change', this.handleUpdate);
}

componentDidUpdate() {
this.$el.trigger('chosen:updated');
}

componentWillUnmount() {
this.$el.off('change', this.handleUpdate);
this.$el.chosen('destroy');
}

render() {
return (
<select ref={el => this.el = el}>
{this.props.children}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when children change?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that this example is potentially dangerous because we're mixing React children with jQuery parent with React grandparents. Maybe it would be better to use a "leaf" component for a jQuery example.

It's also worth noting that generally it is recommended to use React components, and this is just an escape hatch for when you need to integrate with older code.

</select>
);
}
}
```

1. Change listeners need to be setup inside `componentDidMount()` and torn down in `componentWillUnmount` as the events are emitted by the plugin use jQuery, which is outside the React event system.

2. Chosen needs to be notified of any changes to the original select or its children. This is done in `componentWillUpdate()` by triggering a `'chosen:updated'` event.

3. When the component is unmounted, use the cleanup method provided by Chosen. This removes the custom select control and restores the actual `<select>` as well as removing any event listeners the plugin registered.

[Try it on CodePen.](http://codepen.io/wacii/pen/ygzxjG?editors=0010)

## Integrating with Other View Libraries

React can be easily embedded into other applications thanks to the flexibility of `ReactDOM.render()`. While often used once at startup to load a single React application into the DOM, `ReactDOM.render()` can be called multiple times, both to create multiple React applications and to update existing ones. This allows applications to be rewritten in React piece by piece.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth adding this is exactly how we use React at Facebook.


### Replacing String Based Rendering with React

A common pattern in older web applications is to describe chunks of the DOM as a string and insert it into the DOM like so: `$el.html(htmlString)`. These points in a codebase are perfect for introducing React. Just rewrite the string based rendering as a React component.

So the following jQuery implementation...
```js
const htmlString = '<button id="jquery-btn">jQuery</button>';
$el.html(htmlString);
$('jquery-btn').click(() => window.alert('jQuery button'));
```

...could be rewritten using a React Component.
```js
function Button() {
return <button id='react-btn'>React</button>;
}
ReactDOM.render(
<Button />,
document.getElementById('react-container'),
() => {
$('#react-btn').click(() => window.alert('React button'));
}
);
```

From here you could start moving more logic into the component and begin adopting more common React practices.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe explicitly say "For example, in components it is best not to rely on IDs because the same component can be rendered multiple times. Instead, we will use React event system:" and link that to events doc.


```js
function Button({ handleClick }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We avoid destructuring here in favor of props for easier reading to people unfamiliar with ES6.

return <button onClick={handleClick}>React</button>;
}
ReactDOM.render(
<Button handleClick={() => window.alert('React button v2')} />,
document.getElementById('react-container')
);
```

[Try it on CodePen.](http://codepen.io/wacii/pen/RpvYdj?editors=1010)

### Embedding React in a Backbone View

Backbone Views typically use HTML strings, or string producing template functions, to create the content for their associated DOM element. Like before, this process may be replaced with a React component.

Each view will have an associated component, and when the view renders, `ReactDOM.render()` is used to render the component into the view's `el`.

```js
function Component({ text }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(props) please

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also let's not call anything Component, it conflicts with React.Component.

return <p>{text}</p>;
}

const ComponentView = Backbone.View.extend({
render() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For people unfamiliar with Backbone, worth adding a comment here that notes that in Backbone, render runs once and is responsible for constructing the DOM tree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a section about rendering with strings/templates so the render function doesn't come out of nowhere. Not sure if that's enough.

const text = this.model.get('text');
ReactDOM.render(<Component text={text} />, this.el);
return this;
},
remove() {
ReactDOM.unmountComponentAtNode(this.el);
Backbone.View.prototype.remove.call(this);
}
});
```

State is maintained in the view as `this.model` and passed to the component as props.

In addition to normal cleanup, event listeners registered through React as well as component state should be removed by calling `ReactDOM.unmountComponentAtNode()`. This is something React normally calls for us, but because we are controlling the application with Backbone, we must do this manually to avoid leaking memory.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something React normally calls for us

This is ambigious and might give the wrong idea that you don't need to call ReactDOM.unmountComponentAtNode() when you use ReactDOM.render() in some other contexts (e.g. a purely React app that happens to have those imperative calls for modals or something else).

We should make it clearer that unmountComponentAtNode() unmounts the whole React tree from the outside. However inside the React tree, React automatically takes care of unmounting individual components when their parents no longer render them. Not sure how to phrase it without going too much into details.


[Try it on CodePen.](http://codepen.io/wacii/pen/OWZJMQ?editors=0010)

## Integrating with Model Layers

React has little opinion on how data is stored or updated and so React components can easily incorporate the model layer from other frameworks. Models may be consumed as is or extracted using containers for better separation of concerns. Both approaches will be demonstrated using Backbone.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth adding that in general we recommend one directional data flow such as React state, Flux, or Redux, but other approaches work too.


### Using Backbone Models in React components
Copy link

@hswolff hswolff Apr 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to share some work I've done on this front. I recently created a HoC for syncing Backbone Models/Collections with React that might be of value to link to in this doc? I called it connect-backbone-to-react and its API is super inspired by react-redux's. Hopefully this is of value to other developers!


The simplest way to consume Backbone models and collections from a React component is to listen to the various change events and manually force an update.

Components responsible for rendering models would listen to `'change'` events, while components responsible for rendering collections would listen for `'add'` and `'remove` events. In both cases, call `this.forceUpdate()` to rerender the component with the new data.

In the following code, `ListComponent` renders a collection with `ItemComponent` responsible for rendering the individual models.

```js
class ItemComponent extends React.Component {
constructor(props) {
super(props);
this.rerender = () => this.forceUpdate();
}

componentDidMount() {
this.props.model.on('change', this.rerender);
}

componentWillUnmount() {
this.props.model.off('change', this.rerender);
}

render() {
return <li>{this.props.model.get('text')}</li>;
}
}

class ListComponent extends React.Component {
constructor(props) {
super(props);
this.rerender = () => this.forceUpdate();
}

componentDidMount() {
this.props.collection.on('add', 'remove', this.rerender);
}

componentWillUnmount() {
this.props.collection.off('add', 'remove', this.rerender);
}

render() {
return (
<ul>
{this.props.collection.map(model => (
<ItemComponent key={model.cid} model={model} />
))}
</ul>
)
}
}
```

[Try it on CodePen.](http://codepen.io/wacii/pen/oBdgoM?editors=0010)

### Extracting data from Backbone models

Alternatively, whenever a model changes, you can extract its attributes as plain data. The following is [a higher-order component (HOC)](/react/docs/higher-order-components.html) that extracts all attributes of a Backbone model into state, passing the data to the wrapped component.

This way, only the HOC needs to know about Backbone model internals, and the components concerned with presenting data can focus on that.

```js
function backboneModelAdapter(Component) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe connectToBackboneModel

return class extends React.Component {
constructor(props) {
super(props);
this.state = { ...props.model.attributes };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is object rest/spread which isn't in ES yet. Can we use Object.assign() here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there's a different model coming into props? I think it's worth showing how to handle it in componentWillReceiveProps because it's a common mistake in integrations.

this.handleChange = this.handleChange.bind(this);
}

handleChange(model) {
this.setState(model.changedAttributes());
}

componentDidMount() {
this.props.model.on('change', this.handleChange);
}

componentWillUnmount() {
this.props.model.off('change', this.handleChange);
}

render() {
const { model, ...otherProps } = this.props;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same on this line, this is object rest/spread and we probably shouldn't use it here yet.

return <Component {...otherProps} {...this.state} />;
}
}
}
```

A copy is made of the model's attributes to form the initial state. Every time there is a change event, just the changed attributes are updated.

To demonstrate its use, we will use a basic input wrapper. The component will render the `'text'` attribute of the provided model, calling an update function whenever the input is changed, presumably to update the model.

```js
function Input({ text, handleChange }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(props) please

return <input value={text} onChange={handleChange} />;
}

function BackboneModelAdapterDemo() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not super clear this is a component. Can you call this Example and then show how you call ReactDOM.render(<Example />) to make the point?

const model = new Backbone.Model({ text: 'Sam' });
const handleChange = event => model.set('text', event.target.value);
const WrappedInput = backboneModelAdapter(Input);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this inside the component. You're creating a new copy each render, destroying the state and DOM.

return <WrappedInput model={model} handleChange={handleChange} />;
}
```

[Try it on CodePen.](http://codepen.io/wacii/pen/RKyWKZ?editors=0010)