Tag Archive

Tag Archives for " hooks "

Add Hooks to Class Components With Higher-Order Components

If you are using class components, but want to use hooks, you can’t do so directly. One strategy for adding them is to use adapter components. Another strategy is to use higher-order components to pass along data from hooks as props.

Example: Creating a MessageStore

Here’s our sample custom hook: We want to build a flash messaging system, similar to what’s provided by Ruby on Rails.

const useMessageStore = () => {
  const messageReducer = (state, action) => {
  switch (action.type) {
    case 'ADD':
      return [
        ...state,
        action.payload,
      ]
    case 'CLEAR':
      return []
    case 'DISMISS':
      return state.filter((message, index) => index !== action.payload)
    default:
      return state
  }
}
  const [state, dispatch] = useReducer(messageReducer, [])
  return [state, dispatch]
}

Using The Hook in A Functional Component

If we want to display or write messages to our queue from any functional component, we can do so by calling this hook:

const MyMessagingComponent = () => {
  const [messageStore, messageDispatch] = useMessageStore()
  return messageStore.map(message => <span>message.text</span>)
}

And we want to add a message from a function component, we can do it like this:

const MyButton = () => {
  const [messageStore, messageDispatch] = useMessageStore()
  const sendClickMessage = () => { 
    messageDispatch({ type: 'ADD', payload: 'Clicked the button'})
  }
  return (<button onClick={sendClickMessage}>Click Me!</button>)
}

Creating a HOC With Our Hook

Alternatively, we could create a component that passes the data from the hook along:

const withMessageStore = (WrappedComponent) => (props) => {
  const { state, dispatch } = useContext(MessageContext)
  return (<WrappedComponent
    {...props}
    messageStore={state}
    messageDispatch={dispatch}
  />)
}

Apply the HOC to Class Components

Using this method, we can now use our hooks in existing code, without having to refactor the whole thing:

class OldStodgyComponent extends React.Component {
  render() {
    return this.props.messageStore.map(message => <span>message.text</span>)
  }
}

export default withMessageStore(OldStodgyComponent)

Redux Now Has Hooks. A Before and After Comparison

Today the react-redux team released version 7.1.0, which adds hooks to react-redux! Here’s a quick comparison of how it could change how you write components.

First, A Brief Overview of The New Toys

  • useSelector: Pass in a function that takes the state as an argument and returns a value. Used to get a single value from state. Can act as a replacement for mapStateToProps.
  • useDispatch: returns a reference to the dispatch object. It can act as a replacement for mapDispatchToProps.
  • useStore: returns an instance of the store. Generally not recommended.

Old Example: A Search Form

An example component that stores a query and when a form is submitted to search. I wanted to keep the example simple, so use your imagination for the part where it fetches results.

import React from 'react'
import { connect } from 'react-redux'

const defaultSearchBar = ({ query, updateQuery, handleSearch }) => {
  const handleSubmit = (e) => {
    e.preventDefault()
    handleSearch(query)
  }

  const handleChange = e => updateQuery(e.target.value)
  return (
    <form onSubmit={handleSubmit}>
      <input
        name="search"
        value={query}
        onChange={handleChange}
      />
    </form>
  )
}

const mapStateToProps = state => ({
  query: state.query,
})

const mapDispatchToProps = dispatch => ({
  updateQuery: newQuery => dispatch({ type: 'UPDATE_QUERY', payload: newQuery }),
  handleSearch: newSearch => dispatch({ type: 'NEW_SEARCH', payload: newSearch }),
})

const connectedSearchBar = connect(
  mapStateToProps,
  mapDispatchToProps,
)(defaultSearchBar)

The New Component

In our new example, we have a few differences: We eliminate the connect function, mapStateToProps, and mapDispatchToProps entirely. This means our component no longer takes in props directly. Now, our form looks like this:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'

const hookedSearchBar = () => {
  const dispatch = useDispatch()
  const query = useSelector(state => state.query)
  const handleSubmit = (e) => {
    e.preventDefault()
    dispatch({ type: 'NEW_SEARCH', payload: query })
  }
  const handleChange = e => dispatch({ type: 'UPDATE_QUERY', payload: e.target.value })

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="search"
        value={query}
        onChange={handleChange}
      />
    </form>
  )
}

Creating Your Own Custom Hooks

If you have code that gets shared frequently between components, you can create a custom hook to keep all of that functionality together. Here’s an example of us isolating the redux-specific part of our form into its own hook:

useSearchQuery = () => {
  const dispatch = useDispatch()
  const query = useSelector(state => state.query)
  const updateQuery = query => dispatch({ type: 'UPDATE_QUERY', payload: query })
  const updateSearch = search => dispatch({ type: 'NEW_SEARCH', payload: search })
  return { query, updateQuery, updateSearch }
}

Should You Make The Switch?

The ability to create redux hooks above is interesting, but I am also concerned that it could make code harder to test, as testing these components is already dead simple. I’m not sold either way, but I hope this comparison makes it easier for you to make informed decisions about your code base.

How to Use useEffect (and other hooks) in Class Components

