ESLint is a tool that helps you keep your code clean and readable. It can be configured to enforce a set of rules that will help you write better code. In this article, I will show you how with a single rule of ESLint your code will be much cleaner and easier to read.
The problem
Let's say we have a React component that renders a TO-DO list. The component has a few buttons and a few states. It also has a few methods that are used to update the state. The component looks something like this:
import React, { useState } from 'react'; const TODO_STATUS = { PENDING: 'pending', DONE: 'done', }; export const TodoList = () => { const [inputValue, setInputValue] = useState(''); const [todos, setTodos] = useState([]); const addTodo = () => { setTodos([ ...todos, { id: Date.now(), title: inputValue, status: TODO_STATUS.PENDING }, ]); setInputValue(''); }; const removeTodo = (todo) => { setTodos(todos.filter((t) => t.id !== todo.id)); }; const markAsDone = (todo) => { setTodos( todos.map((t) => { if (t.id === todo.id) { return { ...t, status: TODO_STATUS.DONE }; } return t; }), ); }; const markAsPending = (todo) => { setTodos( todos.map((t) => { if (t.id === todo.id) { return { ...t, status: TODO_STATUS.PENDING }; } return t; }), ); }; return ( <div> <h1>TODO List</h1> <div> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> <button onClick={addTodo}>Add</button> </div> <ul> {todos.map((todo) => ( <li key={todo.id}> <span>{todo.title}</span> <button onClick={() => removeTodo(todo)}>Remove</button> {todo.status === TODO_STATUS.PENDING ? ( <button onClick={() => markAsDone(todo)}>Mark as done</button> ) : ( <button onClick={() => markAsPending(todo)}> Mark as pending </button> )} </li> ))} </ul> </div> ); };
This component is not very complex, but it is not very easy to read either. The main problem is that it has a lot of methods that are used to update the state. These methods are not very long, but they are long enough to make the component hard to read. If we want to make this component easier to read, we need to refactor it.
The solution
Using the rule max-lines-per-function
of ESLint, we can limit the number of lines of code in a function:
{ "rules": { "max-lines-per-function": [ "warn", { "max": 70, "skipBlankLines": true, "skipComments": true } ] } }
This rule will warn us if a function has more than 70 lines of code and will remind us to refactor it before it gets too long. I also added the options skipBlankLines
and skipComments
to ignore blank lines and comments, which are not relevant to the length of the function and can help us to make the code more readable.
Live example
A possible refactoring of the component could be splitting the component into smaller components and moving the methods to a custom hook:
import React, { useCallback, useState } from 'react'; export const TodoList = () => { const { todos, addTodo, removeTodo, markAsPending, markAsDone } = useTodoList(); return ( <div> <h1>TODO List</h1> <TodoForm onSubmit={addTodo} /> <ul> {todos.map((todo) => ( <TodoItem key={todo.id} todo={todo} onRemove={() => removeTodo(todo)} onDone={() => markAsDone(todo)} onPending={() => markAsPending(todo, TODO_STATUS.PENDING)} /> ))} </ul> </div> ); }; const TodoForm = ({ onSubmit }) => { const [inputValue, setInputValue] = useState(''); const handleSubmit = (e) => { e.preventDefault(); onSubmit(inputValue); setInputValue(''); }; return ( <form onSubmit={handleSubmit}> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> <button type="submit">Add</button> </form> ); }; const TodoItem = ({ todo, onRemove, onDone, onPending }) => ( <li> <span>{todo.title}</span> <button onClick={onRemove}>Remove</button> {todo.status === TODO_STATUS.PENDING ? ( <button onClick={onDone}>Mark as done</button> ) : ( <button onClick={onPending}>Mark as pending</button> )} </li> ); const TODO_STATUS = { PENDING: 'pending', DONE: 'done', }; const useTodoList = () => { // ... };
Although this file is slightly longer than the previous one, it is much easier to read, and it can be easily refactored into smaller files. This modular approach make sure everyone new to the project can easily understand the code and start contributing without having to spend hours trying to understand what is going on (and yes, that includes you in 6 months!).
My own experience
I have been using this rule for a while now, and I can say that it has the single most important rule that contributed to the readability of my code. I have been using it in all my projects, and I have never regretted it. I highly recommend you to try it out and see for yourself.
If you're interested in all the rules I use in my projects, you can check out my @neuledge/eslint-config
and @neuledge/eslint-config-next
packages. They are botn extended all the recommended rules from ESLint and Unicorn and provide good guidelines for better coding practices.
Simply get started by running:
npm install --save-dev @neuledge/eslint-config
and add the following to your .eslintrc.json
:
{ "extends": ["@neuledge"] }