POST DIRECTORY
Hooks banner

Have you been following all the buzz around the new feature coming in React, Hooks? Well, truthfully, you don’t need to; Hooks is only a proposal, and currently it’s only available in React v16.8.0-alpha.0. In a nutshell, Hooks will allow using ‘stateful logic’, previously only available in class-based components, in functional components. It will make it easy to share ‘stateful logic’ across multiple components without introducing the non-meaningful hierarchies you sometimes get with higher-order components or wrappers.

When Hooks does go to production, there is nothing to fear! Facebook has no plans to remove classes from React, so there’s no rush to learn them or migrate any existing code. But I’m here to report that Hooks is cool, is fun, and likely will change the way we write components.

For your gaming pleasure, I submit this trivia game, built using React Hooks and React Spring, a physics library and early adopter of the new Hooks api.

The Game

See the Pen Flash Cards by Jeneve Parrish (@jeneve) on CodePen.

The Code

To build this, I start by defining a hook, a function I’ll use to inject my App component with state and stateful logic.

const useFetchQuestions = () => {
  const [ questions, setQuestions ] = useState(null)
  const [ loading, setLoading ] = useState(true)
  ...

in the first 2 lines, using the built in hook useState I’ve written the equivalent to the following in a class that extends React.Component

class QuestionFetchingComponent extends React.Component {
  state = {
    questions: null,
    loading: true,
  }

  setQuestions = (questions) => this.setState({questions})

  setLoading = (loading) => this.setState({loading})
  ...

Next I define some helpers; I’m fetching trivia questions from the Open Trivia Database, a free, easy to use database of trivia questions that’s perfect for fun practice projects. The data I get back needs a bit of massaging to fit my needs…

const setNewQuestions = async() => {
  const response = await fetch("https://opentdb.com/api.php?amount=9&difficulty=easy")
  const data = await response.json()
  const formattedQuestions = formatQuestions(data.results)
  setQuestions(formattedQuestions)
  setLoading(false)
}

const formatQuestions = (rawQuestions) => {
  let formattedQuestions = []
  for (let i = 0; i < rawQuestions.length; i++) {
    formattedQuestions.push({
      ...rawQuestions[i],
      choices: [
        ...rawQuestions[i].incorrect_answers, rawQuestions[i].correct_answer
      ].reduce((a,v)=>a.splice(Math.floor(Math.random() * a.length), 0, v) && a, [])
    })
  }
  return formattedQuestions
}

Finally, I’m using the built-in useEffect hook to run the fetching side-effect after render, and returning the things my App component needs to do it’s work.

useEffect(() => {
  setNewQuestions()
}, [])

return { questions, loading, setNewQuestions }

Now my App component can be tidy and spare; it has questions, knows if questions are loading, and knows how to get new questions with just one line and with that it can render a loader, an array of questions and a button to get new ones.

const App = () => {
  const { questions, loading, setNewQuestions } = useFetchQuestions()
  ...

Let’s take a peek at my Card component, which uses the react-spring physics library to produce a pleasing animation effect on hover.

const Card = ({
  question, id, trans, index
}) => {
  const [ showBack, set ] = useState(false)
  const { opacity, transform } = useSpring({
    opacity: showBack ? 1 : 0,
    transform: `perspective(1000px) rotate${trans}(${showBack ? 180 : 0}deg)`,
    config: { mass: 10, tension: 500, friction: 80 },
  })
  ...

The Card component uses the built-in hook useState to keep track of whether the front (showing the question) or the back (showing the correct answer) is shown. It uses the react-spring hook useSpring to effectively animate data; in this example, it will change the numeric values for opacity and transform over time according the ‘physical’ properties defined in config.

useEffect(() => {
    const showBack = () => set(true)
    const hideBack = () => set(false)
    const el = document.querySelector(`#${id}`)
    el.addEventListener('mouseenter', showBack)
    el.addEventListener('mouseleave', hideBack)
    return () => {
      el.removeEventListener('mouseenter', showBack)
      el.removeEventListener('mouseleave', hideBack)
    }
  }, [])

Next, I’ve again used the built-in useEffect hook to give this functional component a lifecycle. On render it will add event listeners, and on dismount, it will remove them. In addition to the advantage of saving lines of code and the ability to reuse this logic, this pattern is easier to read and understand. Let’s take a look at the 2 alternatives, pared down, you can see that with useEffect the relationship between setUp and tearDown is clear:

// with a class-based component
componentDidMount() {
  setUp()
}

componentDidUpdate() {
  setUp()
}

componentWillUnmount() {
  tearDown()
}

// with useEffect

useEffect(() => {
  setUp()
  return tearDown
})

Finally, useSpring will pass “animated” data to animated elements. Under the hood, these elements bypass React, animating in the background using a requestAnimationFrame loop.

return (
  <div id={id} className="Card">
    <animated.div 
      className={`flip front color-${index}`} 
      style={{
        opacity: opacity.interpolate(o => 1 - o),
        transform
        }}
      >
      <div className="question">
        {he.decode(question.question)}
      </div>
      <ul className="choices">
        {question.choices.map(choice => (
            <li>{he.decode(choice)}</li> 
        ))}
      </ul>
    </animated.div>  
    <animated.div 
      className={`flip back color-${index}`}
      style={{
        opacity,
        transform: transform.interpolate(
          t => `${t} rotate${trans}(180deg)`
        )
      }}
      >
      {he.decode(question.correct_answer)}
    </animated.div>
  </div>
)

Try it!

I came away from building this pretty excited about React Hooks and React Spring. And although I’ll wait until it’s fully adopted into React to use Hooks in my client-based work, I can see tons of use-cases, the most important being cases where different components need access to the same data, internal state or lifecycle methods. Hooks could make sharing that code simple and easy. I hope you like my trivia game, and if you’d like to experiment with React Hooks and React Spring yourself, I have created a pre-dependency-loaded template on Codepen you can use here. Enjoy!

''