question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Docs: Rewrite tutorial to show hooks as the primary API

See original GitHub issue

We’re now at a point where I’m comfortable teaching our hooks API first (which I’m also doing right now as I work on the new “Quick Start” Redux core tutorial page).

Given that, it would be nice to convert the React-Redux tutorial over. Would still be helpful to have the connect intro somehow as well, albeit secondary.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:3
  • Comments:33 (15 by maintainers)

github_iconTop GitHub Comments

4reactions
ricokahlercommented, Mar 20, 2021

We’re now at a point where I’m comfortable teaching our hooks API first (which I’m also doing right now as I work on the new “Quick Start” Redux core tutorial page).

I’m really glad we’re at this point! I think it’s the right direction, especially since reactjs.org itself is undergoing the same transition to hooks-first docs.

I’ve been using hooks and the react-redux hooks since they’ve come out and I have a few opinionated takes I wanted to share. If you agree with any of these, feel free to take inspiration in the docs (otherwise, feel free to ignore 😅)

Click to reveal wall of text

Why hooks first?

Hooks are the React team’s solution to many problems in React. They represent the continued future of React and as stated before, they’re also transitioning the current docs to be hooks-first.

I think it’s important to state why hooks should be the preferred option over connect. Here are my reasons (no particular order):

  • Much easier to use with TypeScript
  • Easier to compose with other pieces of global state (e.g. react-router). Discourages copying state into Redux
  • Easier to understand due to less indirection
  • Works well with the default React hooks and replaces the need for some memorization libraries (not a sliver bullet though)

Easier to use with TypeScript

This one is just easy to get out of the way. React hooks were built with static typing in mind. Properly adding types to a higher-order component is complex and confusing. Hooks return the values they inject inline, types intact. This holds true for react-redux.

Easier to compose with other pieces of global state

React hooks were built for composition. If you want to join two or more pieces of state, you can do so in the component inline now.

import { useSelector } from 'react-redux';
import { useParams } from 'react-router';

function Component() {
  // global state from react-router
  const { id } = useParams();

  // global state from redux combined with state from react-router
  const item = useSelector(state => state.entities[id]);

  // local state using the redux state as an initial value
  const [active, setActive] = useState(item.active);

  // ...
}

And custom hooks can elevate these into reusable pieces:

import { useSelector } from 'react-redux';
import { useParams } from 'react-router';

function useItem() {
  const { id } = useParams();
  const item = useSelector(state => state.entities[id]);
  
  return item;
}

function Component() {
  const item = useItem();
}

This contrasts heavily with some previous best practices back in the day. For example, the purpose of the proejcts react-router-redux and connected-react-router were to copy the state of react-router into redux state just so it could be used in mapStateToProps. Given the complexity of higher-order components, this was a very valid pattern but now seems like the wrong place given the ability to compose inline a component or in custom hooks.

Easier to understand due to less indirection

useSelector/useDispatch are APIs with a lighter footprint. You use them inline, directly in the component.

If you want to grab a particular item from state, you can directly return it from useSelector (with the types intact). If you want to dispatch an action, it’s perfectly fine to call dispatch inline

import { selectItem } from '../actions';

function Component() {
  const { id } = useParams();
  const item = useSelector(state => state.items[id]);

  if (!item) {
    return <>Loading…</>;
  }

  return (
    <>
      <h1>{item.title}</h1>
      <button
        onClick={() => {
          dispatch(selectItem(id));
        }}
      >select this item</button>
    </>
  );
}

Compare this with the connect version:

import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { selectItem } from '../actions';

const mapStateToProps = (state, ownProps) => {
  const { location } = ownProps;
  const { id } = location.match;
  const item = state.items[id];

  return { item }
};

// you could return dispatch directly however, that's not the meta for redux
// with useDispatch, you don't have a choice
const mapDispatchToProps = { selectItem };

class Component extends React.Component {
  render() {
    const { item, selectItem, location } = this.props;
    const { id } = location.params;

    if (!item) {
      return <>Loading…</>;
    }

    return (
      <>
        <h1>{item.title}</h1>
        <button
          onClick={() => selectItem(id)}
        >select this item</button>
      </>
    );
  }
}

// it's very hard to add types to this
export default withRouter(connect(
  mapStateToProps,
  mapDispatchToProps
)(Component));

Works well with the default React hooks

In particular, I’ve found it useful to intentionally not calculate values in useSelector and instead rely on built-in optimizations like useMemo and React.memo.

// note: this is an intentionally complex/contrived example
import { memo, useMemo } from 'react';
import { useSelector } from 'react-redux';

function Wrapper() {
  const { id } = useParams();
  const item = useSelector(state => state.items[id]);

  const { sublist } = item;
  const expensiveCalculation = useMemo(() => {
    return sublist.reduce((acc, next) => { /* ... */ }, {});
  }, [sublist]);

  return <ExpensiveRenderingComponent expensiveCalculation={expensiveCalculation} />;
}

const ExpensiveRenderingComponent = memo(({ expensiveCalculation }) => {
  // ...
});

And useSelector/useDispatch also work well with useEffect

function Component() {
  const dispatch = useDispatch();
  const count = useSelector(state => state.screen.count);
  const greaterThanFive = count > 5;

  useEffect(() => {
    if (greaterThanFive) {
      dispatch(showMessage());
    }
  }, [greaterThanFive, dispatch]);

  return // ...
}

In general, I like to be as React idiomatic as possible and hooks-first is the way 😎

Anyway, that’s all I got!

2reactions
markeriksoncommented, Mar 20, 2021

aaaand done! just nuked the versioning setup, so you’ll probably want to go ahead and do a fresh pull from master over into your fork before you get started. I also moved the legacy connect tutorial into /docs/tutorials/connect.md.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mark Erikson on Twitter: "I'd really love to have someone update the ...
Docs : Rewrite tutorial to show hooks as the primary API · Issue #1574 · reduxjs/react-redux. We're now at a point where I'm...
Read more >
Introducing Hooks - React
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class. This new...
Read more >
Hooks — Wagtail Documentation 4.1.1 documentation
This provides a way to register your own functions to execute at certain points in Wagtail's execution, such as when a page is...
Read more >
githooks Documentation - Git
See the documentation for each hook below for details. git init may copy hooks to ... See contrib/hooks/setgitperms.perl for an example of how...
Read more >
The Guide to Learning React Hooks (Examples & Tutorials)
I have provided another StackBlitz demo that enhances the original Context API example. Remember, this code below is not using Hooks, we will...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found