The Ins, Outs, and Pitfalls of Converting ReactJS.org to use Hooks

react

Cross-posted from the official Kickstart Coding blog

Last week we announced our adaptation of the ReactJS.org website to use all React Hooks examples and explanations. This week I thought I would write a bit more about what it was like to make that conversion. The main challenges were getting the existing version set up and running, re-writing the explanations, and getting the examples adapted both on the website itself and in the linked CodePen examples.

ReactJS.org is built with Gatsby, and this was actually my first time using Gatsby for anything significant. All things considered it went pretty well, although I will probably wait to see if Gatsby’s leadership adequately addresses their horrific corporate culture issues before deciding to use their tooling on any projects in the future.

Without further ado, let’s get into it.

Getting Up and Running

As it turns out, getting the ReactJS.org site running locally and deployed online is pretty straightforward. I had a couple of errors before I figured out I should be using yarn commands instead of npm commands, but after I figured that out, the basic yarn commands were all it took to get running:

yarn install
yarn dev

Getting it deployed was as simple as wiring it up to Netlify and the letting Netlify handle the rest. It may have been the most straightforward process I’ve ever gone through for getting someone else’s project up and running.

Converting the “Main Concepts” Pages to Hooks

Once I had everything running, I decided to start the updates by adapting the “Main Concepts” pages one by one. The “Main Concepts” pages cover most basic React concepts, and since they are divided up into shorter, individual explanations, I figured starting with them would be a good way to assess how much work it would be to convert the entire site. At this point in the process, we were still in the ”Let’s see how much work it is to make these changes before committing to adapting the whole site” phase.

As it turned out, these conversions where mostly straightforward: converting class components to function components and adding the requisite useState calls was the majority of the work.

I was pretty excited from the start to see that almost every example got shorter as I updated it. There was no more need for constructors, no more need to explicitly bind component methods, no more this, and also no more weird technically-not-valid-JavaScript method declaration statements like this

handleClick = () => {
  console.log("Is handleClick a let? A const? A...var?");
}

I also found that while some of the conceptual explanations did require some re-working, there were other sections I was able to get rid of entirely (for example, explanations of how this works).

a Git diff of removing an explanation of the “this” keyword

Though there were a few times things got longer as well.

a Git diff of changing a three-step set of instructions to a four-step set of instructions

Overall, most of changes I was making to the documentation seemed like evidence that the switch to hooks would make learning React an easier process for newbies than it had been before. And it was incredibly satisfying to watch almost all of the code examples get both shorter and easier to read.

a Git diff removing “this.state” from some code

a Git diff of some React code getting shorter

a Git diff of replacing a React constructor function with a useState call

Converting the examples was mostly straightforward. The hardest part of converting the ”Main Concepts” documentation turned out to be converting the conceptual explanations. Changing the existing writing on things like constructors, setState, and componentDidMount to bits on useState and useEffect. But after a few drafts and re-drafts, I got those to a place where I felt like they worked reasonably well.

Converting the ”Advanced Guides” section turned out to be more difficult.

Converting the “Advanced Guides” Pages to Hooks

While converting the ”Main Concepts” section was pretty straightforward, converting the ”Advanced Guides” pages required a lot more working through tricky code constructs and techniques.

Some advanced React concepts, like error boundaries, don’t exist in hooks at all. Others, like context updating, or the forceUpdate function, do exist but are more awkward to use via hooks. Some, including error boundaries, I decided to remove entirely. Others, like forceUpdate, I searched around a bit and was able to find explanations of how to update them for a hooks context.

I dedicated a section of the README to listing things I couldn’t figure out and worked my way as best I could through the rest. Fortunately, the advanced concepts that were the most trouble to adapt were mostly concepts that aren’t used very much by beginners.

The hardest part was probably the ”Integrating with Other Libraries” page. It’s been seven years or so since I’ve tried to do anything with Backbone.js. I still haven’t figured out a working conversion of the “Extracting Data from Backbone Models” section of that page on ReactJS.org, but I did manage to get a somewhat weird working version of the “Using Backbone Models in React Components” section cobbled together after much tinkering.