Hooks are a great new feature in React. The first initial case I found them useful was where we had to create class components just because we wanted to use a single ref or store one variable in state. Now in those cases, we can use hooks to write more succinct code. Here’s a quick example using useState :

Using useState

Old and Busted

class SearchBar extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      query: ''
    }
  }

  render() {
    return (
      <form action="https://duckduckgo.com">
        <input
          type="text"
          name="q"
          onChange={(e) => { this.setState({ query: e.target.value }) }}
        />
        <input type="submit" value="search" />
      </form>
    )
  }
}

New Hotness

const SearchBar = () => {
  const [query, setQuery] = useState('')
  return (
    <form action="https://duckduckgo.com" >
      <input
        type="text"
        name="q"
        onChange={(e) => { setQuery(e.target.value) }}
      />
      <input type="submit" value="search" />
    </form>
  )
}

Thinking in Effects

useRef is similar. However, working with useEffectis a bit different. It’s designed to replace several lifecycle methods, such as componentWillReceiveProps, componentWillMount, and componentWillUnmount. It doesn’t map one-to-one to old functionality like state and ref, which makes it more difficult to “think in effects.”

Here’s what finally made it click for me: the first two arguments of useEffect are a function (didUpdate), and an array of dependencies. the didUpdate function can return a function of its own, a cleanUp function. When a component mounts or the dependencies are updated, didUpdate is called. When the component unmounts, cleanUp is called if present.

Once you grok how effects work (“when this variable changes, do something”) over the lifecycle methods, you can save yourself a lot of headaches in code. You no longer have to write code like this:

Old and Busted: componentWillReceiveProps

  componentWillReceiveProps = (nextProps) => {
    if (nextProps.position !== this.props.position) {
      this.moveMap(nextProps.position)
    }
  }

You can now write more succicnt statements like this:

New Hotness: useEffect

useEffect(() => { moveMap(position) }, [position])

Here’s another example is using useEffect to replace componentDidMount and componentWillUnmount for setting and clearing event listeners. By declaring the dependencies array as empty, you only call the didUpdate and cleanUp functions once each. No dependencies mean no updates.

Old and Busted: componentWill(un)mount

  componentWillMount = () => {
    /* attach listeners to google StreetView */
    const streetView = this.getStreetView()
    window.google.maps.event.addListener(streetView, 'zoomChanged', this.handlePovChanged())
  }

  componentWillUnmount = () => {
    window.google.maps.event.clearInstanceListeners()
  }

  getStreetView = () => { /* ... */ }
  handlePovChanged = () => { /* ... */ }

New Hotness: useEffect

  const getStreetView = () => { /* ... */ }
  const handlePovChanged = () => { /* ... */ }
  const { addListener, clearInstanceListeners } = window.google.maps.event
  
  useEffect(() => {
    const streetView = getStreetView()
    addListener(streetView, 'on', handlePovChanged())
    return clearInstanceListeners
  }, []) // empty dependency array = only called on mount and unmount

Instead of grouping functionality by when they are in the lifecycle, you can group them by concern. It makes your code easier to reason about.

There’s one problem. You can’t use useEffect (or any other hook) in a class component. Hooks are only available in functional components. If you want to refactor your lifecycle methods to use useEffect, you have to refactor entire class components writ large. This is both time-consuming and prone to error. What if you could refactor just this one part of the code?

Use Hooks With Adapter Components

Considering that:

  • we cannot use hooks within class components…
  • we can use hooks within functional components…
  • And we can use functional components within class components…

We can see a solution: create a functional component that encapsulates the useEffect behavior, and use that in your class components! It’s a take on the adapter pattern from object-oriented programming: We create a wrapper that encapsulates the functionality of a piece of code (useEffect), while changing its interface. A hook in component’s clothing. 🐺

Here’s a simple one I use to replace componentWillReceiveProps checks like the one above.

Observer: useEffect Adapter Component

const observer = ({ value, didUpdate }) => {
  useEffect(() => {
    didUpdate(value)
  }, [value])
  return null // component does not render anything
}

Example Usage

<Observer value={this.props.position} didUpdate={this.moveMap} />

You don’t have to pass the argument to didUpdate, but in this case I found that the easiest way to have access to that variable. You could also create a similar component to handle mounting and unmounting behavior.

MountHandler: another useEffect Adapter

const mountHandler = ({ onMount, onUnMount }) => {
  useEffect(() => {
    onMount()
    return onUnMount
  },[])
  return null
}

You could combine these into one component, or create more specific adapters that encase more business logic themselves. Do what works best for your codebase. Ideally, these a stop-gap solution that help you refactor class-based components incrementally. Here’s the strategy I used:

  1. Identify a class-based component you want to refactor.
  2. Isolate part of the class-specific code.
  3. Create an adapter component that allows you to remove that class specific code (for example, lifecycle methods)
  4. Run tests, ensure that all functionality still works.
  5. Repeat until you have removed all class-specific code.
  6. Convert the component from a class to a function.

In the end, You end up with code thats easier to reason about and more performant. Hope this helps!