New Component

New component

Location and structure

The files and folder structure follows the tightly-coupled architecture (opens in a new tab) and underlies the Next.js file-system router (opens in a new tab). That means that a file and code should live as close as possible to its context. In the image below you can see we've a UserAdd component (used by pages/users/add/index.tsx) that is located under src/components/users/add. It does have other components it uses. Because they're specific to it, they live in the components folder and follow the same architecture. We don't place components beside the page (inside the pages folder) because they're treated as routes then.

But to maintain a clean structure, we construct the same folder structure that fits to the structure of the page.

New Component Structure

Details

General

  • the component is located in:
    • the components folder
    • a folder that is named after the page folder it belongs to i.e. components/users/UserForm.tsx
  • the file is named after the component and written in PascalCase

Specific

  • use functional components instead of class components
// ❌
class NewComponent extends PureComponent { ... }
 
// ✅
function NewComponent(...) { ... }
Why?

Functional components are easier to read and write. They're also more performant than class components. Additionally, we can make use of React Hooks.


  • import types with import type { ... } from ... synta x
// ❌
import { NewType } from ...
 
// ✅
import type { NewType } from ...

Import Cost Valid

Why?

It helps reducing the bundle size of your JavaScript code. The smaller the bundle size, the faster the page loads.


  • use function declaration syntax instead of arrow function syntax.
// ❌
const NewComponent = (...) => { ... }
 
// ✅
function NewComponent(...) { ... }
Why?

It's easier to read and identify a piece of code as function.


  • export the functional component as default export (export default MyComponent) and at the bottom of the fil e
// ❌
export function NewComponent(...) { ... }
 
// ✅
function NewComponent(...) { ... }
...
export default function NewComponent
Why?

Next.js needs a React component to be exported via export default.


  • type the component's props with a TypeScript type and destructure them where possible.
// ❌
function NewComponent(cmpProps: { username: string; age: number; }) { ... }
 
// ❌
function NewComponent({ username, age }: { username: string; age: number; }) { ... }
 
// ✅
type Props = {
  username: string
  age: number
}
 
// NewComponent.tsx
function NewComponent({ username, age }: Props) { ... }
Why?

Reduces the amount of code and increases the readability.


  • specify the return type of a component.
// ❌
function NewComponent(...) { ... }
 
// ✅
function NewComponent(...): JSX.Element { ... }
Why?

By specifying the concret return type, TypeScript will give you an error if you accidentally return a wrong type. Additionally, it increases the consistency of always specifying the return type of functions throughout the codebase and provides a better readability if you can directly recognize what a function returns.


  • declare types specific to a file inside the file at the top (like type Props)
Why?

  • logic that belongs together should live as close together as possible and they should be separated through a blank line.
// ❌
function NewComponent(...): JSX.Element {
  const [toggleColorTheme, setToggleColorTheme] = useState('light')
  const { t } = useTranslation()
  function onToggleColorTheme(): void {
    ...
    setToggleColorTheme('dark')
    ...
  }
}
 
// ✅
function NewComponent(...): JSX.Element {
  const { t } = useTranslation()
 
  const [toggleColorTheme, setToggleColorTheme] = useState('light')
 
  function onToggleColorTheme(): void {
    ...
    setToggleColorTheme('dark')
    ...
  }
}

  • Separate JSX elements with a blank line if they have the same indentation.
// ❌
function NewComponent(...): JSX.Element {
  return (
    <div>
      <div>
        <h2>Foo</h2>
      </div>
      <div>
        <h3>Bar</h3>
      </div>
    </div>
  )
}
 
// ✅
function NewComponent(...): JSX.Element {
  return (
    <div>
      <div>
        <h2>Foo</h2>
      </div>
 
      <div>
        <h3>Bar</h3>
      </div>
    </div>
  )
}

  • all texts are replaced with a corresponding i18n variable
// ❌
<h3>Login</h3>
 
// ✅
<h3>{t('login.title')}</h3>
Why?

To prevent hard coded strings inside the code and have only one source-of-truth for all your translations.


  • avoid any where possible
// ❌
function NewComponent(...): JSX.Element {
  const { t } = useTranslation()
 
  const [user, setUser] = useState<any>({ name: '', email: '' })
}
 
// ✅
type User = {
  name: string
  email: string
}
 
function NewComponent(...): JSX.Element {
  const { t } = useTranslation()
 
  const [user, setUser] = useState<User>({ name: '', email: '' })
}
Why?

By telling TypeScript that a variable or function is of type any you disable TypeScript for it and don't get any type-safety anymore. Some typing is hard, we all know that, but at least ask for help and don't go the simple and insecure way.


  • treat server requests as sensible
Why?

Double-check if you make unnecessary or too many server requests caused by a suboptimal implementation. It could led to a bad performance and UX.


  • provide feedback for every write, delete, update operation
Why?

A good UX consists of proper user feedback i.e. notifications. That gives the user confidence that his action was successful or has failed.


  • use proper code formatting via Prettier & linting via ESLint
Why?

Tools like Prettier (semantic) and ESLint (syntax) ensure a consistent codebase and keep readability on a high level.


  • check console (browser and terminal) for any errors or warnings
Why?

Even though your implementation is fine, a warning or even an error can occur. Sometimes they're not reflected in the UI but in the console.

Example

// NewComponent.tsx
import { User } from 'your-types-package'
 
type Props = {
  onSave: () => void
}
 
export function NewComponent({onSave}: Props): JSX.Element {
  const { t } = useTranslation()
 
  const [user, setUser] = useState<User>({ name: '', email: '' })
 
 
  const [toggleColorTheme, setToggleColorTheme] = useState('light')
 
  function onToggleColorTheme(): void {
    ...
    setToggleColorTheme('dark')
    ...
    onSave()
  }
 
    return (
    <div>
      <div>
        <h2>{t('newComponent.title')}</h2>
      </div>
 
      <div>
        <h3>{name}</h3>
      </div>
    </div>
  )
}