[EN] Project Development Guide with React — Emre MUTLU

[EN] Project Development Guide with React — Emre MUTLU

The React Handbook

·

15 min read

Hello,
This guide contains rules that must be followed when developing applications with React. This guide aims to ensure that the codes written by different people are compatible with each other and to produce higher quality codes with fewer errors. In other words, it is aimed that people working in a team coherently writing code.
Note: Take everything here as an opinion*, not an absolute. There’s more than one way to build software.*

About the Author
For over two years, I’ve been developing projects with React. I learned the necessary information to enter this sector on the internet. That’s why I want to pay my debt to the community and contribute to the development of the community by sharing the knowledge I have gained on the internet.

“Knowledge grows as it is shared”

CONTENTS:

WHAT TO KNOW BEFORE YOU START READING - Basic Javascript Information
- Why Did I Choose to Use VSCode?
- SOLID Principles

WHAT TO DO - VSCode Settings
- Basic Principles
- Naming
- Formatting

RECOMMENDED
- Form Usage
- VSCode Snippets
- Leave it how you want to find it
- Asking for Help
- Starting with the Simple Things

WHAT NOT TO DO - Avoid actions that will lead to loss of performance
- Avoid the Use of “Magic String”
- Avoid the Use of “Inline CSS”
- Avoid Writing Long Code
- Direct Object Intervention should be avoided
- Incorrect State Use Should Be Avoided
- Do You Need to Use Else?
- Do You Need to Use Array.push()?
- Avoid Props Drilling
- Bonus :)

I WANT TO TAKE MY REACT KNOWLEDGE EVEN FURTHER
Last Word

Photo by Ben White on Unsplash

THINGS TO KNOW BEFORE YOU START READING

There are too many technical terms in the text, so to explain them all, I added a link to the explanation for the subject mentioned in the technical terms. (Underlined phrases are clickable links.)

Basic Javascript Information

Be sure to learn the basic Javascript information described in this article: https://www.freecodecamp.org/news/top-javascript-concepts-to-know-before-learning-react/

Be sure to understand the following statement thoroughly.
“Everything is a component in React.”*
- Well, we use [HTML](developer.mozilla.org/en-US/docs/Learn/Gett..) tags (eg: <div>…</div>).
+ Actually, it is a [React Component](reactjs.org/docs/react-component.html#gatsb..); we are using [JSX](reactjs.org/docs/introducing-jsx.html). We do not use [HTML](freecodecamp.org/news/html-vs-jsx-whats-the..).
For an introduction to JSX, read: [reactjs.org/docs/introducing-jsx.html*](htt..

Note: This guide assumes you are using the functional component. Click to examine the differences between functional components and class components.

Note: This guide assumes you are using VSCode. If you use Webstorm or any other code editor, you can ignore the VSCode-related parts.

Why did I choose to use VSCode?
Because I love VSCode’s code snippets system, shortcuts, the repository of add-ons, and customizability.
You can find detailed information about VSCode at this address: https://code.visualstudio.com/docs/editor/whyvscode#:~:text=With%20support%20for%20hundreds%20of,navigate%20your%20code%20with%20ease.

SOLID Principles

If we apply the SOLID principles to REACT:
S = Single responsibility principle (SRP) => “Every function/module/component should do exactly one thing”

In the example below, user information and displaying the component are done in a component. This is a violation of the Single Responsibility Principle.

const ActiveUsersList = () => {
  const [users, setUsers] = useState([])

  useEffect(() => {
    const loadUsers = async () => {  
      const response = await fetch('/some-api')
      const data = await response.json()
      setUsers(data)
    }
    loadUsers()
  }, [])

  const weekAgo = new Date();
  weekAgo.setDate(weekAgo.getDate() - 7);
  return (
    <ul>
      {users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => 
        <li key={user.id}>
          <img src={user.avatarUrl} />
          <p>{user.fullName}</p>
          <small>{user.role}</small>
        </li>
      )}
    </ul>    
  )
}

Instead of giving more than one responsibility to the component, we should split the duties into two different functions, as shown in the example below.

In the example below, user information access is assigned to the hook named useUser(). The responsibility of displaying the data (showing it in the interface) is given to the component called ActiveUsersList. This way, both functions have only one task.

const useUsers = () => {
  const [users, setUsers] = useState([])

  useEffect(() => {
    const loadUsers = async () => {  
      const response = await fetch('/some-api')
      const data = await response.json()
      setUsers(data)
    }
    loadUsers()
  }, [])

  return { users }
}
const ActiveUsersList = () => {
  const { users } = useUsers()

  const weekAgo = new Date()
  weekAgo.setDate(weekAgo.getDate() - 7)
  return (
    <ul>
      {users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => 
        <li key={user.id}>
          <img src={user.avatarUrl} />
          <p>{user.fullName}</p>
          <small>{user.role}</small>
        </li>
      )}
    </ul>    
  )
}

