Party of Five: React
If you want to be at the cutting edge of JavaScript, especially with startups and remote work.
I’m going to start with my conclusion, just so that we are on the same page.
Going It Alone
It only recently clicked why I was struggling with React: it’s not actually an abstraction. The vast majority of frameworks and libraries are (or include) abstractions. Regardless of the language or platform, the framework shields the user from the worst of it, softens some of its edges. Phoenix does this to Elixir, which in turn abstracts bits of Erlang. Laravel abstracts elements of PHP which itself is an abstraction over C. Everything from jQuery to Ember abstracts JavaScript, wrapping its odd event patterns and scoping for simpler, less ambiguous and less error-prone syntax.
React unashamedly does not. JavaScript’s intestines are all out here. The changing scope of this on actions, the unintuitive execution of bound closures, the unclear comparison operators, explicit event watching, the question of whether you’re dealing with the function or the function definition, etc. React supporters proudly declare that “it’s just JavaScript”. Which is for the most part true. This gives React all of the power and flexibility of JavaScript. If you’re a super expert JavaScript person, React’s low level is good. It provides a lot of power. But for a developer with less knowledge or experience, that power is pointed right at your face.
If there are holes in your JavaScript knowledge, React will stick its finger in them and wiggle it around. Originally the previous sentence didn’t use the word “finger”, I’m still not sure I made the right call on that edit. Regardless, React demands a high level of understanding of a lot of underlying JavaScript details, certainly more than any other framework, which abstract a lot of this.
Here’s a fun test. What is the correct way to bind an event to a local action handler:
onChange={() => this.handleTitleChange()}
onChange={this.handleTitleChange}
onChange={this.handleTitleChange.bind(this)}
onChange={() => this.handleTitleChange}
onChange={(event) => this.handleTitleChange(event)}
How is handleTitleChange defined?
handleTitleChange = (event) => {
handleTitleChange() {
handleTitleChange: function(value) {
Any combination of these options has subtly different effects, and none of them are explicitly right or wrong. Need access to this? To the event? This isn’t always available. Worse if you need it to be async, for example. Or you need to pass in arguments to the function. The specific variations of implementation can make for a rough process when writing code.
Compare this to Vue, for example:
<button @click="saveItem()">Clicky</button>
Which will always do the same thing under all circumstances, and provide a consistent and clear context for the function’s execution.
React’s official solution for how to link to an event while having access to the event, a passed in argument, a correctly assigned this
, while also being compatible with async is as follows:
onChange=¯\_(ツ)_/¯
That’s just one example, of course. Just one way React can hurt someone with anything less than perfect skills. I wanted to get that out of the way first, because I think it’s the critical difference between React and every other library or framework discussed, and it puts some of the below in context.
Getting Started
Starting a React App these days is the same exact process as every other framework. It wasn’t always, but thanks to create-react-app there’s some sanity about starting a new project.
Running create-react-app react-todo
got me up and running with a build process through Webpack, router, etc. Everything a growing app needs. Running npm run start
will load the dev server. That’s… about it. The create-react-app cli tool is incredibly bare bones, and offers very little in the way of help. After that you’re on your own. As someone used to Ember’s highly productive CLI-based work flow, not to mention Angular and Aurelia doing the same, React’s “right click → new file” or copy and paste driven development feels pretty primitive, not to mention slow and error .
Setting Up Routing
The next step is setting up some routes, and that means coming to terms with React’s router setup. Put simply, React’s router was the hardest to use and frankly straight-up ugly.
Contrast React with Ember here.
//React
import Home from 'components/Home';
import Todos from 'components/Todos';
import About from 'components/About';render(
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/todos" component={Todos} />
<Route path="/about" component={About} />
</Switch>
</BrowserRouter>
);//Ember
Router.map(function() {
this.route('about');
this.route('todos');
});
The Ember code is auto-generated, too, I didn’t evenhave to write it.
This isn’t the only way to do routing in React, but it seems to be the standard, and is pretty much a shit show.
The above is what I’ve ended up with, but could well be wrong, and not in an obvious way. I initially assumed it was React Router I wanted. Because I’m a fucking idiot who doesn’t know anything. I actually needed to install React Router DOM. That means that I previously ended up with <Router>
and it seemed like it should work but it didn’t. Naturally this was before I found the official documentation — some guy’s Medium article.
This is common with React. There’s decent enough documentation for React itself, but with the number of things required to make a React app work properly — Redux, Redux Thunk, React Router, React Saga, and various middlewares — these things just aren’t documented, at least not in a cohesive way. All too often the closest to official documentation is a Medium article or Stack Overflow post. It’s not helped that React’s routers has changed versions to React Router 4 recently and some syntax changes have been significant.
Listing the Todo Items
Again, the next step is to get access to the API and start looping through the todo items and get a basic list.
Data Access
Unlike previous options, React leaves HTTP access entirely up to the writer. There’s nothing built-in, nothing preferred. I chopped and changed between Fetch and Axios, for no particular reason and they both worked essentially the same. I’ll use Axios here, though.
The pattern was fairly obvious and reasonably well documented. React (like everything else) has a series of lifecycle hooks on components, one of which is componentWillMount, but in truth it seems easier to use the constructor, as it’s just a standard class. The following approximates what was in there, though I’ve probably missed something.
constructor() {
axios.get('http://todo-api.dev/todos').then(({data}) => {
this.setState(items: data);
})
}
You’ll note that setState call. That’s React’s internal component state management. Each React class component extends a base component, which gives it this functionality. It has to be set up in the constructor of the class, though, explicitly defining an object called this.state
which has the keys you want to look after. Changes to that state can (or at least should) only be done with setState.
You might also notice that I said it was approximately this code. If you’re wondering why I don’t just check, the fact is I scrapped all of the above in a complete rewrite, for reasons I’ll go into later.
Working with JSX — first of all, I don’t like JSX
Looping over and displaying the items in a React app takes JSX. I should pre-empt the people saying that you don’t have to use JSX by saying that you don’t have to poo in a toilet either, but everyone does and you’re weird if you don’t.
I have issues with JSX. The main issue I have with JSX is that every time I bring up my issues with JSX, it causes a flood of dumb things. I considered writing my issues with JSX into a separate article so that people could put all of the trite non-responses into one place.
One thing I want to clear up is that I know that JSX is “syntactic sugar”. People keep bringing that up as a dismissal of any criticism like people “just don’t get it”. We get it. You can be aware of something being an abstraction while simultaneously thinking that it’s
a. A shitty abstraction
2. An abstraction over something shitty
The problem I have with JSX is and has always been the violation of principles of separation of concerns. This is dismissed usually by redefining both “separation” and “concerns”, rather than addressing the reason people bring it up.
I’ve looked into this to see whether it genuinely does violate strict “separation of concerns”, by taking it back to first principles of code reusability. My conclusions based on that are a big shrug. I really don’t know. I do know that there are reasons that separating technologies has merits, which are not addressed by dismissal. Separating technologies allows more focused editing, and separates out areas of responsibility. Specific template files give more more out of the box syntax highlighting, for example, than everything being a .js
. A dedicated templating DSL also honours the Law of Least Power, something that a full scripting language does not.
Speaking of which, it’s constantly brought up that JSX is “just JavaScript”, and that’s preferable to learning a vendor specific DSL. I’d reject both parts of that claim. I’d argue that JSX in fact is a vendor specific DSL. It’s clearly neither standard HTML nor JavaScript. I’d also argue that templating DSLs have been computer science standards and best-practise for decades. I’ve used Twig, Smarty, Razor, Handlebars, Plates, Blade, Angular, Knockout, and more and not one of them took a significant amount of time to learn. The vocal rejection of the complexity of a templating engine is quite amusing in a community that enthusiastically embraces Redux.
Working with JSX — Secondly, JSX isn’t that bad
Regardless of that philosophical issue, using JSX isn’t difficult. It has some annoyances. The JSX className
thing isn’t a big deal when using the simple HTML found in demos, but it’s a little more of a pain when using something like Bootstrap. Bootstrap markup is like me: classy as fuck. This means markup can’t be copied and used directly, can’t be generated by common tools, doesn’t syntax highlight or auto-complete like standard markup, etc.
Anyway, I got distracted again. Using JSX is easy because component structure can be built up by using functions that split out elements of the UI. This means that each of these functions returns relevant markup. Because it’s a good idea for functions to return markup if you’re using well engineered solutions like React or Wordpress. *cough*. Anyway, building and selecting these function is very much standard JavaScript, and that was something I enjoyed a lot.
Every component has a render()
function and in demos they typically return JSX.
render(){
return <h3><Title /></h3>;
}
Obviously this is a quality component. But there’s a gap between the function and its return that is capable of complex calculation, simple conditionals, or more.
render(){
const button = user.loggedIn ?
this.logInButton() :
this.logOutButton(); return (
<nav>
{this.menuLinks()}
{button}
</nav>
)
}
This arbitrary bad markup example still shows what the goal is.
I really like this. The ability to compose markup like this by calling relevant functions is surprisingly intuitive. Conditionally setting variables, calling functions, etc. Makes it nice and easy. I mean, obviously it’s wrong and bad and everything but I still quite like it. Don’t tell anyone.
JSX lacks any kind of control structure, including loops. That’s all handled through JavaScript itself, so my component ended up something like the following.
renderList() {
return this.state.todos.map((todo, index) => (
<li className="list-group-item">{todo.title}</li>
));
}render(){
const listItems = this.renderList(); return (
<ul class="list-group">
{listItems}
</ul>
)
}
Bootstrap and the Active LI Issue
Installing Bootstrap and Font Awesome was trivial in both cases. This is a very standard Webpack setup, and has no problem with well established tools.
It was the Active LI that quickly turned into an abysmal clusterfuck. Just to recap, Bootstrap wants its “active” class on the li, rather than the actual a tag, something that isn’t the default for frameworks. Getting access to the current route in a link wasn’t particularly intuitive. In theory the NavLink
component should do it, but because the element we want to modify isn’t the link itself that wasn’t possible. Using Bootstrap, I found I needed to install React Router Bootstrap and React Bootstrap. And that added a new component of LinkContainer
. For some reason I had a weird issue with this, meaning it was always considering the index page as “active” for whatever reason. I tried a lot to fix this, including using Switch
and making sure my home path was set to exact. In the end it turned out I needed a separate component again, an IndexLinkContainer
. Debugging and fixing this sort of nonsense is time consuming and frustrating. When React people tell you how easy React is, they tend to skip over this kind of bullshit. Thanks to installing React Router Dom, React Router Bootstrap, and React Bootstrap I ended up with this.
<Navbar>
<Nav>
<IndexLinkContainer exact path="/">Home</IndexLinkContainer>
<LinkContainer path="/todos">Todo List</LinkContainer>
<LinkContainer path="/about">About Us</LinkContainer>
</Nav>
</Navbar>
That all looks fine, but for some strange reason, that markup when implemented puts a spare container element inside the first navbar. This leads to a bit of annoying margin on the left hand side of the home link. Why the hell that’s in there I’m not sure.
edit: I looked into the above more, and it turns out Bootstrap best-practise is to put a container inside a nav where the parent container is not fluid. I’m not sure why, but I’m not going to criticise React Bootstrap for being right.
List Item Components
Obviously we want to do more than just loop and show the titles, so we need to have a component to isolate those behaviours. This is where things get a bit strange in React. Not all components are equal, and terms come flying at you quickly.
A component can be a smart component, a layout component, a render callback component, a conditional component, a functional component, a presentational component, a higher order component, a controlled component, a container, a class-based component, a stateless component, or a child component.
And I only made up one of those. Maybe two.
A functional component is a simpler form of component, which is suitable for basic components that just need a render function, with little or no behaviour. They’re also not capable of having a state, and need their state passed in as a prop. They would be an ideal use for the application I was making, so of course I didn’t know I was supposed to use them and did everything wrong.
I built a component with a lot of assumptions, based largely on the components I built for previous frameworks. In retrospect I would do it differently, breaking down into smaller stateless functional components, which is apparently more idiomatic for React. In truth this is probably good advice for any modern framework. Smaller, more focused components with less or no state.
My larger component went fine, though. The content of an li
tag modified by the presence of a state isEditing
variable, then buttons that set internal state, etc.
Updating and Creating
Rob Eisenberg, the developer of Aurelia and Durandal, said something in a presentation a while ago that I’m reminded of now.
React is much better for read only apps. It’s more difficult for input intensive apps. — Rob Eisenberg
Editing with React is odd. There’s a particular pattern that occurs, where the value of an input needs to be reactive. This is called a “controlled component” in React parlance. React disapproves of two way binding — I’m not sure if it is even possible. The approach instead is that the value of an input is set from state. Changes to the input update the state, but state changes trigger a re-render. Which would wipe the value that was just set.
This means that every input being changed needs an explicit handling method (though admittedly it’s usually achievable as an inline setState call) and a property defined in the state. That’s a lot of boilerplate for something other frameworks handle without the bullshit. It’s also a very circular and oddly inelegant pattern which is not at all intuitive, and quite error-prone. It’s easy to end up with broken fields as typing redraws out your changes.
Inelegant or not I ended up with a solution but it involved some obvious downsides.
Updating state was successfully achieved, but that was only local component state. One way binding means the actual state of that object is now ambiguous. The item component is in one state, the list component is unchanged.
You need one of two approaches to manage state: two-way binding, or a central store. Angular chooses two way binding. So does Aurelia. Ember does both, two-way binding to the store. React does neither. This leaves the user in charge of all state maintaining. This is something I expected would be easy in such a simple app. I was going to be smug and point out that see, you probably don’t need Redux.
And to a degree I was right.
The correct solution here would be “lifting scope”. Rather than having a “smart” TodoList component and a smart TodoItem component with its own internal state, I could have a dumb and simple TodoItem. All the state managed by the TodoList and then passed into the item.
In principle the item component could then be a “stateless functional component”, a much simpler structure that eliminates local state for a corresponding increase in clarity and performance. In practise I didn’t bother — though I didn’t need the local scope I did want to keep the helper functions I was using for the interface elements. Ideally those helper jsx functions could themselves be stateless functional components, but that was a step I didn’t bother going down.
The above was the correct solution. I have to stress that for an application of this size the above was absolutely all that would be necessary, and all that would be preferred.
But the intent of this investigation wasn’t to find out how to make a ToDo app in each framework, that’s trivial. It was to implement it like a real production app. In order to be honest and useful it would need to implement this app as if it was a bigger and more complex app, dealing with a lot more concerns. It really needed to use Redux.
I have to say again that Redux is not always necessary and is far too often reached for when React’s internal setState makes much more sense. I have used it here in full knowledge that it was a needless complexity.
Refactoring to Redux
Devotees will tell you that Redux is a simple pattern, that it just uses a few basic elements so it can’t be complex. But that’s misleading — human DNA is made of only four nucleobases and we’re pretty complex.
In truth I found Redux to be the single most frustrating and complex thing in this entire project. While Redux itself is (arguably) a simple enough pattern, the process of binding a React app to the Redux pattern is a complex one. I watched numerous video series and eventually signed up for a paid course on Udemy, just trying to learn this shit. No other framework required professional tuition.
People telling me this is simple and dismissing it can go fuck themselves. Redux is simple…. once you understand it. But gaining that understanding is not a trivial thing, and it’s frustrating and insulting to have that challenge hand-waved. I’m speaking only from my own experiences, here. Maybe it’s easy for others, maybe I’m unusually thick, I don’t know.
It’s OK that it’s complex. Some things are complex. Some things take learning. But it’s better to acknowledge and accept that than pretend it’s not the case and treat new users like they must be some kind of moron to struggle. Again, it’s not so much Redux as React-Redux. Acting like Redux lives in a vacuum is misleading.
In any case, the core problem with implementing Redux in something like isn’t just that it’s complex. It’s that it’s irreducibly complex. With most complex systems or patterns you can muddle through it. You might have it a bit wrong, or be missing part of it, or dodgy up some elements. But with Redux in React you really need to have a complete and comprehensive understanding of the entire pattern, and have it perfectly implemented in order to get anything working at all.
Using Redux leaves the user with lines like this.
export default connect(mapStateToProps, mapActionsToProps)(TodoList);
This is a standard bit of Reactdux code that will be familiar and unremarkable to anyone who’s used the pattern, and completely arse to anyone who hasn’t.
The use of Redux adds a staggering amount of boilerplate to a React application. Every component with store access needs a function to connect state into the props, and then another one to connect actions in the same way.
function mapDispatchToProps(dispatch){
return bindActionCreators({fetchItems, createItem}, dispatch);
}function mapStateToProps(state){
return {
todos: state.todos,
currentItem: state.currentItem
}
}export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Roughly that (give or take) is required on every single component with access to the Redux store. React components with Redux are considered “containers”. And so this means a component can be a functional component, class component, container, or controlled component. Probably a bunch of other terms people throw out wildly and you have to look up.
The store itself has to be created and injected into the app as well, meaning a shitload more boilerplate for setting up the store and middleware in the index, though at least it’s confined to one place.
That is going to seem universally critical, and to some degree it is. There are things I like about Redux. Most particularly I like it as an organisational pattern. Events can all be managed in one place, as can the effects they have. HTTP requests, for example, can be put in the actions. The monolith frameworks all have a service injection pattern or in Ember’s case, a larger abstraction over data access. The library based frameworks, React and Vue, can end up with things like api calls scattered through various lifecycle hooks. This is one of the things I like least about the solution I ended up with for Vue. There’s no cohesive, correct place to put things like this. Flexibility is seen by many as a benefit, but there are benefits to something being guided and structured, especially if there are well built escape hatches.
The thing about Redux is… it’s hard to put this in a way that isn’t going to make React developers really mad. But I don’t see the big deal. There’s nothing Redux does that Angular’s Observables doesn’t do with more functionality and less boilerplate.
If you just want a store, Ember has one built in out of the box and it’s tied beautifully in with the rest of the framework. Vuex similarly manages state with vastly less boilerplate, though it’s really just a store, lacking any structural advantage.
Don’t get me wrong, I see the benefit of Redux. But it really is a lot of boilerplate and complexity. In truth, Redux feels like a base pattern that the framework I want to use would abstract for me. That is, after all, the point of frameworks.
I’m not alone in thinking this, and there are abstractions for Redux such as Kea. Kea takes away a lot of the gibberish binding code by using decorators. I’m not convinced it’s really an abstraction, and may just end up moving all of the code into the decorators instead.
React Tunnel Vision
One thing I do want to call out before I finish. In doing this article I researched extensively what benefits frameworks have over alternatives. For React more than any other, its advantages are — to be frank — complete bullshit.
For example, a React presentation made literally a week before writing the React section highlighted “Virtual DOM” as a key benefit of React. But V-DOM diffing is the pattern used by all of these frameworks, with the exception of Aurelia. It’s hardly a point of difference.
Here’s a fine example: https://www.altexsoft.com/blog/engineering/the-good-and-the-bad-of-reactjs-and-react-native/
Every benefit listed here is common to every other framework with the exception of v-dom mentioned earlier. If these are reasons to use React, and they’re not valid, does that mean there’s no longer a reason to use React? Ironically, every con here is unique to React.
I’d recommend React supporters look seriously at some frameworks like Angular or Ember. There are some great ideas, and the developer experience may well be a breath of fresh air. This isn’t to say that you should abandon React, but it might be worth knowing what’s happening across the fence. Having multiple tools in your box can always be beneficial.
Conclusion
I’m glad I came to grips with React. To be completely honest it’s one I’ll definitely continue with. Not because I love it, but because React dominates the frontend space in certain areas, such as remote work and startups.
React developers more than anything remind me of people who insist that you have to build your own PC from parts. That way you get complete control over every aspect of the final product. But I did my time doing that shit. I did my time sourcing optimal parts and putting them together. I’ve cut the crap out of knuckles and stabbed myself with screwdrivers. (I didn’t say I was good at it.) These days I just go to an Apple Store and buy a laptop. I’ve got things to do and I want a tool that helps me do it.
Ultimately it comes down to abstractions. I’m a fan. I like a good abstraction. I think they’re useful and important. There’s no right answer here, I’m speaking from my own perspective and preference. And to me, the boilerplate-heavy and error-prone nature of a React application is a concern. The need to learn and understand a range of solutions from Redux to Thunk to Saga, etc, means these things fly thick and fast.
Where my analogy about building a PC falls apart is that building a PC actually does make for a better product in the end. React devotees will make the same claim. I’m not convinced by it.
But more than any other framework I know I’m just scratching the surface with React. There’s a lot to learn. I hope it gets easier.