React - How to design components ?
===

###### tags: `React`
## Before
Login and register form are the core components for pretty much every website, and I usually just code directly without destructing to smaller components.
The folder strucutre would be like this, I would create `LoginPage.tsx` and `SignupPage.tsx` and put everything inside this two components, I have to admit that this was not the best and efficient way and I was ended up repeating myself.
```jsonld
├── src
│ ├── components
│ │ ├── Application.tsx
│ ├── pages
├── LoginPage.tsx
├── SignupPage.tsx
```
```typescript
// LoginPage.tsx
export const LoginPage = () => {
return (
<form>
<div>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
placeholder="Enter your email"
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
placeholder="Enter your password"
/>
</div>
)
}
```
## After trying to restructure components - 1
Look at the code above, first of all, I had to insert values one by one and did the same for `Register.tsx`, but it woild be easy for me to style because I could just create one css for styling forms.
But hey, I am using React, it supposed to be dynamic and be able to build by using smaller bricks, but what should I proceed when it comes to design components for my application?
Below was my initial plan, I literally break an entire form into four pieces.
- `FormHeader.tsx`: Customizable header which can pass different values.
- `FormInput.tsx`: In order to write less code, we can deine values brforehand and pass as props into this child component,
- `ForRememberMe.tsx`: This is a simple component which contains its own state when user clicks the checkbox.
- `FormAction.tsx`: For all button actions, we can reuse this component.

