loading...

Thinking in React: The 2020 version

lukeshiru profile image ▲ LUKE知る Updated on ・3 min read

Is 2020 and the original Thinking in React article still has class components in it, so I feel is time to do an updated version of it:

Start mocking

We should always start with a mock, either provided by a designer/design team in those big projects, or made by ourselves if it's a small personal project. So let's say we want the classic login experience:

Login Mockup

Break mock into components

Now that we have the mock, we need to take a look at it and identify its parts:

Login Mockup with highlighted parts

Once identified, we should use clear names for every "component" (PascalCase by React convention):

  • LoginForm (red): The whole login form.
  • SubmitButton (green): The button to submit the "form".
  • Label (pink): The form labels.
  • Input (orange): The form inputs.
  • PasswordInput (light blue): The form input with type password.

Build components

Now that we have identified the components, let's build them!

const Label = props => <label {...props} />;

const Input = props => <input {...props} />;

const PasswordInput = ({ type = "password", ...props }) => (
  <Input {...{ type, ...props }} />
);

const SubmitButton = ({ type = "submit", ...props }) => (
  <button {...{ type, ...props }} />
);

const LoginForm = props => <form {...props} />;

Notice, that we can even reuse Input inside PasswordInput.

Use components

Now that we have those components, we can use them to bring our mock to life. Let's call this wrapping component LoginContainer:

const LoginContainer = () => (
  <LoginForm>
    <Label htmlFor="username">Username</Label>
    <Input id="username" name="username" />
    <Label htmlFor="password">Password</Label>
    <PasswordInput id="password" name="password" />
    <SubmitButton>Login</SubmitButton>
  </LoginForm>
);

This needs API interaction and eventHandling, but first...

Early optimizations

While working on the components, we might detect optimizations such as every time we use an Input or PasswordInput component, we add a Label to it, so in order to keep DRY, let's create components to avoid repetition:

const FormInput = ({ name, id = `${name}-id`, title, ...props }) => (
  <>
    <Label htmlFor={id}>{title}</Label>
    <Input {...{ id, name, title, ...props }} />
  </>
);

const FormPasswordInput = ({ name, id = `${name}-id`, title, ...props }) => (
  <>
    <Label htmlFor={id}>{title}</Label>
    <PasswordInput {...{ id, name, title, ...props }} />
  </>
);

So now, our LoginContainer looks like this:

const LoginContainer = () => (
  <LoginForm>
    <FormInput name="username" title="Username" />
    <FormPasswordInput name="password" title="Password" />
    <SubmitButton>Login</SubmitButton>
  </LoginForm>
);

Adding state

State should generally be left for last, thinking and designing everything as stateless as possible, using props and events. It makes components easier to maintain, test and overall understand.

If you need state, it should be handled by either state containers (Redux, MobX, unistore, and so on) or a container/wrapper component. In our super simple login example, the place for the state could be LoginContainer itself, let's use React hooks for this:

const LoginContainer = () => {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  const login = async event => {
    event.preventDefault();
    const response = await fetch("/api", {
      method: "POST",
      body: JSON.stringify({
        username,
        password,
      }),
    });
    // Here we could check response.status to login or show error
  };

  return (
    <LoginForm onSubmit={login}>
      <FormInput
        name="username"
        title="Username"
        onChange={event => setUsername(event.currentTarget.value)}
        value={username}
      />
      <FormPasswordInput
        name="password"
        title="Password"
        onChange={event => setPassword(event.currentTarget.value)}
        value={password}
      />
      <SubmitButton>Login</SubmitButton>
    </LoginForm>
  );
};

The approach of avoiding state is related to Functional Programming principles, but basically is to keep the components as pure as possible.

TL;DR

  1. Mock.
  2. Identify components.
  3. Build them.
  4. Use them (and optimize them when needed).
  5. Try to stay as stateless as possible. Add state only if needed.

That's it! Thank you for reading!

Posted on by:

Discussion

pic
Editor guide
 

Thank you for writing! I saw some notations that I haven't used myself, so I'll have to keep those in mind. I was surprised to see the shorthand for <Fragment>, only to find out that it has been in React since 2017 :(

I've been trying to into Hooks (I have some projects with classbased components that could benefit from it). So when I got to the part referencing Hooks, I was a little bummed out that the article stopped there. Maybe it would make a nice subject voor a next article.

 

Sure! I can write a follow-up on how to think state and lifecycle the "2020 way". I saw several articles talking about how to move from class methods to hooks, but the idea behind hooks is doing things differently, not doing the same things with a different syntax.

Follow me either in here or in Twitter and I'll write that article soon.

Thanks for the inspiration, I'll mention this comment in the next post if you don't mind 😊

 

You have a new follower! While you are at it, it might be interesting if you can share your thoughts on (shared) state with useState and userReducer in comparison with Redux. As again, I have some projects on which Redux was used, but I have the feeling that the dependency on Redux could be dropped by using use* + Provider. At the same time, Redux might be the better choice for certain problems/scenarios.

 

Hi and thanks for writing this article.

Out of curiosity - why do you need different FormInput and FormPasswordInput when you can pass the input type as prop?

Thanks

 

Hi! Is just an abstraction so you can have separate behaviors/styles in those components down the line. Let's say for example that you want to add that 👁️ button some password fields have to show the password. If you had that directly in the Input then you need to add a ternary or if to show it only when type is "password", but with this approach you can have that button and all its styles encapsulated in the FormPasswordInput component.

Is funny how HTML has input with type to show all kinds of different elements, but for Headings they have h1, h2, h3, and so on.

Thanks a lot for taking the time to read and comment, Yuriy!