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 challenges getting set up, 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.
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.
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).
Though there were a few times things got longer as well.
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.
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.
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.
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.
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 😅
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 a 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.
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.
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.
[2] Article graphic by Al Duncanson
Mark is a software developer and teacher. In his work he tries to write decent code, support diversity and social justice in tech, and energetically frown at people who use the word "disruption" more than they use the word "ethical”.
Outside of work, he hikes, attempts to commit to musical instruments, feels feelings about things, and silently guesses who uses Tumblr based on their word and spelling choices.