MarcoPolo – Partially Functional - reactMarcoZola2019-09-06T00:00:00+00:00https://marcopolo.io/tags/react/atom.xmlThoughts on "Why is React doing this?"2019-09-06T00:00:00+00:002019-09-06T00:00:00+00:00https://marcopolo.io/code/why-react-response/<h1 id="response-to-why-react">Response to <a href="https://gist.github.com/sebmarkbage/a5ef436427437a98408672108df01919">Why React?</a></h1>
<p>Some quick thoughts I had after reading the <a href="https://gist.github.com/sebmarkbage/a5ef436427437a98408672108df01919">Why React?</a> gist.</p>
<p>Disclaimer: <em>I want to be critical with React. I don't disagree that it has done some amazing things</em></p>
<h2 id="compiled-output-results-in-smaller-apps">"Compiled output results in smaller apps"</h2>
<blockquote>
<p>E.g. Svelte apps start smaller but the compiler output is 3-4x larger per component than the equivalent VDOM approach.</p>
</blockquote>
<p>This may be true currently, but that doesn't mean it will always be true of compiled-to frameworks. A theoretical compiler can produce a component that uses a shared library for all components. If a user doesn't use all the features of a framework, then a compiler could remove the unused features from the output. Which is something that could not happen with a framework that relies on a full runtime.</p>
<p>Note: I'm not advocating for a compiled-to approach, I just think this point was misleading</p>
<h2 id="dom-is-stateful-imperative-so-we-should-embrace-it">"DOM is stateful/imperative, so we should embrace it"</h2>
<p>I agree with OP here. Most use-cases would not benefit from an imperative UI api.</p>
<h2 id="react-leaks-implementation-details-through-usememo">"React leaks implementation details through useMemo"</h2>
<p>A common problem to bite new comers is when they pass a closure to a component, and that closure gets changed every time which causes their component to re-render every time. <code>useMemo</code> can fix this issue, but it offloads a bit of work to the developer.</p>
<p>In the above context, it's an implementation detail. I'm not saying it's the wrong or right trade off, I'm only saying that the reason you have to reach for <code>useMemo</code> when passing around closures is because of how React is implemented. So the quote is accurate.</p>
<p>Is that a bad thing? That's where it gets more subjective. I think it is, because these types of things happen very often and, in a big app, you quickly succumb to death by a thousand cuts (one closure causing a component to re-render isn't a big deal, but when you have hundreds of components with various closures it gets hairy).</p>
<p>The next example OP posts is about setting users in a list.</p>
<pre data-lang="js" class="language-js "><code class="language-js" data-lang="js">setUsers([
...users.filter(user => user.name !== "Sebastian"),
{ name: "Sebastian" }
]);
</code></pre>
<p>If you are happy with that syntax, and the tradeoff of having to use <code>key</code> props whenever you display lists, and relying on React's heuristics to efficiently update the views corresponding to the list, then React is fine. If, however, you are okay with a different syntax you may be interested in another idea I've seen. The basic idea is you keep track of the diffs themselves instead of the old version vs. the new version. Knowing the diffs directly let you know exactly how to update the views directly so you don't have to rely on the <code>key</code> prop, heuristics, and you can efficiently/quickly update the View list. This is similar to how <a href="https://github.com/immerjs/immer">Immer</a> works. <a href="https://docs.rs/futures-signals/0.3.8/futures_signals/tutorial/index.html">Futures Signals</a> also does this to efficiently send updates of a list to consumers (look at <code>SignalVec</code>).</p>
<h2 id="stale-closures-in-hooks-are-confusing">"Stale closures in Hooks are confusing"</h2>
<p>I agree with OP's points here. It's important to know where your data is coming from. In the old hook-less style of React, your data was what you got from your props/state and nothing else. With hooks, it's easier to work with stale data that comes in from outside your props. It's a learning curve, but not necessarily bad.</p>
<p>One thing I find interesting is that the use of hooks moves functional components into becoming more stateful components. I think this is fine, but it loses the pure functional guarantees you had before.</p>
<p>I haven't yet made up my mind about hooks that interact with the context. (i.e. <code>useSelector</code> or <code>useDispatch</code>) since the context is less structured. i.e. This component's selector function for <code>useSelector</code> relies on the state being <code>X</code>, but <code>X</code> isn't passed in, it's set as the store in redux configuration file somewhere else. Now that the component relies on the shape of the store being <code>X</code> it makes it harder to move out. This may not actually matter in practice, and it may be much more useful to be able to pull arbitrary things out of your store. Hence why I'm currently undecided about it.</p>
An Intro to Functional Reactive Programming in UIs2014-11-16T00:00:00+00:002014-11-16T00:00:00+00:00https://marcopolo.io/code/frp/<p>Maybe you've heard of <a href="https://facebook.github.io/react/">React</a>, <a href="https://github.com/swannodette/om">Om</a>,
or <a href="http://elm-lang.org/">Elm</a>, and wondering: what's the deal with
functional reactive programming (FRP)?</p>
<p>This post will act as primer on FRP using vanilla JS, but the ideas presented
here translate pretty easily in any language and UI system.</p>
<p>So let's start with an informal, pragmatic definition of FRP:</p>
<blockquote>
<p>Use streams of data to create the application state (data)</p>
</blockquote>
<p>And</p>
<blockquote>
<p>Build a UI given only the application state with pure functions (view)</p>
</blockquote>
<h2 id="streams-and-arrays">Streams and arrays</h2>
<p>You can imagine streams of data as a set of values over time.</p>
<p>A stream of numbers representing a counter would look like:</p>
<pre><code>[0,1,2,3,4,5,6,...]
</code></pre>
<p>Each number is essentially a snapshot of the value at that time.</p>
<p>Streams are similar to arrays, but the main difference is time.
An immutable array has all the values it will ever have when it is created, while a stream represents all the values that have happened and will
happen.</p>
<p>Here's a concrete example: You are an owner of an exclusive restaurant.
It's so exclusive that people have to make reservations months in advance.
Every night you have a list of people at your restaurant (because they've
already made reservations). Imagine the list being <code>[amy, sally, bob]</code>.
To count the number of guests, we would just reduce over the list
adding 1 for every guest. If we wanted to know how much each guest spent
we would map against a function that tells us the guest's bill.</p>
<p>This is just a normal array with normal map/reduce construct.
For completeness here's the equivalent code.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">var guests = ["amy", "sally", "bob"];
var bills = { amy: 22.5, sally: 67.0, bob: 6.0 };
// Count the guests
guests.reduce(function(sum, guest) {
return sum + 1;
}, 0);
// => 3
// Get the bills
guests.map(function(guest) {
return bills[guest];
});
// => [22.5, 67, 6]
</code></pre>
<p>Unfortunately Sally had some bad fish and died after eating at your
restaurant, so everyone has cancelled their reservations and you are
now a fast food place. In this case you don't have a list of guests,
instead you have a <em>stream</em> of people who come in and order food.
<code>Frank</code> might come in at 10 am, followed by <code>Jack</code> at 2 pm. To get
similar data as before we would again map/reduce over the stream,
but since we are operating over a stream that never ends, the values
from map/reduce themselves become streams that never end.</p>
<p>Here is some equivalent pseudo code for streams that calculates
the <code>guestCounts</code> and the <code>guestBills</code>.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">guests = [... time passes ..., Frank, ... time passes ..., Jack, ... ]
guestCounts = [... time passes ..., 1, ... time passes ..., 2, ... ]
guestBills = [... time passes ..., 5.50, ... time passes ..., 6.50, ... ]
</code></pre>
<p>So a stream is just like an array that never ends, and represents
snapshots of time.</p>
<p>Now that we have an intuitive idea what streams are, we can actually
implement them.</p>
<h2 id="streams-of-data">Streams of data</h2>
<p>A stream of numbers representing a counter would look like:</p>
<pre><code>[0,1,2,3,4,5,6,...]
</code></pre>
<p>If we wanted to keep track of how long someone was on our page,
we could just display the latest value of the counter stream
in our UI and that would be enough.</p>
<p>A more involved example: Imagine we had a stream of data
that represents the keys pressed on the keyboard.</p>
<pre><code>["p","w","n","2","o","w","n",...]
</code></pre>
<p>Now we want to have a stream that represents the state of the system,
say the amount of keys pressed.</p>
<p>Our key count stream would look like:</p>
<pre><code>["p","w","n","2","o","w","n",...]
=>
[ 1, 2, 3, 4, 5, 6, 7, ...]
</code></pre>
<p>This transformation would happen with a reducing function.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">var keyCountReducer = function(reducedValue, streamSnapshot) {
return reducedValue + 1;
};
</code></pre>
<p>This function takes in the stream value, and a reduced value so far, and
returns a new reduced value. In this case a simple increment.</p>
<p>We've talked about streams for a while now, let's implement them.</p>
<p>In the following code, we create a function that will return an object with two
methods: <code>observe</code> for registering event listeners and <code>update</code> for adding a value
to the stream.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">// A function to make streams for us
var streamMaker = function() {
var registeredListeners = [];
return {
// Have an observe function, so
// people who are interested can
// get notified when there is an update
observe: function(callback) {
registeredListeners.push(callback);
},
// Add a value to this stream
// Once added, will notify all
// interested parties
update: function(value) {
registeredListeners.forEach(function(cb) {
cb(value);
});
}
};
};
</code></pre>
<p>We also want to make a helper function that will create a new reduced stream
given an existing <code>stream</code>, a <code>reducingFunction</code>, and an <code>initialReducedValue</code>:</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">// A function to make a new stream from an existing stream
// a reducing function, and an initial reduced value
var reducedStream = function(stream, reducingFunction, initialReducedValue) {
var newStream = streamMaker();
var reducedValue = initialReducedValue;
stream.observe(function(streamSnapshotValue) {
reducedValue = reducingFunction(reducedValue, streamSnapshotValue);
newStream.update(reducedValue);
});
return newStream;
};
</code></pre>
<p>Now to implement the keypress stream and count stream.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">// Our reducer from before
var keyCountReducer = function(reducedValue, streamSnapshot) {
return reducedValue + 1;
};
// Create the keypress stream
var keypressStream = streamMaker();
// an observer will have that side effect of printing out to the console
keypressStream.observe(function(v) {
console.log("key: ", v);
});
// Whenever we press a key, we'll update the stream to be the char code.
document.onkeypress = function(e) {
keypressStream.update(String.fromCharCode(e.charCode));
};
// Using our reducedStream helper function we can make a new stream
// That reduces the keypresses into a stream of key counts
var countStream = reducedStream(keypressStream, keyCountReducer, 0);
countStream.observe(function(v) {
console.log("Count: ", v);
});
</code></pre>
<p>Now with the new stream we can display it like we did before.</p>
<p>Which leads us into our next point...</p>
<h2 id="rendering-uis-given-data">Rendering UIs given data</h2>
<p>Now that we have a system for generating state through streams,
let's actually show something off.</p>
<p>This is where React.js shines, but for the purpose of this post we'll
build our own system.</p>
<p>Let's say at one point in time our data looks like:</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">{"Count":1}
</code></pre>
<p>And we want to render a UI that represents this information.
So we'll write a simple piece of JS that renders html directly from the map.
To keep it easy, we'll use the keys as div ids.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">//Pure Function to create the dom nodes
var createDOMNode = function(key, dataMapOrValue) {
var div = document.createElement("div");
div.setAttribute("id", key);
// Recurse for children
if (typeof dataMapOrValue === "object" && dataMapOrValue !== null) {
Object.keys(dataMapOrValue).forEach(function(childKey) {
var child = createDOMNode(childKey, dataMapOrValue[childKey]);
div.appendChild(child);
});
} else {
// There are no children just a value.
// We set the data to be the content of the node
// Note this does not protect against XSS
div.innerHTML = dataMapOrValue;
}
return div;
};
// Render Data
var render = function(rootID, appState) {
var root;
// Check if the root id is even defined
if (document.getElementById(rootID) === null) {
// We need to add this root id so we can use it later
root = document.createElement("div");
root.setAttribute("id", rootID);
document.body.appendChild(root);
}
root = document.getElementById(rootID);
// Clear all the existing content in the page
root.innerHTML = "";
// render the appState back in
root.appendChild(createDOMNode(rootID, appState));
};
render("counter", { Count: 1 });
</code></pre>
<p>After running this code on a <a href="about:blank">blank page</a> we have a page
that says <code>1</code>, it worked!</p>
<p>A bit boring though, how about we make it a bit more interesting by updating
on the stream.</p>
<h2 id="rendering-streams-of-data">Rendering Streams of data</h2>
<p>We've figured out how streams work, how to work with streams, and how to
render a page given some data. Now we'll tie all the parts together; render
the stream as it changes over time.</p>
<p>It really is simple. All we have to do is re-render whenever we receive
a new value on the stream.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">// Let's observe the countstream and render when we get an update
countStream.observe(function(value) {
render("counter", value);
});
// And if we wanted to render what the keypress stream tells us, we can do so
// just as easily
keypressStream.observe(function(value) {
render("keypress", value);
});
</code></pre>
<h2 id="single-app-state">Single App State</h2>
<p>A single app state means that there is only one object that encapsulates the
state of your application.</p>
<p>Benefits:</p>
<ul>
<li>All changes to the frontend happen from this app state.</li>
<li>You can snapshot this state and be able to recreate the
frontend at any point in time (facilitates undo/redo).</li>
</ul>
<p>Downsides:</p>
<ul>
<li>You may conflate things that shouldn't be together.</li>
</ul>
<p>Having a single place that reflects the whole state is pretty amazing,
how often have you had your app get messed up because of some rogue event?
or hidden state affecting the application, or an ever growing state
scattered around the application.</p>
<p>No more.</p>
<p>A single app state is a natural end to the directed acyclic graph that
we've created with streams.</p>
<pre><code>stream1 -> mappedStream
\
mergedStream -> appStateStream
/
stream2 -> reducedStream
</code></pre>
<h2 id="implementing-single-app-state">Implementing Single App State</h2>
<p>In our previous example we had two pieces of state,
the counter and the keypress. We could merge these together into one stream, and
then form a single app state from that stream.</p>
<p>First let's make a helper function that will merge streams for us. To keep it
general and simple we'll take only two streams and a merging function.
It will return a new stream which is the merge of both streams with the mergeFn.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">// A merge streams helper function
var mergeStreams = function(streamA, streamB, mergeFn) {
var streamData = [null, null];
var newStream = streamMaker();
streamA.observe(function(value) {
streamData[0] = value;
newStream.update(mergeFn.apply(null, streamData));
});
streamB.observe(function(value) {
streamData[1] = value;
newStream.update(mergeFn.apply(null, streamData));
});
return newStream;
};
</code></pre>
<p>This implementation will call the merge function with the latest value from the
streams or null if the stream hasn't emitted anything yet. This means the output
can return duplicate values of one of the streams.</p>
<p>(As a side note, the performance impact of duplicate values can be mitigated
with immutable datastructures)</p>
<p>We want to put both the keypress and the counter in one object, so our
merge function will do just that.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">var mergeIntoObject = function(keypress, counter) {
return { counter: counter, keypress: keypress };
};
</code></pre>
<p>Now to create the single app state stream, and render that single app state.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">var appStateStream = mergeStreams(keypressStream, countStream, mergeIntoObject);
appStateStream.observe(function(value) {
render("app", value);
});
</code></pre>
<h2 id="final-code">Final Code</h2>
<p>Most of these functions are library functions that you wouldn't need to implement
yourself. The final application specific code would look like:</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">// Create the keypress stream
var keypressStream = streamMaker();
// Whenever we press a key, we'll update the stream to be the char code.
document.onkeypress = function(e) {
keypressStream.update(String.fromCharCode(e.charCode));
};
var keyCountReducer = function(reducedValue, streamSnapshot) {
return reducedValue + 1;
};
// Using our reducedStream helper function we can make a new stream
// That reduces the keypresses into a stream of key counts
var countStream = reducedStream(keypressStream, keyCountReducer, 0);
var mergeIntoObject = function(keypress, counter) {
return { counter: counter, keypress: keypress };
};
var appStateStream = mergeStreams(keypressStream, countStream, mergeIntoObject);
appStateStream.observe(function(value) {
render("app", value);
});
</code></pre>
<p>You can see a running version of this code <a href="http://jsfiddle.net/Ld3o1Lm5/2/">here</a></p>
<h2 id="the-render-a-closer-look">The render, a closer look</h2>
<p>So what does the render actually do?</p>
<p>Well, it clears the inner html of a containing div and adds an element inside of it.
But that's pretty standard, how are we defining what element is created?
Why yes, it's the createDOMNode function. In fact, if you wanted your data displayed
differently (e.g. in color, or upside down) all you'd have to do is write your own
createDOMNode function that adds the necessary styles or elements.</p>
<p>Essentially, the <code>createDOMNode</code> function controls what your UI will look like.
createDOMNode is a pure function, meaning for the same set of inputs, you'll
always get the same set of outputs, and has no side effects (like an api call).
This wasn't a happy accident, FRP leads to a
design where the functions which build your UI are pure functions!
This means UI components are significantly easier to reason about.</p>
<h2 id="time-travel">Time travel</h2>
<p>Often when people talk about FRP, time travel is bound to get brought up.
Specifically the ability to undo and redo the state of your UI. Hopefully, if
you've gotten this far, you can see how trivial it would be to store the data
used to render the UIs in an array and just move forward and backward to
implement redo and undo.</p>
<h2 id="performance">Performance</h2>
<p>If you care about performance in the slightest, you probably shuddered when
I nuked the containing element and recreated all the children nodes. I don't
blame you; however, that is an implementation detail. While my implementation
is slow, there are implementations (e.g. React) that only update the items and
attributes that have changed, thus reaping performance benefits with no cost
to the programmer! You are getting a better system for modeling UIs and
the performance boosts for free! Furthermore a lot of smart people are working
on React, and as it gets faster, so will your app. Without any effort on your
part.</p>
<h2 id="now-with-actual-libraries">Now with actual libraries</h2>
<p>A lot of what we wrote was the library to get streams up and running,
however those already exists (e.g. <a href="http://baconjs.github.io/">Bacon.js</a> and <a href="https://facebook.github.io/react/">React.js</a>)</p>
<p>A couple quick notes if this is your first time looking at React.js or Bacon.js.</p>
<p><code>getInitialState</code> defines the initial local state of the component.
<code>componentWillMount</code> is a function that gets called before the component
is placed on the DOM.</p>
<p><code><stream>.scan</code> is our reducing function in Bacon.js parlance.</p>
<pre data-lang="javascript" class="language-javascript "><code class="language-javascript" data-lang="javascript">// Our streams just like before
var keypressStream = Bacon.fromEventTarget(document.body, "keypress").map(
function(e) {
return String.fromCharCode(e.charCode);
}
);
var countStream = keypressStream.scan(0, function(count, key) {
return count + 1;
});
var KeyPressComponent = React.createClass({
getInitialState: function() {
return { count: 0, keypress: "<press a key>", totalWords: "" };
},
componentWillMount: function() {
this.props.countStream.onValue(
function(count) {
this.setState({ count: count });
}.bind(this)
);
this.props.keypressStream.onValue(
function(key) {
this.setState({ keypress: key });
}.bind(this)
);
// Add something extra because why not
this.props.keypressStream
.scan("", function(totalWords, key) {
return totalWords + key;
})
.onValue(
function(totalWords) {
this.setState({ totalWords: totalWords });
}.bind(this)
);
},
render: function() {
return React.DOM.div(
null,
React.createElement("h1", null, "Count: " + this.state.count),
React.createElement("h1", null, "Keypress: " + this.state.keypress),
React.createElement("h1", null, "Total words: " + this.state.totalWords)
);
}
});
React.render(
React.createElement(KeyPressComponent, {
keypressStream: keypressStream,
countStream: countStream
}),
document.body
);
</code></pre>
<p>jsfiddle for this code <a href="http://jsfiddle.net/jf2j62wj/10/">here</a>.</p>
<h2 id="closing-notes">Closing Notes</h2>
<p><a href="https://facebook.github.io/react/">React</a> is great for reactively rendering the ui.
<a href="http://baconjs.github.io/">Bacon.js</a> is a great library that implements these streams.</p>
<p>If you're looking to really delve into FRP:
<a href="http://elm-lang.org/">Elm</a> has a well thought out FRP system in a haskell like language.</p>
<p>If you're feeling adventurous give Om & Clojurescript a shot.
<a href="https://github.com/swannodette/om">Om</a> is a great tool that adds immutability
to React, and brings React to Clojurescript</p>
<p>Finally, Evan Czaplicki (Elm creator) did a <a href="https://www.youtube.com/watch?v=Agu6jipKfYw">great talk on FRP</a></p>