DEV Community

Cover image for Things I learned when I finish my first Next.js project
Phan Dũng Trí
Phan Dũng Trí

Posted on

Things I learned when I finish my first Next.js project

I decided to learn Next.js few months ago, partly because I heard people talking about it, but mostly because I wanted to learn new thing and improve my knowledge. So I opened Next.js documentation and read its concept. I was so impressed of its features that I created a new Next.js project and started coding right away. I already had an idea so that was the perfect time to implement it. There are things that you only encounter when practicing, and you will learn a lot not only from those things themself, but also the related ones. And here are the things that I've learned while doing my Next.js project.

Before we begin, here is my project: KEYREVEAL

Rehydrate DOES NOT work like Render

In the early days into the Server-Side Rendering world, for me, everything wasn't different so much from Client-Side Rendering. When finding resources on internet, I've encountered the term "rehydrate", but at that time, I didn't care really much about it. I thought it was as same as rendering. Until some thing unexpected happened.

const Component = () => {
  const data = useLocalStorage("saved");

  return data ?
    <Text>Data is: {data}</Text> :
    <Alert>There is no data</Alert>;
};
Enter fullscreen mode Exit fullscreen mode

This component will render the data retrieved from localstorage or show an alert if the data is empty or doesn't exist. useLocalStorage hook accesses the localstorage then returns the value of the passed key, if it can't find the key or the localstorage isn't accessible, then it returns undefined.

Because the localstorage isn't available on the server, it will form a page with an Alert then send that fully-formed HTML to the client. The HTML will be shown on the screen as soon as the client receives it. But this HTML is not fully interactive, it needs React to render and attach event handlers to it. This process is called rehydrate.

localstorage can be now accessed by the client and we assume that localstorage contains a key saved with value. So Component will be rendered with Text instead of Alert.

The problem is this component is breaking the rules. In the development environment, React will warn you about this behavior.

React warns about mismatched DOM

When React performs rehydration, it will reuse the DOM which is sent from the server. The rehydration process is optimized to be fast, it assumes that the DOM structure won't change so that there is no "spot-the-differences" job happens, it just tries to fit its DOM with the existing one.

Although React sometimes can handle this behavior very well, but in some cases, this will lead to bugs like the component is rendered in the unwanted place. If you intentionally want to render things different on the server and the client, there is a solution called two-pass rendering.

Two-pass rendering

In two-pass rendering, the client and the server will render the identical DOM. After the rehydration process, the client will start rendering the differences.

const Component = () => {
  const data = useLocalStorage("saved");
  const [isClient, setIsClient] = useState(false);


  if (isClient)
    return data ?
      <Text>Data is: {data}</Text> :
      <Alert>There is no data</Alert>;
  else return <Loading />;
};
Enter fullscreen mode Exit fullscreen mode

As you can see, both server and client will render the Loading because isClient has the same false value in two environments. The client and the server now use the same DOM structure, so how do we render the localstorage data?

You probably know that useEffect only happens in the client, so to render the localstorage data, we can set isClient to be true in useEffect.

useEffect(() => setIsClient(true), []);
Enter fullscreen mode Exit fullscreen mode

We only want this run once on the first render. React will recognize the update and perform re-rendering. The downside of this solution is the application needs to render the UI twice before it's fully interactive. But in the most cases, this isn't the big deal.

Component Libraries is not that bad

In the past, when working with component libraries like React Bootstrap, Material UI or Ant Design, I felt like I was put into an inconvenience small box. I thought I had to follow their rules and my creativity was imprisoned. Because of that, I designed the UI from scratch for most of my school project. And of course, the quality is just acceptable for... the school project, but I was pleased with it. Honestly, building the UI by myself taught me a lot of things about CSS and UI design that I would never learn from using Component Libraries.

My UI

Spend days writing CSS for the UI of my school project

Yep, it took me ages to complete the UI, so that I don't want to write all CSS by myself in this project, I want to give component libraries another shot.

I avoid Material and Bootstrap because I had bad experiences with them in the past and I also want to try a new thing. Geist UI is my first choice. It simple and I like its design. But then I realized it didn't meet my project requirement. I still have to write a lot to make it fit my project.

Once time I was surfing Reddit, I found a post in r/reactjs announcing a new release for a component library. Coincidentally, I was looking for a new component library at that time, so why don't give it a look.

Mantine UI

It was Mantine UI and it was beyond my expectation. The design is modern and beautiful. There are a lot of pre-built components, but not redundant, and useful hooks that can satisfy your needs. Each component has styles API which supports styling of each component part, this makes all Mantine components highly customizable. Mantine provides some extensions likes modals manager, notifications system, rich text editor,... so that you don't have to spend time building your own. I have a good experience when working with Mantine, every thing is so smooth. Honestly, it's my most favorite component library so far.

Use FormData to increase the performance of the form

When I see a React tutorial on the internet, if it uses form or input, highly chance it will use them as controlled components, which means each input will have its own state.

const Form = () => {
  const [name, setName] = useState("");
  const [address, setAddress] = useState("");

  const submit = (e) => {
    e.preventDefault();
    console.log(`${name} lives at ${address}.`);
  };

  return <form onSubmit={submit}>
    <input
      placeholder="Input name..."
      value={name}
      onChange={(e) => setName(e.currentTarget.value)} />
    <input 
      placeholder="Input address..."
      value={address}
      onChange={(e) => setAddress(e.currentTarget.value)} />
    <input type="submit" />
  </form>;
};
Enter fullscreen mode Exit fullscreen mode

The downside of this is when you type a single character in either of those input, it will make the whole Form re-render. It isn't a problem with small and simple form. But if you have a big and complex form, especially the form with dynamic inputs, you will notice the lag. It takes a bit longer to reflect the updated value of the input to the screen. Another downside is you have to create a bunch of states for each input in your form, but this can be solved by using useReducer hook.

If your form just basically collects the user data, then Javascript has a powerful built-in FormData interface that helps you get rid of those useState.

const Form = () => {
  const submit = (e) => {
    e.preventDefault();
    const fd = new FormData(e.currentTarget);
    console.log(`${fd.name} lives at ${fd.address}.`);
  };

  return <form onSubmit={submit}>
    <input placeholder="Input name..." name="name" />
    <input placeholder="Input address..." name="address" />
    <input type="submit" />
  </form>;
};
Enter fullscreen mode Exit fullscreen mode

No state, no re-rendering. Don't worry, your input data is still shown and updated in the input. The form is now uncontrolled.

The input doesn't necessarily live inside the form

Every thing inside a form, obviously, belongs to that form. So that the FormData constructor will know what fields the form has and the submit button will know it sends the event to which form.

But in some cases, the input can't be inside the form, how can we make it belong to that form? There is an attribute named form that specifies which form the input belongs to.

<form id="user-form" onSubmit={submit}>
  <input placeholder="Input name..." name="name" />
  <input placeholder="Input address..." name="address" />
</form>
<input
  placeholder="Input password..."
  name="password"
  type="password"
  form="user-form" />
<input type="submit" form="user-form" />
Enter fullscreen mode Exit fullscreen mode

With this attribute, you can put the input anywhere and it still belongs to the specified form.

Conclusion

There are still many new things I have learned when doing this project, but maybe I will share in another post. For me, practicing is the best way to discover and learn new lessons. You keep digging into it, the programming world will keep expanding.

Discussion (0)