DEV Community 👩‍💻👨‍💻

Cover image for Thinking in React: The 2020 version
Luke Shiru for Laser Reindeer

Posted on • Updated on

Thinking in React: The 2020 version

Take into consideration that this article is a few years old at this point, so you might want to look for a more recent version with modern practices. At the time of writing of this message the oficial React Docs are finally being updated, so maybe when you read this they are out of beta already. Remember to always look for up-to-date tutorials and documentation.

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 = props => <Input type="password" {...props} />;

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

const LoginForm = props => <form {...props} />;
Enter fullscreen mode Exit fullscreen mode

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>
            Username
            <Input name="username" />
        </Label>
        <Label>
            Password
            <PasswordInput name="password" />
        </Label>
        <SubmitButton>Login</SubmitButton>
    </LoginForm>
);
Enter fullscreen mode Exit fullscreen mode

This needs API interaction and event handling, 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 wrap it in a Label, so in order to keep DRY, let's make a curried function to wrap in Label:

const labeled =
    Input =>
    ({ children, labelProps, name, ...props }) =>
        (
            <Label {...labelProps}>
                {children}
                <Input {...{ name, ...props }} />
            </Label>
        );
Enter fullscreen mode Exit fullscreen mode

And now we can create two new components with that:

const FormInput = labeled(Input);
const FormPasswordInput = labeled(PasswordInput);
Enter fullscreen mode Exit fullscreen mode

So now, our LoginContainer looks like this:

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

Adding state

The 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 a 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 = useCallback(
        event => {
            event.preventDefault();

            fetch("/api", {
                method: "POST",
                body: JSON.stringify({ username, password }),
            })
                .then(response => response.json())
                .then(response => {
                    // Handle the response somehow
                })
                .catch(console.error);
        },
        [username, password],
    );

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

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!

Top comments (8)

The discussion has been locked. New comments can't be added.
Collapse
vte profile image
Timo Grevers

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.

Collapse
lukeshiru profile image
Luke Shiru Author

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 😊

Collapse
vte profile image
Timo Grevers

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.

Collapse
vikramvi profile image
Vikram Ingleshwar

Thanks a ton for your efforts, god bless you

Collapse
trimud profile image
Yuriy Boev

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

Collapse
lukeshiru profile image
Luke Shiru Author

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!

Collapse
officialksolomon profile image
officialksolomon

How are you able to pass children to the Label component without props.children

Collapse
lukeshiru profile image
Luke Shiru Author

children is just another property, so if you don't do anything special with it, you can just pass it along with the rest of the props using the spread operator (...). This two are exactly the same:

const Example = ({ children, ...props }) => <a {...props}>{children}</a>;

const Example = props => <a {...props} />;
Enter fullscreen mode Exit fullscreen mode

Extracting the children property is only useful when you'll do something with it, a few examples of good reasons to extract children:

const Example = ({ children, ...props }) => (
  <div {...props}>
    <span>prepend something</span>
    {children}
    <span>append something else</span>
  </div>
);

import { Children } from "react";

const Example = ({ children, ...props }) => {
  const childrenArray = Children.map(children, child => {
    /* Do something with each child */
  });

  return <div {...props}>{childrenArray}</div>
};
Enter fullscreen mode Exit fullscreen mode

In other cases where you're passing the children as a direct child of to the same element that has the rest of props, you can just pass children with the rest of the properties like I did with the Label component.

Hope that helps! Cheers!

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.