Transforming Fullstack Development with Remix: A React Comparison
Tags: remix, javascript, react, beginners
Published: 2024-06-03 01:07:04

In my recent livestream, I discussed the crucial concept of [Fullstack Data Flow](https://remix.run/docs/en/main/discussion/data-flow) in the Remix Framework. I mistakenly referred to it as the Remix Full stack cycle, so I want to clarify. This overarching flow within Remix is essential to grasp as you dive into this framework.

I aim to compare the new approach to building with React in Remix with our traditional way of using React alone, often with multiple libraries. This comparison should be particularly beneficial for React developers who are still undecided about adopting full stack frameworks like Remix.

Currently, Remix is built around React Router to enhance Server Side Rendering capabilities. It offers built-in support for nested routes, providing a more manageable structure through a single directory: `app/routes`.

The intricacies of building routes in Remix warrant their own blog post, so I won't dive into that here. I appreciate the variety of options available though, and many engineers have strong opinions about their preferred approach.

## Table of Contents

1. [Let’s talk about the flow of data](#lets-talk-about-the-flow-of-data)
   - [Loaders](#loaders)
   - [Components](#components)
   - [Actions](#actions)
2. [React alone in comparison](#react-alone-in-comparison)

## Let’s talk about the flow of data

The flow is split into three phases:
**Loaders → Components → Action**

![Remix FullStack Data Flow made on Miro](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jb2m2a9of2vboza5pl9j.png)

### Loaders

In Remix, `loader` functions are used to fetch data on the server-side. They should always be defined in route files.

Keep in mind by default in Remix we are rendering our data from the server or also known as Server Side Rendering (SSR)

**Example:**

```jsx
export const loader = async ({ params }) => {
  const data = await fetchData(params.id);
  return json(data);
};
```

### Components

In Remix, components receive the data fetched from loader functions to be rendered. Components access this data with `useLoaderData` hook.

**Example:**

```jsx
import { useLoaderData } from "@remix-run/react";

export const loader = async ({ params }) => {
  const data = await fetchData(params.id);
  return json(data);
};

export default function Post() {
  const data = useLoaderData();
  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
    </div>
  );
}
```

### Actions

Action functions handle form submissions, validations, and other mutations. They are called when a form is being submitted, allow the server to process the data, update if successful or return validation errors that indicate to the user what failed.

**Example:**

```jsx
export default function NewPost() {
  const actionData = useActionData();

  return (
    <div>
      <h1>Create a New Post</h1>
      {actionData?.error && <p style={{ color: "red" }}>{actionData.error}</p>}
      <Form method="post">
        <div>
          <label>
            Title: <input type="text" name="title" />
          </label>
        </div>
        <div>
          <label>
            Content: <textarea name="content" />
          </label>
        </div>
        <button type="submit">Create Post</button>
      </Form>
    </div>
  );
}

export const action = async ({ request }) => {
  const formData = await request.formData();
  const newPost = await createPost(formData);
  return redirect(`/posts/${newPost.id}`);
};
```

As we’ve seen in Remix how we fetch data, render that data in our components, and handle data mutations through form handling, the overall pattern is not that different from when we used React alone but as you can see we are are rarely using our regular hooks `useState` or `useEffect`.

## React alone in comparison

This would compare to Remix’s `loader` function and `useLoaderData` hook.

```jsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';

const Post = ({ postId }) => {
  const [post, setPost] = useState(null);

  useEffect(() => {
    axios.get(`/api/posts/${postId}`)
      .then(response => {
        setPost(response.data);
      })
      .catch(error => {
        console.error("There was an error fetching the post!", error);
      });
  }, [postId]);

  if (!post) return <div>Loading...</div>;

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
};

export default Post;

```

That’s not bad but now there is less separation between the data and how we go about fetching that data. We’ve been used to that pattern for a while and got used to it.

Now let’s compare Remix’s `action` function and `useActionData` to using React standalone

```jsx
const PostForm = () => {
  const [formData, setFormData] = useState({ title: '', content: '' });
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};

    if (!formData.title.trim()) {
      newErrors.title = 'Title is required';
    }

    if (!formData.content.trim()) {
      newErrors.content = 'Content is required';
    }

    setErrors(newErrors);

    return Object.keys(newErrors).length === 0;
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: value,
    });
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    if (!validate()) {
      return;
    }

    try {
      const response = await axios.post('/api/posts', formData);
      console.log('Post created', response.data);
    } catch (error) {
      console.error('There was an error creating the post!', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="text"
          name="title"
          value={formData.title}
          onChange={handleChange}
          placeholder="Title"
        />
        {errors.title && <p style={{ color: 'red' }}>{errors.title}</p>}
      </div>
      <div>
        <textarea
          name="content"
          value={formData.content}
          onChange={handleChange}
          placeholder="Content"
        />
        {errors.content && <p style={{ color: 'red' }}>{errors.content}</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

export default PostForm;
```

Yes, we can definitely find ways to consolidate this and use third party libraries that help with forms like [react-final-form](https://final-form.org/react) or [Formkik](https://formik.org/docs/overview) but can we be honest that following Remix’s pattern helps us write cleaner code from the beginning? There’s a lot you don’t need to do in Remix because of it’s intuitive nature to rely on not only React Router but on the browser’s natural flow as well.

If you made it this far, thanks for reading. I talk more about Remix than I write about it. I host a livestream titled [Astrology & JavaScript Series](https://astrology-javascript-series.vercel.app/) on Wednesdays at 1pm ET on X and Twitch. I’m building a tool that helps you understand astrology more by learning the foundational blocks first.

I use Typescript within Remix and good ole Tailwind. I pull all the required data from [DivineAPI](https://divineapi.com/) (Astrology & JavaScript Series Sponsor!). They provide me all the information I need, working with their Western Astrology API. I talk often about my process, go through the code and do some live-coding as well.