Learning React Without Using React Part 2
First Concepts Then Frameworks
Read part 1 if you haven’t yet.
Part 2 will continue where we left off the last time. The post will focus on improving our simple todo list. The current implementation consists of a composition of functions that render the complete app and includes a naive store that manages our state. There are a number of things we need to do to improve our application though. Here is a link to the current example and code.
First of all we haven’t taken care of handling our events in a proper way. Our components don’t handle any events. In React data flows down while events move up. This means our functions should trigger events up the chain as soon as they occur. For example our ItemRow function should call a function that has been passed down via props. How do we achieve this? Here’s an initial try.
function ItemRow(props) {
var className = props.completed ? 'item completed' : 'item';
return $('<li>')
.on('click', props.onUpdate.bind(null, props.id))
.addClass(className)
.attr('id', props.id)
.html(props.text);
}
We added an event listener on our list element which fires an onClick function as soon as the item is clicked. The onUpdate function is passed down via props.
Now what if we could create a function that handles creating the elements for us?
function createElement(tag, attrs, children) {
var elem = $('<' + tag + '>');
for (var key in attrs) {
var val = attrs[key];
if (key.indexOf('on') === 0) {
var event = key.substr(2).toLowerCase();
elem.on(event, val)
} else {
elem.attr(key, val);
}
}
return elem.html(children);
}
By implementing our createElement function we can refactor our ItemRow function to something like this:
function ItemRow(props) {
var className = props.completed ? 'item completed' : 'item';
return createElement('li', {
id: props.id,
class: className,
onClick: props.onUpdate.bind(null, props.id)
}, props.text
);
}
It is important to note that in React createElement creates a javaScript objects that is a representation of a Dom Element not an element itself. On a side note let’s take a look at what really happens when you write JSX in React.
The following JSX example
return ( <div id='el' className:'entry'>Hello</div>)
is converted to
var SomeElement =
React.createElement('div', {id: 'el', className: 'entry'}, 'Hello');
calling SomeElement() would return an object like this
{
// ..
type: 'div',
key: null,
ref: null,
props: {children: 'Hello', className: 'entry', id: 'el' },
}
For a more detailed insight read React Components, Elements, and Instances.
Back to our example. Where does onUpdate come from?
Let’s take a look at our initial render function. Our render defined a updateState function and passed it on to the ItemList component via props.
function render(props, node) {
function updateState(toggleId) {
state.items.forEach(function(el) {
if (el.id === toggleId) {
el.completed = !el.completed;
}
});
store.setState(state);
} node.empty().append([ItemsList({
items: props.items,
onUpdate: updateState
})]);
}
ItemsList function itself passes onUpdate to every single ItemRow.
function extending(base, item) {
return $.extend({}, item, base);
}function ItemsList(props) {
return createElement(‘ul’, {}, props.items
.map(extending.bind(null, {
onUpdate: props.onUpdate
}))
.map(ItemRow));
}
By taking this approach we achieved the following: data flows down the component hierarchy and events flow up the tree. This also means that we can remove the global listener we previously defined to listen on item clicks, toggling the state accordingly. We moved the function into render, which is now the aforementioned updateState.
More improvements.
Let’s refactor the input and button elements from the markup into a function. So eventually our markup will only consist of a div.
<div id="app"></app>
For example a input element can be easily created like this
var input = createElement(‘input’, { id: ‘input’ });
We can also move the global listener function that listened on button clicks into our SearchBar function. SearchBar returns an input and button element and handles click events by triggering a callback function passed in via props.
function SearchBar(props) {
function onButtonClick(e) {
var val = $('#input').val();
$('#input').val('');
props.update(val);
e.preventDefault();
}
var input = createElement('input', { id: 'input' });
var button = createElement('button',
{ id: 'add', onClick: onButtonClick.bind(null)}, 'Add'); return createElement(‘div’, {}, [input, button]);
}
Our render function needs to call SearchBar and pass in the appropriate props now. Before we update the render function, let’s take a second to get an idea on how a re-render should take place. Let’s neglect our store for a moment and take care of handling state in a high level component. Up until now, all functions were stateless, we’ll create a function that handles state and updates the children functions when suitable.
Container Component
Let’s create the high level container. Also read Presentational and Container Components for a clearer understanding. The following should give you a high level idea.
So let’s call our container component App. All it does is call the SearchBar and ItemList functions and returns an array of elements. We are going to refactor our render function again. Most of the code will simply move into App.
Before taking a look at the App function, let’s have a quick look at our render:
function render(component, node) {
node.empty().append(component);
}render(App(state), $(‘#app’));
render only takes care of rendering the application to a defined node now. React is more complicated than this trivial implementation. We’re only appending an element tree into a defined root element, but it should suffice to give a high level idea of the concept.
Our App function is now our old render function sans the DOM handling.
function App(props) {
function updateSearchState(value) {
state.items.push({id:state.id++, text:value, completed:false});
store.setState(state);
}
function updateState(toggleId) {
state.items.forEach(function(el) {
if (el.id === toggleId) {
el.completed = !el.completed;
}
});
store.setState(state);
} return [SearchBar({update: updateSearchState}),
ItemsList({items: props.items, onUpdate: updateState})];
}
We still need to fix one more thing. We are accessing a global store and calling setState to enable a re-render.
Let’s refactor the App function to re-render it’s children components without having to call the store. How do we achieve this?
First of all let’s neglect the store and figure out how calling a setState function would re-render the component and it’s children elements.
We will need to keep track of the current state inside this high level component and also take care of re-rendering the DOM as soon as setState has changed. Here’s a very primitive approach:
function App(props) { function getInitialState(props) {
return { items: [], id: 0 };
} var _state = getInitialState(), _node = null; function setState(state) {
_state = state;
render();
} // ...}
We initialize our state by calling getInitialState and we call the App render function as soon as we update the state via setState.
render either creates a node or simply updates the node when state changes.
// naive implementation of render…
function render() {
var children = [SearchBar({ update: updateSearchState}),
ItemsList({ items: _state.items,
onUpdate: updateState
})];
if (!_node) {
return _node = createElement(‘div’, { class: ‘top’ }, children);
} else {
return _node.html(children);
}
}
In no way is this efficient, it should only highlight the fact, that calling setState inside a React Component, does not render the complete Application, only the Component and its children.
Here is the updated render call. We’re calling App without any arguments, simply relying on getInitialState to return the initial state.
function render(component, node) {
node.empty().append(component);
}render(App(), $(‘#app’));
Check the functioning example including the complete code.
Refinements
What if we had a generic function that would return an object with a setState function and be able to differentiate between props being passed in and the component state itself?
Something like this f.e.
var App = createClass({
updateSearchState: function(string) { //... }, updateState: function(obj) { //... }, render: function() {
var children = [SearchBar({ update: this.updateSearchState}),
ItemsList({ items: this.state.items,
onUpdate: this.updateState
})];
return createElement(‘div’, { class: ‘top’ }, children);
}
})
The good news is that React offers you multiple options to create Components including using React.createClass. Other options include ES6 classes and stateless functions for more information consult the docs.
Our example app covered how data flows down and events flow up the component hierarchy. We’ve all seen how to handle state inside a component. There is a lot more to know and learn about React. The following links should help with continuing from here on.
Further learning
Removing User Interface Complexity, or Why React is Awesome
Presentational and Container Components
React Components, Elements, and Instances
Wrap Up
The plan was also to create an advanced state container, implementing undo/redo and more for part 2, but I think it would be out of scope for this post actually.
I might write a part 2.1 if there is some interest.
This is work in progress. Any feedback welcome. I might be missing the point or might have neglected imported aspects. Leave a comment here or on twitter. Thanks.
Also read: