loading...

Split fat component into smaller flexible components in React

malcolmkee profile image Malcolm Kee ・3 min read

When writing React component, one of the common thing that happen is that the component that you write has became a big fat chunk, causing it harder to read and understand.

When that happens, it is always advised for you to split the big component into few smaller component so it is easier to understand. Besides, the smaller components could be reused elsewhere.

Sometimes, it is quite obvious how to do it by just moving your code into another component. But sometimes it is not so obvious what is the best way to split the code.

For instance, a scenario that can make splitting component more complicated is that your onClick handler for your child component need to know the id of the parent, which is a common requirement when you have some nested data structure.

In those case, currying may help you to split the component in a clean way.

What is currying

Currying is a functional programming technique to transform a function that takes multiple arguments into a sequence of functions.

For instance, a typical add function and usage looks like below:

const add = (x, y) => {
    return x + y;
}
add(2, 3); // 5

Using currying, the add function can be rewrited as below:

const add = x => y => {
    return x + y;
}
add(2)(3); // 5

Now you understand what is currying, let's see how it can help us in splitting component.

Intro to Example

To help you understand how currying may help, assuming you have the data structure below.

data = [
    {
        id: 1,
        name: "Parent 1",
        sublist: [
            {
                id: 11,
                name: "Child 1",

            },
            {
                id: 12,
                name: "Child 2",

            }
        ]
    },
    {
        id: 2,
        name: "Parent 2",
        sublist: [
            {
                id: 21,
                name: "Child 3",

            },
            {
                id: 22,
                name: "Child 24",

            }
        ]
    }
];

And the initial components looks like this: (I know it is not so big in this example, just imagine the data structure is longer and you need to display more data in the component)

const FatComponent = ({ data }) => {
  const updateItem = (parentId, childId) => {
    someFunction(parentId, childId);
  };

  return data.map(parent => (
      <div>
        <span>name: {parent.name}</span>
        <div>
          {parent.sublist.map(item => (
            <div>
              <span>{item.name}</span>
              <button onClick={() => this.updateItem(parent.id, item.id)}>remove</button>
            </div>
          ))}
        </div>
      </div>
    ));
}

Attempt to split the component

We can split the component like below:

const FatComponent = ({ data }) => {
  const updateItem = (parentId, itemId) => {
    someFunction(parentId, childId);
  };

  return data.map(parent => <Parent updateItem={updateItem} {...parent} />);
};

const Parent = ({ id, name, sublist, updateItem }) => (
  <div>
    <span>{name}</span>
    <div>
      {sublist.map(item => <Item updateItem={updateItem} parentId={id} {...item} />)}
    </div>
  </div>
);

const Item = ({ name, id, updateItem, parentId }) => (
  <div>
    <span>{name}</span>
    <button onClick={() => updateItem(parentId, id)}>remove</button>
  </div>
);

However, this solution is not clean because it makes Item become tightly coupled to the parent, as the parent component must pass down updateItem and parentId props to Item component.

Ideally, Item should accept buttonOnClick props and attach it to the button onClick handler, like below:

const Item = ({ name, id, buttonOnClick }) => (
  <div>
    <span>{name}</span>
    <button onClick={buttonOnClick}>remove</button>
  </div>
);

This would makes Item flexible and more likely to be reused for other components.

Solution - Using currying

By using currying, we would able to achieve that:

const FatComponent = ({ data }) => {
  const updateItem = parentId => itemId => () => {
    someFunction(parentId, itemId);
  };

  return data.map(parent => <Parent updateItem={updateItem(parent.id)} {...parent} />);
};

const Parent = ({ name, sublist, updateItem }) => (
  <div>
    <span>{name}</span>
    <div>
      {sublist.map(item => <Item buttonOnClick={updateItem(item.id)} parentId={id} {...item} />)}
    </div>
  </div>
);

const Item = ({ name, id, buttonOnClick }) => (
  <div>
    <span>{name}</span>
    <button onClick={buttonOnClick}>remove</button>
  </div>
);

Clean and sweet!

Final Words

Even though functional programming is not required for you to coding in React, however, learning more of functional programming would help you to write cleaner and better React code.

Happy coding!

Discussion

pic
Editor guide
Collapse
arnemahl profile image
Arne Mæhlum

This is spot on. People who just picked up React commonly make components with lots of jsx, like you would if you were writing the entire html for a page in one file. If you don't split that up into logical compokents you are missing half the point of React. (The other half is how React renders after each state change, makikng you write declarative code.)