O = Open-closed principle (OCP) => “Software entities should be open for extension, but closed for modification”

In the example below, a Header component generates a new “Link” for each “pathname.” As the number of “paths” to be added to the application increases, the component will become quite complex. In other words, we must change the Header component for each new path we add.

const Header = () => {
  const { pathname } = useRouter()

  return (
    <header>
      <Logo />
      <Actions>
        {pathname === '/dashboard' && <Link to="/events/new">Create event</Link>}
        {pathname === '/' && <Link to="/dashboard">Go to dashboard</Link>}
      </Actions>
    </header>
  )
}
const HomePage = () => (
  <>
    <Header />
    <OtherHomeStuff />
  </>
)
const DashboardPage = () => (
  <>
    <Header />
    <OtherDashboardStuff />
  </>
)

To prevent this situation, sending the links to the relevant page to the Header component within that page would be an excellent option.

const Header = ({ children }) => (
  <header>
    <Logo />
    <Actions>
      {children}
    </Actions>
  </header>
)
const HomePage = () => (
  <>
    <Header>
      <Link to="/dashboard">Go to dashboard</Link>
    </Header>
    <OtherHomeStuff />
  </>
)
const DashboardPage = () => (
  <>
    <Header>
      <Link to="/events/new">Create event</Link>
    </Header>
    <OtherDashboardStuff />
  </>
)

L = Liskov substitution principle (LSP) => Objects in a program should be interchangeable with instances of their subtypes without changing the correctness of that program. This is a bit more related to classes. But if we try to adapt it to React, we can provide type control of the props passed to the component with PropTypes or TypeScript.

In the example below, both components are waiting for the same interface.

interface CatFact {
  facts: string[];
  color: string;
}

const CatFactA = ({ facts, color }: CatFact) => {
  return (
    <>
      {facts.map((fact, index) => (
        <p style={{ color }}>
          Fact {index}: {fact}
        </p>
      ))}
    </>
  );
};

const CatFactB = ({ facts, color }: CatFact) => {
  return (
    <ul style={{ color }}>
      {facts.map((fact) => (
        <li>{fact}</li>
      ))}
    </ul>
  );
};

export default () => {
  const catFactData: CatFact = {
    facts: [
      "Cats make about 100 different sounds. Dogs make only about 10.",
      "I don't know anything about cats.",
      "Domestic cats spend about 70 percent of the day sleeping and 15 percent of the day grooming."
    ],
    color: "red"
  };
  const abTest = Math.floor(Math.random() * 2) + 1;
  return (
    <div>
      {abTest === 1 ? (
        <CatFactA {...catFactData} />
      ) : (
        <CatFactB {...catFactData} />
      )}
    </div>
  );
};

I \= Interface segregation principle (ISP) => “Clients should not depend upon interfaces that they don’t use.”

In the example below, the component named DisplayUser has retrieved the entire user object via props to access the user’s name. In other words, there may be many expressions in the user object that the component named DisplayUser is not interested in — for example, the user’s hair color, age, etc.

const DisplayUser = (props) => {
  return (
    <div>
      <h1>Hello, {props.user.personalInfo.name}! </h1>
    </div>
  )
};

const App = () => {
  const user = {
    personalInfo: {
      name: "josh",
      age: 23
    },
    physicalFeatures: {
      hairColor: "blone",
      heightInC,: 175
    }
  }
  return (
    <div>
      <DisplayUser user={user} />
    </div>
  )
};

Instead of passing the whole user object from the parent component to the child component named DisplayUser, we should pass only the object that concerns that component.

In the example below, since DisplayUser is only interested in the user’s name, it will be enough to pass it through the props.

const DisplayUser = ({name}) => {
  return (
    <div>
      <h1>Hello, {name}! </h1>
    </div>
  )
};

const App = () => {
  const user = {
    personalInfo: {
      name: "josh",
      age: 23
    },
    physicalFeatures: {
      hairColor: "blone",
      heightInC,: 175
    }
  }
  return (
    <div>
      <DisplayUser name={user.personalInfo.name} />
    </div>
  )
};

D = Dependency inversion principle (DIP) => “One should depend upon abstractions, not concretions”

In the example below, API is called directly inside the LoginForm component. However, this makes the LoginForm component directly dependent on API.

import api from '~/common/api';

const LoginForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = async (evt) => {
    evt.preventDefault()
    await api.login(email, password)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">Log in</button>
    </form>
  )
}

To prevent this situation, we need to create a parent component that can connect the LoginForm component and the API object like glue and enable them to communicate.

type Props = {
  onSubmit: (email: string, password: string) => Promise<void>
}
const LoginForm = ({ onSubmit }: Props) => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const handleSubmit = async (evt) => {
    evt.preventDefault()
    await onSubmit(email, password)
  }
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">Log in</button>
    </form>
  )
}
import api from '~/common/api'
const ConnectedLoginForm = () => {
  const handleSubmit = async (email, password) => {
    await api.login(email, password)
  }
  return (
    <LoginForm onSubmit={handleSubmit} />
  )
}

\ For example, you should not do data fetching in a component. The Component’s job is to render. It doesn’t need to know anything else. For this reason, we should do the data extraction in a different method and only pass the result of this data extraction to the Component.*

(Code examples above:
- https://konstantinlebedev.com/solid-in-react/
-
https://medium.com/docler-engineering/applying-solid-to-react-ca6d1ff926a4
taken
from their addresses).

Photo by Alvan Nee on Unsplash

WHAT TO DO

1. VSCode Settings
First of all, it is mandatory to use the “ESLint,” “Prettier,” and “GitLens” plugins for the VSCode application.
Why is it mandatory?
- ESLint instantly warns you about your mistakes and prevents you from building incorrectly.
- Prettier allows your codes to be formatted within the framework of your specific settings. (For example: Let a maximum of 120 characters be displayed on a line, more on the bottom line. Or end each variable with a semicolon after it is defined.) (We usually use Prettier at the time of saving.)
- GitLens is a tool that allows you to do your Git operations from the VSCode interface. (You can see which line was added by who and with which commit message. In this way, you can better analyze the purpose of the written code.).
* Install the “Emre MUTLU — Javascript — Extension Pack” add-on package if you wish.

Emre MUTLU — Javascript — Extension Pack

The standard “Commit” format must be implemented on git. (For example, you can consider the commit messages system here and expand it by adding new rules for your projects. (Example: https://github.com/emremutlu08/react-best-practices/blob/main/common-commit-format/common-commit-format.md)

2. Basic Principles - The principles of “DRY, KISS, and YAGNI” must be followed.
— DRY = Don’t Repeat Yourself: You should not have duplicated code.
— KISS = Keep It Super Simple (Orj: Keep It Simple, Stupid): Make your code simple
— YAGNI = You Ain’t Gonna Need It: You should not create features that it’s not really necessary.

- It would be best if you put the “Single Responsibility” principle into practice.
- All components must have an id. (This way, it will be easier for us to debug.) These IDs should not conflict with each other.
Therefore, if the React version used in the project is less than 18, use the expression => id = Math.floor(Math.random() * 100 + 1) + ‘-’ + FILE_NAME,
If it’s large, you can use the expression id = useId() + ‘-’ + FILE_NAME.
- Everything has to be a component if it will use more than once.
- All props used in the Component FILE_NAME.propTypes = { … props should be added here } - If mapping on a component array, adding a unique key value to the component in the most inclusive position is mandatory.
- Limit nested folders to 3–4 levels to avoid complexity.

3. Naming

Pay attention to naming conventions.

  • Proper and descriptive nomenclature is mandatory. (For example, instead of typing isModelOneOpen or shortening it to isModOnOp, you should name it isCreateModelOpen long and descriptively.) This way, you don’t have to add comments to explain it.

  • Constant names should be Snake Case (All Caps). (THANKS_FOR_READING)

  • File and folder names should be kebab-case. (thanks-for-reading)

  • Function names should be camelCase. (thanksForReading)

  • Component names should be PascalCase. (ThanksForReading)

  • Custom hook names must start with useSmt (Ex: useWindowSize)

4. Formatting

Photo by Erda Estremera on Unsplash

Form Usage

  • To prevent frequent state changes, it will be helpful to use input fields within the <form></form> tag and to get the information in case of onSubmit. (React applications “render” happens “on every state change”. If this happens too often, it negatively affects application performance.)
    Note: You can also use the useMemo hook to avoid unnecessary rendering in different situations.

NOT RECOMMENDED

RECOMMENDED

Note: If you have more than one button in a <form>…</form>, you must provide a type for every button element.

<form className="flex justify-content-between" onSubmit={() => {}}>
  <InputElement id="Name" name="Name" label={NAME_INPUT_TEXT} required />
  <BackButton type="button" onClick={onBack} /> // This is not invokes onSubmit
  <SendButton type="submit" /> // This invokes onSubmit
</form>

VSCode Snippets

  • Create “VSCode snippets” (code snippets) for frequently used expressions. (This way, you avoid spending too much time on things that must be written repeatedly.)
    (You can use this tool to quickly generate snippets: snippet-generator.app)

We type ‘fa’, and the snipped appears.

Leave it how you want to find it

  • Leave it the way you want to find it: Someone who came before you may have left the code messy, did copy-paste work, in short, left a poor quality code. You should fix the code whenever you find the opportunity so that you and the people who come after you will encounter more understandable codes.

We want to change this.

To this :)

Asking for Help

It is recommended to ask for help: After you have done enough research and are sure that you cannot reach the result on your own, you should ask a teammate for help. Because you can get the answer quickly thanks to them saying a keyword you did not think of at that moment.

Starting with the Simple Things

It is recommended to start with simple tasks: Divide the task into small parts and always make simple tasks your priority. In this way, both “I could not progress in this process!” You get rid of the feeling, and you can tell your manager, “I have completed parts a, b, and c of this process, and only part X remains.”
If you start working from “X” to solve the difficult one, and your manager asks you, “what did you do” three days later, it wouldn’t be nice to say I’m still on “X,” right? :)

