2019-05-17
|~5 min read
|843 words
When all you have is a hammer, every problem looks like a nail.
When approaching new problems, it’s easy to fall into old ways of doing things - at least at first. There’s an efficiency argument to be made for this. If we have reliable solutions to solving certain problems, it doesn’t make a ton of sense to look for alternatives unnecessarily.
New problems, by definition, are unfamiliar territory - so the old solutions may be extendable (with our without any need for modification). To know, we have to start working on the problem and starting with what we know is a reasonable place.
That approach is how I found myself starting at an error screen having triggered an infinite loop.
I was working on a new page in our application that would show a user’s preferred photo. User photos are stored on a database, which meant that I would need to make a database call and wait while that resolved.
This particular project is using the new Hooks API within React, and I wanted to follow suit. Whereas I had a pretty good idea of how I might approach this for class components, I had never done any asynchronous work with hooks.
However, having recently thought about the lifecycles in React 1 and then reading the React team’s docs on hooks, I thought I had a decent framework to think about this. In a class component, I would have put the async call in the componentDidMount
phase, and the docs showed examples where useEffect
was used in lieu of the componentDidMount
- so that’s where I started.2
The hammer in my toolbox - the way I would have approached this problem for a class component - can be summarized with the steps:
componentDidMount
to wait for the DOM to be availableimport React from "react";
import { PhotoAPI } from API;
class MyComponent {
constructor(props){
super(props);
this.state = {
photo: '',
}
async componentDidMount() {
const photo = await PhotoAPI.getPhoto(...)
this.setState({photo})
}
render() {
<React.Fragment>
<img src={this.state.photo} />
</React.Fragment>
}
};
Given my foundation with class components for asynchronous calls, that’s where I started with my new functional component.
(Actually, this skips the first attempt where I tried the useEffect
method with async
— it’s not supported, though Robin Wieruch shows how you might accomplish data fetching with useEffect
.3)
Notice, this approach looks very similar to the class approach. I have a local state, I am using useAsync
which awaits the return of the API call. Then setting state using my setPhoto
method, I can now use that value in my return which will render to the DOM.
import React, {useState} from "react";
import { PhotoAPI } from API;
import { useAsync } from "react-use";
function myComponent(props) {
const [ photo, setPhoto ] = useState(undefined)
const primaryPhoto = useAsync( () => PhotoAPI.getPhoto(...), [photo]);
setPhoto(primaryPhoto.value)
return (
<div>
<img src={photo} />
</div>
)
...
};
There’s only one problem: this creates an infinite loop.
I only call useAsync
when the value of photo
changes (notice the [photo]
in the second argument position for useAsync
), but I set that value each time it returns, which means I can call it again, and again, and again…
I guess my reliable solution wasn’t so reliable any more.
Before I started working with Hooks, I noticed a lot of people make comments along the lines of “it’s a different way of thinking.” The React team themselves call this out specifically in their introduction to Hooks4> :
It takes a bit of a mindshift to start “thinking in Hooks.”
When refactoring to hooks, we actually don’t need to use state at all. Instead, the useAsync
method returns a promise which has a loading
attribute and once it’s resolved a value
.
This makes our new code succinct and very readable.
import React from "react";
import { useAsync } from "react-use";
function myComponent(props) {
const primaryPhoto = useAsync( () => API.getPhoto(...), []);
return (
<div>
{ primaryPhoto.loading
? loading...
: <img src={primaryPhoto.value} />
}
</div>
)
...
};
While it’s tempting, and I would argue smart, to start a new problem by attempting to reuse old knowledge - sometimes it doesn’t work out. The problems can be fundamentally different in ways you couldn’t have anticipated. The new problem can also offer new opportunities, different approaches, and surprising lessons.
You can shoe-horn old solutions into new problems or create new skills and add tools to your tool chest. It takes work and can be frustrating — or fun. The choice is yours.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!