In my recent livestream, I discussed the crucial concept of Fullstack 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
Let’s talk about the flow of data
The flow is split into three phases:
Loaders → Components → Action
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:
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:
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:
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.
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
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 or Formkik 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 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 (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.
Top comments (1)
Hi Thought you might be interested in another Remix