3 ms·
This is actually all you really need to know about flux: https://twitter.com/substack/status/621818725159710720 https://twitter.com/substack/status/62181872515
by malandrew 11y ago
This is actually all you really need to know about flux:
https://twitter.com/substack/status/621818725159710720 https://twitter.com/substack/status/621818725159710720
https://twitter.com/substack/status/621832733564628992 https://twitter.com/substack/status/621832733564628992
https://twitter.com/substack/status/621639688919515136 https://twitter.com/substack/status/621639688919515136
That first tweet succinctly explains flux in less than 140 characters.
At the end of the day, pretty much every react "best practice" I've seen converges on approaches that raynos/mercury offer out-of-the-box.
React's decision to allow local state inside components was an "original sin" and every single engineers that decides to author a library to make React/Flux simpler is really just engineering around the poor decision of allowing local state.
Have of those should, might, could update if they felt like it react class props are unnecessary complexity that had to be bolted on to overcome issues with local state.
Here's a better approach:
(1) components that are pure functions that take in state that they should render. state hasn't changed? don't call the function
(2) components that have state that needs to be tracked can export a function that produces an instance of the state that component consumes
(3) compose the state of the UI, by calling the state instantiating function of all the components in your UI. Nest as appropriate.
(4) Make sure all these state instances are bijective lenses that keep one source of truth for a certain state value. Everywhere that state is needed or could be modified receives that "cursor" via dependency injection. The simplest example demonstrating a state cursor is the raynos/observ library.
The composed state object is the waist of your app hourglass, just like IP is the waist of the Internet. All I/O modifies state and that state propagates from there. Anything rendered to the screen is the O in I/O. Any events coming from your mouse or other peripheral is the I in I/O. Any syncing via XHR or websockets can be the I and the O in I/O. All I/O flows to the state.
Anything that needs to react to changes in that state have two options: subscribe to state change events (push) or read the current state as necessary (pull).
This really isn't all that hard and React/Flux has overcomplicated things immensely and given it a flashy name. The myriad libraries that purport to make it easier and simpler are just overcompensating for something that fundamentally needs to be re-engineered, but won't because it breaks backwards compatibility and requires people to move state logic (read: business logic) out of the component classes (that probably shouldn't have been there in the first place).
- TheAceOfHearts 11y agoI mostly agree, but I don't think you're entirely correct about component local state. I do flux and I keep everything in stores or some variation, and my components take everything as props, so they're usually pure functions. However, for my base (i.e. heavily reusable) components, I use local state to track ephemeral component state. One such example, if I'm doing an animation when someone clicks on an input, such as a material design textfield.
- malandrew 11y agoThat too is solvable by eliminating local state and has an advantage of making animation time travel debugging possible. So in my comment above I mentioned having a component export two functions. One function is a pure render function and one function creates a state instance. The state of the animation would be one of the things tracked in the state instance: var Observable = require('observ'); var svg = require('virtual-dom/virtual-hyperscript/svg'); module.exports = state; module.exports = render; function state(initialPosition) { return new Observable(initialPosition || [0,0]); } function render(positionState) { var center = positionState(); return svg('circle', { 'fill': 'rgba(255, 0, 0, 0.1)', 'stroke': 'black', 'stroke-width': '2', 'cx': center[0], 'cy': center[1], 'r': 10 }); } The parent component would export its state in the same way. Its own state would be composed by calling the state() function of its own subcomponents. Turtles all the way down. At the top of the app, the saved state would be injected into the state of the top level component, which would decompose it into parts and inject it into the state functions of its subcomponents such as the one above. This single composed state object tree makes it trivial to record diffs in state for playback or to serialize and save the state to localStorage or the network. It's trivial to work with the approach above. As trivial as the local state approach of react. It's extremely consistent about where state lives. It doesn't sometimes live in a global place and sometimes live in some other place. Another developer joining the project doesn't have to hunt around for where state for a particular value is being maintained. It also makes it simple to do far more complex things with animation, like build a complex animation editing application where an animation might be controlled by knobs, buttons, sliders, dials, etc. With local state you'd have to wire up those controls directly to the internal state of a component, completely messing up separation of concerns. With the composed state object, you just write one function that maps the state of two sliders (x&y) to the state of the circle and have it listen to change events on the slider position. It's trivial to wire things up in both directions by having them listen to each other. Time travel (undo/redo) is also trivial to implement: https://github.com/Raynos/mercury/blob/master/time-travel.js https://github.com/Raynos/mercury/blob/master/time-travel.js 67 LOC with generous whitespace. You could wire up the time-travel state to a slider as well to accomplish something like this: https://www.youtube.com/watch?v=ri614C_Buwg https://www.youtube.com/watch?v=ri614C_Buwg For the viewing state over time features in the froggato level editor you simply take the series of state over time, use it to instantiate state() multiple times and render them all in the same frame. Show one circle: scene -> circleRender(circleStateAtTimePosition(now)); Show one circle over time: scene -> circleRender(circleStateAtTimePosition(now - 2)); -> circleRender(circleStateAtTimePosition(now - 1)); -> circleRender(circleStateAtTimePosition(now)); -> circleRender(circleStateAtTimePosition(now + 1)); -> circleRender(circleStateAtTimePosition(now + 2)); Local state simply makes everything much harder to reason about as you move beyond all but the most trivial apps. Lastly, for long running ephemeral state animations, you gain the ability to "cancel" the animation. Imagine you have a "card" animating out from the right to show a panel, but you click on something else that would close that panel and open another one. It's much simpler to cancel that animation midway, close the first card and animate out the second card. The alternative is to add a bunch of methods to the card class to handle the cancellation logic. If you need that cancellation logic in other components you need a mixin, but it's all brittle because its tied to the key names on this.props as opposed to being able to make a animation controlling function used in an animation coordinator that merely needs to receive cursors (i.e. nothing is hard coded to key names) You might say "but I have no reason to track ephemeral state in that way" and you would be correct, you don't need to. But there are no disadvantages of doing so relative to local state. Furthermore you gain predictability about where state lives and the ability to not worry about refactoring if one day decide that you want to now control a particular piece of ephemeral state.