Skip to content

Instantly share code, notes, and snippets.

@loklaan
Last active January 12, 2019 13:58
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loklaan/bcdfded782d346cd9936 to your computer and use it in GitHub Desktop.
Save loklaan/bcdfded782d346cd9936 to your computer and use it in GitHub Desktop.
Knockout + React

Knockout & React, in harmony

Bless

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h3>Knockout Example</h3>
<div id="koTest">
<div data-bind="text: text"></div>
<div data-bind="react: { $: Components.ToDoList, props: { todos: todos }}"></div>
</div>
<h3>React Example</h3>
<div id="reactTest"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.6.15/browser.min.js"></script>
<script type="text/babel">
window.Components = {};
/* ================================================================== */
/* knockout-react.js BEGIN */
/**
* Mixin for allowing ko bindings inside of React components.
*
* When a react component changes, its `state` & `props` objects are wrapped
* and applied as a ViewModel to the root DOM node of the React component.
*/
var KnockoutMixin = {
updateKnockout() {
// This has been bound to the dependancy chain in __koModel,
// found in the componentDidMount lifecycle stage. Changing this
// observable will cause __koModel to revaluate it's values.
this.__koTrigger(!this.__koTrigger());
},
componentDidMount() {
this.__koTrigger = ko.observable(true);
this.__koModel = ko.computed(function () {
// Magic.
// Calling this observable will add it to the dependancy
// chain for __koModel, as it is a computedObservable.
// Anytime __koTrigger is changed, such as in updateKnockout(),
// this model will revaluate
this.__koTrigger();
return {
props: this.props,
state: this.state
};
}, this); // Bind this computedObservable to the components 'this'
// Bind the __koModel view model to the components mounted DOM
// node
ko.applyBindings(this.__koModel, this.getDOMNode());
},
componentWillUnmount() {
ko.cleanNode(this.getDOMNode());
},
componentDidUpdate() {
this.updateKnockout();
}
};
/**
* Creates a ko handler for react components, with the keyword 'react'.
* It must be passed an $ option, with the name of the React Component,
* which is exposed globally.
* It can be passed a props option, which can be a ko observable, that
* will be passed as the props to the react component.
*
* <ul data-bind="react: { $: ToDoList, props: { todos: todos } }><ul>
*
* ToDoList must be on window. Tip: Namespace your components in window.Components
* todos must exist in the view model that is bound to the nearest bound DOM node parent
*/
//
var reactHandler = ko.bindingHandlers.react = {
render: function ( el, Component, props ) {
React.render(
React.createElement(Component,props),
el
);
},
init: function ( el, valueAccessor, allBindingsAccessor, viewModel, bindingContext ) {
var options = valueAccessor();
var Component = ko.unwrap(options.component || options.$);
var props = ko.toJS(options.props || viewModel);
reactHandler.render(el, Component, props);
return { controlsDescendantBindings: true };
},
update: function ( el, valueAccessor, allBindingsAccessor, viewModel, bindingContext ) {
var options = valueAccessor();
var Component = ko.unwrap(options.component || options.$);
var props = ko.toJS(options.props || viewModel);
reactHandler.render(el, Component, props);
return { controlsDescendantBindings: true };
}
};
/* knockout-react.js END */
/* ================================================================== */
// Our react component
var ToDoList = React.createClass({
// Since we have ko bindings on inner elements, we use our Mixin
mixins: [ KnockoutMixin ],
propTypes: {
todos: React.PropTypes.array.isRequired
},
render() {
return (
<ul data-bind="foreach: props.todos">
<li data-bind="text: $data"></li>
</ul>
);
}
});
window.Components.ToDoList = ToDoList; // Expose the component globally
/** Knockout example */
var viewModel = {
text: "some text",
todos: ko.observableArray([
"one","two","three"
])
};
ko.applyBindings(viewModel, document.getElementById("koTest"));
setInterval(function () {
viewModel.todos.shift();
viewModel.todos.push("" + Math.random());
}, 100);
/** React example */
var render = function (todos) {
React.render(
React.createElement(ToDoList, { todos: todos }),
document.getElementById("reactTest")
);
};
var reactList = ["one","two","three"]; // Initial values
render(reactList);
setInterval(function () {
reactList.shift();
reactList.push("" + Math.random());
render(reactList);
}, 100);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment