- read

4 React Tips to Instantly Improve Your Code

Pavel Pogosov 38

Solid knowledge of React is one of the most valuable skills for a frontend developer. Many companies are constantly searching for React developers and want to pay them more and more. That’s why it’s important to constantly improve as a developer, as it’s a highly rewarding endeavor.

To help you in your journey, I’d like to share four tips that have been helpful to me in writing better React code. I hope you’ll find something new and useful. So let’s get right into it!

Table of contents

Return functions from handlers

If you are familiar with functional programming, you know what I’m talking about. We can easily call it “currying” in some way. In essence, we set some function parameters in advance.

We have an explicit problem with the boilerplate code below. That technique will help us!

export default function App() {
const [user, setUser] = useState({
name: "",
surname: "",
address: ""
});

// First handler
const handleNameChange = (e) => {
setUser((prev) => ({
...prev,
name: e.target.value
}));
};

// Second handler!
const handleSurnameChange = (e) => {
setUser((prev) => ({
...prev,
surname: e.target.value
}));
};

// Third handler!!!
const handleAddressChange = (e) => {
setUser((prev) => ({
...prev,
address: e.target.value
}));
};

// What if we need one more input? Should we create another handler for it?

return (
<>
<input value={user.name} onChange={handleNameChange} />
<input value={user.surname} onChange={handleSurnameChange} />
<input value={user.address} onChange={handleAddressChange} />
</>
);
}

Solution

export default function App() {
const [user, setUser] = useState({
name: "",
surname: "",
address: ""
});

const handleInputChange = (field) => {
return (e) => {
setUser((prev) => ({
...prev,
[field]: e.target.value
}));
};
};

return (
<>
<input value={user.name} onChange={handleInputChange("name")} />
<input value={user.surname} onChange={handleInputChange("surname")} />
<input value={user.address} onChange={handleInputChange("address")} />

{JSON.stringify(user)}
</>
);
}

Separate responsibilities

Making a “God” component is a common mistake that developers make. It’s called “God” because it contains many lines of code that are hard to understand and maintain. I strongly recommend dividing components into sets of independent sub-modules.

A typical structure for this would be:

  • UI module, responsible for visual representation only.
  • Logic/Model module, containing only business logic. An example of this is a custom hook.
  • Lib module, containing all required utilities for the component.

Here’s a small demo example to help illustrate this concept.

export function ListComponent() {
// Our local state
const [list, setList] = useState([]);

// Handler to load data from the server
const fetchList = async () => {
try {
const resp = await fetch("https://www.url.com/list");
const data = await resp.json();

setList(data);
} catch {
showAlert({ text: "Something went wrong!" });
}
};

// We want to fetch only on mount
useEffect(() => {
fetchList();
}, []);

// Handler responsible for deleting items
const handleDeleteItem = (id) => {
return () => {
try {
fetch(`https://www.url.com/list/${id}`, {
method: "DELETE"
});
setList((prev) => prev.filter((x) => x.id !== id));
} catch {
showAlert({ text: "Something went wrong!" });
}
};
};

// Here we just render our data items
return (
<div className="list-component">
{list.map(({ id, name }) => (
<div key={id} className="list-component__item>">
{/* We want to trim long name with ellipsis */}
{name.slice(0, 30) + (name.length > 30 ? "..." : "")}

<div onClick={handleDeleteItem(id)} className="list-component__icon">
<DeleteIcon />
</div>
</div>
))}
</div>
);
}

We should start by writing our utils that will be used in model and UI modules.

export async function getList(onSuccess) {
try {
const resp = await fetch("https://www.url.com/list");
const data = await resp.json();

onSuccess(data)
} catch {
showAlert({ text: "Something went wrong!" });
}
}

export async function deleteListItem(id, onSuccess) {
try {
fetch(`https://www.url.com/list/${id}`, {
method: "DELETE"
});
onSuccess()
} catch {
showAlert({ text: "Something went wrong!" });
}
}

export function trimName(name) {
return name.slice(0, 30) + (name.lenght > 30 ? '...' : '')
}

Now we need to implement our business logic. It simply will be a custom hook returning things we need.

export function useList() {
const [list, setList] = useState([]);

const handleDeleteItem = useCallback((id) => {
return () => {
deleteListItem(id, () => {
setList((prev) => prev.filter((x) => x.id !== id));
})
};
}, []);

useEffect(() => {
getList(setList);
}, []);

return useMemo(
() => ({
list,
handleDeleteItem
}),
[list, handleDeleteItem]
);
}

The final step is to write our UI module and then combine it all together.

export function ListComponentItem({ name, onDelete }) {
return (
<div className="list-component__item>">
{trimName(name)}

<div onClick={onDelete} className="list-component__icon">
<DeleteIcon />
</div>
</div>
);
}

export function ListComponent() {
const { list, handleDeleteItem } = useList();

return (
<div className="list-component">
{list.map(({ id, name }) => (
<ListComponentItem
key={id}
name={name}
onDelete={handleDeleteItem(id)}
/>
))}
</div>
);
}

Use objects map instead of conditions

If you need to display various elements depending on the variable, you can implement that tip. Using this easy strategy makes components more declarative and simplifies code understanding. Moreover, it makes it more painless to extend functionality further.

Problem

🌟 Don’t forget to subscribe to learn vital things about Frontend!

function Account({type}) {
let Component = UsualAccount

if (type === 'vip') {
Component = VipAccount
}

if (type === 'moderator') {
Component = ModeratorAccount
}

if (type === 'admin') {
Component = AdminAccount
}

return (
<div className='account'>
<Component />
<AccountStatistics />
</div>
)
}

Solution

const ACCOUNTS_MAP = {
'vip': VipAccount,
'usual': UsualAccount,
'admin': AdminAccount,
'moderator': ModeratorAccount,
}

function Account({type}) {
const Component = ACCOUNTS_MAP[type]

return (
<div className='account'>
<Component />
<AccountStatistics />
</div>
)
}

Put independent variables outside of React lifecycle

The idea is to separate logic that doesn’t require the React component lifecycle methods from the component itself. This improves code clarity by making dependencies more explicit. Therefore, it becomes much easier to read and understand components.

Problem

function useItemsList() {
const defaultItems = [1, 2, 3, 4, 5]
const [items, setItems] = useState(defaultItems)

const toggleArrayItem = (arr, val) => {
return arr.includes(val) ? arr.filter(el => el !== val) : [...arr, val];
}

const handleToggleItem = (num) => {
return () => {
setItems(toggleArrayItem(items, num))
}
}

return {
items,
handleToggleItem,
}
}

Solution

const DEFAULT_ITEMS = [
1, 2, 3, 4, 5
]

const toggleArrayItem = (arr, val) => {
return arr.includes(val) ? arr.filter(el => el !== val) : [...arr, val];
}

function useItemsList() {
const [items, setItems] = useState(DEFAULT_ITEMS)

const handleToggleItem = (num) => {
return () => {
setItems(toggleArrayItem(items, num))
}
}

return {
items,
handleToggleItem,
}
}

Thanks for reading!

I hope, you found this article useful. If you have any questions or suggestions, please leave comments. Your feedback helps me to become better.

Don’t forget to subscribe⭐️

More content at PlainEnglish.io. Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.

Interested in scaling your software startup? Check out Circuit.