For a while as I was working on the “Main Concepts” and the “Advanced Guides” sections, I worked on converting the code examples on the documentation pages, but ignored the CodePen-based examples. Those I decided to comment out and save for later. Eventually, though, especially as I got to working on converting the “build a tic-tac-toe game” tutorial pages, it got harder to put those conversions off.

Converting the CodePen Examples

One of the first things I realized when I started converting the CodePen examples was that the React CDN used by CodePen doesn’t expose hooks functions directly. Everywhere I used functions like useState or useEffect on the documentation pages, I had to use React.useState and React.useEffect on CodePen. For a while I just dealt with this by peppering my code with React. statements, but after a while I decided to do some fiddling to see if there was a better way.

import and require statements are not available on CodePen, so that wasn’t an option. I briefly experimented with setting the hooks functions as constants at the top of each example, like this:

const { useState, useEffect } = React;

but that seemed messy, and it meant that there would still be differences between the code showing on the documentation site and the code in the CodePen links. This seemed like it could be confusing for people just starting out and fiddling around with the examples.

Finally, I hit on the idea of hosting a small JavaScript file that exposed the hooks functions myself. Once that file was online, I could import it with a script tag the same way that CodePen imports the React CDN. The resulting file, which is included in almost all of the CodePen examples, is at reactwithhooks.netlify.app/expose_hooks.js and looks like this:

if (typeof React !== 'undefined') {
  window.useState = React.useState
  window.useEffect = React.useEffect
  window.useRef = React.useRef
  window.useContext = React.useContext
  window.useReducer = React.useReducer
  window.useCallback = React.useCallback
  window.useMemo = React.useMemo
  window.useRef = React.useRef
  window.useImperativeHandle = React.useImperativeHandle
  window.useLayoutEffect = React.useLayoutEffect
  window.useDebugValue = React.useDebugValue
}

And just like that, the CodePen examples worked without any weird extra lines added to the main body of code. It’s all hidden away in the script tags.

The rest of the conversion of the CodePen examples went mostly smoothly, and it revealed quite a few bugs I had accidentally left in the code examples I had written on the documentation pages.

Smaller (mis)adventures

Semicolons

In the middle of this work I discovered that there was a contribution guide that I had not bothered to look for previously, and it stipulated among other things that all the JavaScript examples should use semicolons. Because I usually don’t use semicolons in my JavaScript, there are now about fifteen different commits in the git log specifically for adding semicolons back to every example ever 😅

Autorefresh Limitations

For the most part, Gatsby is set up to automatically recompile and reload pages as you make changes to project files. If you change a code example in the state-and-lifecycle.md file, the application reloads to show the new content. However, code examples that are stored in their own separate javascript files do not trigger automatic reloads. For those, it takes a full cycle of control + c, yarn dev, and waiting for the Gatsby start-up-and-compile process to complete to see what changes will look like.

When I first encountered this, I had some trouble figuring out why the changes I was making weren’t showing up. Was I editing the right file? Were there errors preventing it from completing some required process?

Once I figured it out, it wasn’t so much of a problem, although still a bit frustrating to have to do a complete re-start of Gatsby to see each change.

Search Bar

The last significant issue that came up was with the search bar the site uses. When students first started trying the site out, we discovered that the search bar was still directing them to pages on ReactJS.org instead of our reactwithhooks.netlify.app domain. This led to students seeing a confusing mix of hooks-based and class-based React explanations without knowing why the examples were changing so much from page to page.

After doing some digging, I discovered that the search bar on ReactJS.org uses a hosted site search service called Algolia. I did a little bit of research into the service to see about adopting it for our version, but decided for now to just leave the search bar off.

It’s Live!

All in all, making this adaptation was one of the most fun projects I’ve been able to work on recently. We got a chance to use it as a resource for students during our last cohort, and it seemed to work pretty well. I’m hoping that it will be useful for other people trying to learn React as well, and as with all of our open source work, feedback is appreciated. The repo is at github.com/kickstartcoding/reactjs.org (on the convert-all-to-hooks branch)[1] and the documentation site itself is live at reactwithhooks.netlify.app.

[1] Endless love to anyone with advice on anything in the issues I couldn’t figure out section of the README.