React - How to design components ? === ![](https://i.imgur.com/ZLirEbH.png) ###### 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. ![](https://i.imgur.com/i4pgyR3.png) 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.