The folder structure would be like this.
```
|--src
|-- components
| |-- authForm
| |-- authTypes.ts
| |-- FormHeader.tsx
| |-- FormInput.tsx
| |-- FormRememberMe.tsx
| |-- FormAction.tsx
|-- pages
|-- LoginPage.tsx
|-- SignupPage.tsx
```
First thing first, let's define the values that will be passing as props.
```typescript
// authTypes.ts
export interface IAuthForm {
labelText: string;
labelFor: string;
id: string;
name: string;
type: string;
autoComplete: string;
isRequired: boolean;
placeHolder: string;
}
export interface IHEADER {
heading: string;
paragraph: string;
linkTitle: string;
linkUrl: string;
}
export interface IFormAction {
handleSubmit: () => void;
type: string;
text: string;
name: string;
}
export const LOGIN: IAuthForm[] = [
{
labelText: 'Email',
labelFor: 'email-address',
id: 'email-address',
name: 'email',
type: 'email',
autoComplete: 'email',
isRequired: true,
placeHolder: 'Email Address',
},
{
labelText: 'Password',
labelFor: 'password',
id: 'password',
name: 'password',
type: 'password',
autoComplete: 'password',
isRequired: true,
placeHolder: 'Password',
},
];
export const SIGNUP: IAuthForm[] = [
{
labelText: 'User Name',
labelFor: 'user-name',
id: 'user-name',
name: 'user-name',
type: 'text',
autoComplete: 'user',
isRequired: true,
placeHolder: 'User Name',
},
{
labelText: 'Email',
labelFor: 'email-address',
id: 'email-address',
name: 'email',
type: 'email',
autoComplete: 'email',
isRequired: true,
placeHolder: 'Email Address',
},
{
labelText: 'Password',
labelFor: 'password',
id: 'password',
name: 'password',
type: 'password',
autoComplete: 'password',
isRequired: true,
placeHolder: 'Password',
},
];
export const LOGINHEADER: IHEADER = {
heading: 'Login your account',
paragraph: "Don't have an account yet ? ",
linkTitle: 'Signup here',
linkUrl: '/signup',
};
export const SIGNUPHEADER: IHEADER = {
heading: 'Sign up Here',
paragraph: 'Already a member ? ',
linkTitle: 'Login here',
linkUrl: '/login',
};
```
Inside `SignupPage.tsx`, I had to import my predefine values and passed as props. For `input`, since it's an array that contained 3 objects, I could map through them.
```typescript=
// SignupPage.tsx
export const SignupPage = () => {
const id = useId();
const handleSubmit = (e: React.FormEvent<HTMLInputElement>): void => {
e.preventDefault();
console.log('hello');
};
return (
<FormWrapper>
<div>
<FormHeader
heading={SIGNUPHEADER.heading}
paragraph={SIGNUPHEADER.paragraph}
linkTitle={SIGNUPHEADER.linkTitle}
linkUrl={SIGNUPHEADER.linkUrl}
/>
<form>
{SIGNUP.map((item) => (
<FormInput
key={id}
labelText={item.labelText}
labelFor={item.labelFor}
id={item.id}
name={item.name}
type={item.type}
autoComplete={item.autoComplete}
isRequired={item.isRequired}
placeHolder={item.placeHolder}
/>
))}
</form>
<FormRememberMe />
<FormAction
handleSubmit={() => handleSubmit}
type="button"
text="Sign up"
name="Sign up"
/>
</div>
</FormWrapper>
);
};
```
```typescript
// LoginPage.tsx
export const SignupPage = () => {
const id = useId();
const handleSubmit = (e: React.FormEvent<HTMLInputElement>): void => {
e.preventDefault();
console.log('hello');
};
return (
<FormWrapper>
<div>
<FormHeader
heading={SIGNUPHEADER.heading}
paragraph={SIGNUPHEADER.paragraph}
linkTitle={SIGNUPHEADER.linkTitle}
linkUrl={SIGNUPHEADER.linkUrl}
/>
<form>
{SIGNUP.map((item) => (
<FormInput
key={id}
labelText={item.labelText}
labelFor={item.labelFor}
id={item.id}
name={item.name}
type={item.type}
autoComplete={item.autoComplete}
isRequired={item.isRequired}
placeHolder={item.placeHolder}
/>
))}
</form>
<FormRememberMe />
<FormAction
handleSubmit={() => handleSubmit}
type="button"
text="Sign up"
name="Sign up"
/>
</div>
</FormWrapper>
);
};
```
```typescript
// FormHeader.tsx
export const FormHeader = ({
heading,
paragraph,
linkTitle,
linkUrl,
}: IHEADER) => {
return (
<div>
<h2>{heading}</h2>
<div>
<p>{paragraph}</p>
<span>
<Link to={linkUrl}>{linkTitle}</Link>
</span>
</div>
</div>
);
};
```
```typescript
// FormInput.tsx
export const FormInput = ({
labelText,
labelFor,
id,
name,
type,
isRequired,
placeHolder,
autoComplete,
}: IAuthForm) => {
return (
<div>
<label htmlFor={labelFor}>{labelText}</label>
<input
id={id}
name={name}
type={type}
required={isRequired}
placeholder={placeHolder}
autoComplete={autoComplete}
/>
</div>
);
};
```
```typescript
// FormRememberMe.tsx
export const FormRememberMe = () => {
return (
<div>
<div>
<input type="checkbox" name="rememberMe" id="rememberMe" />
<label htmlFor="rememberMe">Remember Me</label>
</div>
<div>
<a href="#">Forgot Password?</a>
</div>
</div>
);
};
```
This approach did help me to create resuable components, I can use some of them somewhere else, but I found out it's not event efficient when to implement styles, because I was using styled-components, I had to look up every file and styled it, eventhough I still could create one stylesheet and write down styles I need, but it would need me to jump one to another file,perhaps it's not maintainable and readable as well.
For this approach, I think it would be better to use CSS framework such like TailwindCSS or BootStrap, I can implement as inline style.
## After trying to restructure components - 2
Sice I wasn't fancy the first approach, I thought maybe I should really think which component has more chance to be reused, in this case, perhaps `FormInput.tsx` has a better chance, we could have another form for contact or some sort of survey, so I've decided to just keep `FormInput.tsx`.
In this case, the folder structure would be like this.
```
|--src
|-- components
| |-- authForm
| |-- authTypes.ts
| |-- FormInput.tsx
|
|-- pages
|-- LoginPage.tsx
|-- SignupPage.tsx
```
Here's the reframe one, I could implement styles only between `LoginPage.tsx` and `FormInput.tsx`.
```typescript
// LoginPage.tsx
export const LoginPage = () => {
const handleSubmit = (e: React.FormEvent<HTMLInputElement>): void => {
e.preventDefault();
console.log('hello');
};
return (
<FormWrapper>
<FormInsideContainer>
{/* Header */}
<FormHeader>
<h2>{LOGINHEADER.heading}</h2>
<span>
{LOGINHEADER.paragraph}
<Link to={LOGINHEADER.linkUrl}>{LOGINHEADER.linkTitle}</Link>
</span>
</FormHeader>
{/* form */}
<InputContainer>
{LOGIN.map((item) => (
<FormInput
labelText={item.labelText}
labelFor={item.labelFor}
id={item.id}
name={item.name}
type={item.type}
autoComplete={item.autoComplete}
isRequired={item.isRequired}
placeHolder={item.placeHolder}
/>
))}
</InputContainer>
{/* Remember Me */}
<RememberMeContainer>
<div>
<input type="checkbox" name="rememberMe" id="rememberMe" />
<label htmlFor="rememberMe">Remember Me</label>
</div>
<div>
<a href="#">Forgot Password?</a>
</div>
</RememberMeContainer>
{/* Login Btn */}
<ButtonContainer type="submit" name="signup">
Login
</ButtonContainer>
<BackToHome>
<Link to="/">
<FiArrowLeft />
Back to home page
</Link>
</BackToHome>
</FormInsideContainer>
</FormWrapper>
);
};
```
## After trying to restructure components - 3
As a container component, it might be a bit overwhelming, and not readable due to syntaxes were too long.
I was thinking maybe I could amend again, this time, the folder structure would be just like approach 1.
```
|--src
|-- components
| |-- authForm
| |-- authTypes.ts
| |-- FormHeader.tsx
| |-- FormInput.tsx
| |-- FormRememberMe.tsx
| |-- FormAction.tsx
|-- pages
|-- LoginPage.tsx
|-- SignupPage.tsx
```
Code would shift like this.
```typescript
// LoginPage.tsx
export const LoginPage = () => {
return (
<FormWrapper>
<FormInsideContainer>
<FormHeader />
<FormInput />
<FormRememberMe />
<FormAction
btnText={BUTTONLOGIN.btnText}
name={BUTTONLOGIN.name}
title={BUTTONLOGIN.title}
/>
<BackToHome>
<Link to="/">
<FiArrowLeft />
Back to home page
</Link>
</BackToHome>
</FormInsideContainer>
</FormWrapper>
);
};
```
Looked like it's cleaner than before, but since I made child components from uncontrolled to controlled, meaning they weren't flexible anymore.
> Reference:
> [React Dev-Controlled & uncontrolled components](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components)
Before this implementation, I didn't have to worry about the path, I imported all predefined values of `LOGIN` and `SINGUP` and passed them inside `FormInput.tsx` which was a simple component, it accept whatever values I passed, so when I tried to switch from login to signup, it would show the exact values.
With this implementation, since I made `FormInput.tsx` as a controlled component, that means I needed to add extra logic to identify the path and showed different values accordingly.
```typescript=
// FormInput.tsx
export const FormInput = () => {
const location = useLocation();
const path = location.pathname;
return (
<>
{path === '/login' && (
<InputContainer>
{LOGIN.map((item) => (
<div key={item.id}>
<label htmlFor={item.labelFor}>{item.labelText}</label>
<input
required={item.isRequired}
name={item.name}
type={item.type}
id={item.id}
/>
</div>
))}
</InputContainer>
)}
{path === '/signup' && (
<InputContainer>
{SIGNUP.map((item) => (
<div key={item.id}>
<label htmlFor={item.labelFor}>{item.labelText}</label>
<input
required={item.isRequired}
name={item.name}
type={item.type}
id={item.id}
/>
</div>
))}
</InputContainer>
)}
</>
);
};
```
## Conclusion
There're other solutions I probably did not think of, but I think approach 1 seems to be a better choice as I want to make it as readable and reusable as possible eventhough it might be troublesome to jump between files for styling.