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 mockingLink to Start mocking heading
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:
Break mock into componentsLink to Break mock into components heading
Now that we have the mock, we need to take a look at it and identify its 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 componentsLink to Build components heading
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} />;
Notice, that we can even reuse Input
inside PasswordInput
.
Use componentsLink to Use components heading
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>
);
This needs API interaction and event handling, but first...
Early optimizationsLink to Early optimizations heading
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>
);
And now we can create two new components with that:
const FormInput = labeled(Input);
const FormPasswordInput = labeled(PasswordInput);
So now, our LoginContainer
looks like this:
const LoginContainer = () => (
<LoginForm>
<FormInput name="username">Username</FormInput>
<FormPasswordInput name="password">Password</FormPasswordInput>
<SubmitButton>Login</SubmitButton>
</LoginForm>
);
Adding stateLink to Adding state heading
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>
);
};
The approach of avoiding state is related to Functional Programming principles but basically is to keep the components as pure as possible.
TL;DRLink to TL;DR heading
- Mock.
- Identify components.
- Build them.
- Use them (and optimize them when needed).
- Try to stay as stateless as possible. Add state only if needed.
That's it! Thank you for reading!