Photo by Nik on Unsplash

WHAT NOT TO DO

Avoid actions that will lead to loss of performance

  • Anything that will not be used on the current screen should not be called beforehand. (Especially at the beginning, it is necessary not to call everything and reduce the application’s performance.)

Avoid the Use of “Magic String”

  • It is necessary to avoid using strings that appear suddenly in the file called “Magic String”.
    * For example: (Type item.userTypeId === userTypeEnum.Admin instead of item.userTypeId === 2) should be written so that it is easy to read, and you don’t repeatedly look to see what Admin’s code was. (At the same time, if the Admin’s code changes, you will save yourself the hassle by changing it in one place.)
    * Example-2: (Use <button>{EDIT_BUTTON_TEXT}</button> instead of <button>EDIT</button>). (You can also use i18next, which provides convenient localization operations.)

Avoid the Use of “Inline CSS”

  • It is necessary to avoid using “Inline CSS” and proceed based on “Component” as much as possible. Because when there is a CSS change, changing it one by one from everywhere causes big problems.

Avoid Writing Long Code

  • Line length in a file should never exceed 500 lines. If you have written more than 300 lines of code, you should check what you wrote, divide it into meaningful parts / use it by separating it into methods. You have to import from different files.

Direct Object Intervention should be avoided

  • Do not interfere with an object as directly as possible. You must create a new copy of the object and modify it.

Incorrect State Use Should Be Avoided

  • Do not try to change the state information without using setState.

Do You Need to Use Else?

  • Avoid unnecessary if-else statements when you add the return statement in the if; the following statements do not work. So you don’t need to use else.

Do You Need to Use Array.push()?

  • Instead of array.push(), array restructuring, and appending should be done.
    * […previousArray, { newObjectElement1: newObjectElementValue1 }]

Avoid Props Drilling

  • Avoid Props Drilling. (If your props reach more than two component levels, you are doing “Props Drilling”.)

Bonus :)

  • Please do not send requests in a for loop. :)

I WANT TO CARRY MY REACT KNOWLEDGE FURTHER

MUST READ:
- https://reactjs.org/docs/thinking-in-react.html
-
https://alexkondov.com/tao-of-react/
-
https://www.patterns.dev/
-
Learn the basics of Javascript very well.
- https://reactpatterns.com/

Last Words:

We have come to the end of the guide, where I try to convey my experiences with React. I hope you have gained helpful information for yourself and your team in this article.
If you want to give helpful feedback on this guide, you can reach me on LinkedIn.


I want to thank all my friends who helped me develop this guide with their feedback. :)

I’m thinking of collecting the guide you read in a git repo under “React Best Practices” and sharing it on Github.
If you want to support me in this work and contribute to the development of the React ecosystem, you can give a star to the Github repo.

GitHub Repo: https://github.com/emremutlu08/react-best-practices

You can join my LinkedIn network with +26,000 followers to follow my daily posts about React and JavaScript: https://www.linkedin.com/in/emremutlujs/

Resources and Examples:

Writer:
* Emre MUTLU — Linkedin

Supporters with their Feedback:
* Ahmet Emre BAŞAKÇIOĞLU — Linkedin
* Melike ŞANLI ARSLAN — LinkedinMedium
* Ezgi BIÇAK — LinkedinMedium
* Ercan AKALAR — Linkedin

Translators:
* Emre MUTLU — Linkedin
* Seha ÖZBEK — Linkedin