DEV Community

MINYEONG KIM
MINYEONG KIM

Posted on

Designing React Components for Reusability and Efficiency

This is about how I thought of ways to make taking care of all project on me easier.

When creating components, we usually encounter two distinct types:

Reusable components are built to be resuable across multiple parts of an application.

Non-Reusable components are tailored for a particular use case.

Now, let's learn how to build these components and spot some common slip-ups.

Reusable components

Here's a simple button component:

const Button = ({ color, text, onClick, size }) => {
  const styles = {
    backgroundColor: color || 'white',
    padding: size === 'large' ? '16px 32px' : '8px 16px',
    fontSize: size === 'large' ? '18px' : '14px',
  };

  return (
    <button
      style={styles}
      onClick={onClick}
    >
      {text}
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

You can use this component into your project right away.

it is important to design reusable components so that anyone can use them even if they are distributed as open source.

But sometimes we make them non-reusable like this:

const Button = ({ color, text, size }) => {
  const dispatch = useDispatch();
  const isLoggedIn = useSelector(state => state.user.isLoggedIn);

  const styles = {
    backgroundColor: color || 'white',
    padding: size === 'large' ? '16px 32px' : '8px 16px',
    fontSize: size === 'large' ? '18px' : '14px',
  };


  return (
    <button
      style={styles}
      onClick={handleClick}
      disabled={!isLoggedIn}
    >
      {text}
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

In this component, you can see it relies on Redux. This means we can't easily use this button in a different project. It's not reusable anymore.

Non-Reusable components

Often, we need to create non-reusable components.

I'm taking a closer look at a common pattern many of us follow Here's to-do list.

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo } from '@/actions'; 

const TodoList = () => {
  const [task, setTask] = useState('');
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = () => {
    if (task) {
      dispatch(addTodo(task));
      setTask('');
    }
  };

  const handleToggleTodo = (index) => {
    dispatch(toggleTodo(index));
  };

  return (
    <div>
      <h1>Todo List</h1>
      <input
        type="text"
        value={task}
        onChange={(e) => setTask(e.target.value)}
      />
      <button onClick={handleAddTodo}>Add</button>
      <ul>
        {todos.map((todo, index) => (
          <li
            key={index}
            onClick={() => handleToggleTodo(index)}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;
Enter fullscreen mode Exit fullscreen mode

This code isn't too long, so you might think it's pretty easy to work with. But if you've ever come across a really long component, you know it can be tough to figure out what's happening.

To prevent complexity, you might want to consider using a custom hook.

Here's how the to-do list looks with a custom hook:

import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo } from './actions';

const useTodos = () => {
  const [task, setTask] = useState('');
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = () => {
    if (task) {
      dispatch(addTodo(task));
      setTask('');
    }
  };

  const handleToggleTodo = index => {
    dispatch(toggleTodo(index));
  };

  return {
    task,
    todos,
    setTask,
    handleAddTodo,
    handleToggleTodo
  };
};

export default useTodos;
Enter fullscreen mode Exit fullscreen mode

You might think that a custom hook can be hard to grasp, but the idea is straightforward: if you want to manage and controll state or side effects, you can organize them all within a custom hook.

And we can make to-do list component like below.

import React from 'react';
import useTodos from './useTodos';

const TodoList = () => {
  const {
    task,
    todos,
    setTask,
    handleAddTodo,
    handleToggleTodo
  } = useTodos();

  return (
    <div>
      <h1>Todo List</h1>
      <input
        type="text"
        value={task}
        onChange={(e) => setTask(e.target.value)}
      />
      <button onClick={handleAddTodo}>Add</button>
      <ul>
        {todos.map((todo, index) => (
          <li
            key={index}
            onClick={() => handleToggleTodo(index)}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

Enter fullscreen mode Exit fullscreen mode

Now, in the to-do list component, there's nothing about state management.
If you need to make changes related to state, you would only need to edit the custom hook file. This approach makes maintenance easier.

Top comments (1)

Collapse
 
sergio-gn profile image
Sergio Garcia Neto

This post is really helpful. When the projects starts to get bigger and complex, reusing components and differenciating the non-reusing ones is essencial. Thank you for the insight!