In our previous post, we successfully loaded review data from a Markdown file using front matter to handle properties like the title, date, and image path. Now, let's take it a step further by separating the data loading logic from the component itself. This will make our code cleaner, more modular, and easier to maintain.
Why Separate the Data Layer?
Separating the data layer from the presentation layer has several benefits:
Clean Code: Keeps the component focused only on displaying the data.
Reusability: Makes the data-fetching logic reusable across different components.
Flexibility: Allows you to change the data source (e.g., switch from local files to an API) without altering the component.
Step-by-Step Guide to Data Layer Separation
Step 1: Move Data Loading Logic into a Separate Function
First, let's write a new function called getReview that will handle loading and parsing the Markdown file. We'll define this function in the same file for now.
import { readFile } from 'node:fs/promises';
import matter from 'gray-matter';
import { marked } from 'marked';
async function getReview(slug) {
const text = await readFile(`./content/reviews/${slug}.md`, 'utf8');
const { content, data: { title, date, image } } = matter(text);
const body = marked(content);
return { title, date, image, body };
}
Step 2: Refactor the Component to Use the New Function
Now, let's update our component to use the getReview function. This way, the component will only be responsible for displaying the data.
import Heading from '@/components/Heading';
import { getReview } from '@/lib/reviews';
export default async function StardewValleyPage() {
const review = await getReview('stardew-valley');
return (
<>
<Heading>{review.title}</Heading>
<p className="italic pb-2">{review.date}</p>
<img src={review.image} alt={review.title} width="640" height="360" className="mb-2 rounded" />
<article dangerouslySetInnerHTML={{ __html: review.body }} className="prose prose-slate max-w-screen-sm" />
</>
);
}
Step 3: Make the Function Reusable
To make getReview truly reusable, we should not hard-code the review identifier. Instead, we will pass it as an argument. This way, the function can be used to load any review.
async function getReview(slug) {
const text = await readFile(`./content/reviews/${slug}.md`, 'utf8');
const { content, data: { title, date, image } } = matter(text);
const body = marked(content);
return { title, date, image, body };
}
Step 4: Move the Function to a Separate File
To keep our project organized, we should move the getReview function into a separate file. A common convention is to create a lib folder for utility functions.
Create a new file: lib/reviews.js.
Move the getReview function into this file.
// lib/reviews.js
import { readFile } from 'node:fs/promises';
import matter from 'gray-matter';
import { marked } from 'marked';
export async function getReview(slug) {
const text = await readFile(`./content/reviews/${slug}.md`, 'utf8');
const { content, data: { title, date, image } } = matter(text);
const body = marked(content);
return { title, date, image, body };
}
Step 5: Update the Component to Use the New File
Finally, update your component to import the getReview function from the new file.
import Heading from '@/components/Heading';
import { getReview } from '@/lib/reviews';
export default async function StardewValleyPage() {
const review = await getReview('stardew-valley');
return (
<>
<Heading>{review.title}</Heading>
<p className="italic pb-2">{review.date}</p>
<img src={review.image} alt={review.title} width="640" height="360" className="mb-2 rounded" />
<article dangerouslySetInnerHTML={{ __html: review.body }} className="prose prose-slate max-w-screen-sm" />
</>
);
}
Conclusion
By separating the data layer from the presentation layer, we have made our code cleaner, more modular, and easier to maintain. The getReview function encapsulates all the logic for loading and parsing review data, making it reusable across different components. This separation also makes it easier to switch data sources in the future without affecting the component code.
By following this approach, you can create more maintainable and scalable applications, setting a strong foundation for future development.
Happy coding!
Comments