React Refactoring (w/Storybook): Separate UI and Business Logic
SeongKuk Han
Posted on September 24, 2022
React Refactoring (w/Storybook): Separate UI and Business Logic
When I joined my previous company, no one knew about the project code, the developer who made the project left.
There were many components, I didn't know what components had what kind of business logic. There were components that I wanted to reuse but I couldn't because they had a complicated business logic that could affect other components' behaviors. This makes it hard to recycle the component and reuse the UI, and sometimes I even made a component that already existed as similar looking.
One day, I decided to refactor them. For that, I separated UI and Business logic, and I used storybook
for listing components. After this work, I was able to reuse components' UIs easily and see how they look.
I'll show you an example of this.
There is a component TodayVisitorCard
that shows a number of visitors.
import { useEffect, useState } from "react";
import styled from "@emotion/styled";
const CardWrapper = styled.div`
border: 1px solid black;
border-radius: 8px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
padding: 24px;
text-align: center;
width: fit-content;
`;
interface VisitorResponse {
total: number;
today: number;
}
const TodayVisitorCard = () => {
const [data, setData] = useState<VisitorResponse | null>();
const [hasError, setHasError] = useState(false);
const fetchVisitor = async () => {
try {
const res = await fetch("http://localhost:7777/visitor");
if (res.status !== 200) {
throw new Error(`Response status is ${res.status}.`);
}
const data = await res.json();
setData(data);
} catch (e) {
console.error(e);
setHasError(true);
setData(null);
}
};
useEffect(() => {
fetchVisitor();
}, []);
if (data === undefined) return <div>loading...</div>;
return (
<CardWrapper>
{hasError && <div>Something went wrong!</div>}
{data && (
<>
<h2>Total: {data.total}</h2>
<span>Today: {data.today}</span>
</>
)}
</CardWrapper>
);
};
export default TodayVisitorCard;
Let's separate the logic.
import { useEffect, useState } from "react";
import styled from "@emotion/styled";
const CardWrapper = styled.div`
border: 1px solid black;
border-radius: 8px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
padding: 24px;
text-align: center;
width: fit-content;
`;
interface VisitorResponse {
total: number;
today: number;
}
const TodayVisitorCard = () => {
const [data, setData] = useState<VisitorResponse | null>();
const [hasError, setHasError] = useState(false);
const fetchVisitor = async () => {
try {
const res = await fetch("http://localhost:7777/visitor");
if (res.status !== 200) {
throw new Error(`Response status is ${res.status}.`);
}
const data = await res.json();
setData(data);
} catch (e) {
console.error(e);
setHasError(true);
setData(null);
}
};
useEffect(() => {
fetchVisitor();
}, []);
if (data === undefined) return <div>loading...</div>;
return <TodayVisitorCardUI {...data} hasError={hasError} />;
};
const TodayVisitorCardUI = ({
hasError,
total,
today,
}: {
hasError?: boolean;
total?: number;
today?: number;
}) => {
return (
<CardWrapper>
{hasError && <div>Something went wrong!</div>}
{total && <h2>Total: {total}</h2>}
{today && <span>Today: {today}</span>}
</CardWrapper>
);
};
export { TodayVisitorCard };
export default TodayVisitorCardUI;
Now, you can use other business logic easily with the UI, and
you can list up components UIs with storybook
like below.
import { ComponentStory, ComponentMeta } from "@storybook/react";
import TodayVisitorCard from ".";
export default {
title: "\"components/TodayVisitorCard\","
component: TodayVisitorCard,
args: {
hasError: false,
},
argTypes: {
today: {
type: "number",
},
total: {
type: "number",
},
},
} as ComponentMeta<typeof TodayVisitorCard>;
const Template: ComponentStory<typeof TodayVisitorCard> = (args) => (
<TodayVisitorCard {...args} />
);
export const Example = Template.bind({});
Example.args = {
today: 6522,
total: 139,
};
export const Error = Template.bind({});
Error.args = {
hasError: true,
};
Conclusion
In the real world, It would be more complicated. There might be lots of things you should consider about. Structures and Naming Conventions might be one of them. You should make your own strategy that fits your project.
I hope it would be helpful for someone.
Happy Coding!
Posted on September 24, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.