0

I have a custom table component and it has two props: items and columns. I want to implement a sorting feature. Sorting is not a big deal. I am sorting the items inside the table component but when items are sorted, also the state that stores the items must be changed which is outside of my table component. I don't want to pass setState method cause of my component is generic. It would be very useless if I pass setState method everywhere.

How do popular libraries solve this problem without need of pass a state-altering method? Do they copy the state to an internal state and then modify it or something? I hope I could explain my problem.

abdllhcay
  • 69
  • 2
  • 6
  • There is a way to do that with `useContext`, but generaly used for authentication kind of scenario. => https://stackoverflow.com/a/51573816/668572 – Sedat Polat Feb 22 '22 at 14:48

1 Answers1

1

It might help to think in terms of controlled vs uncontrolled components. You may be familiar with this from core elements like <input>s, where you can either pass in a defaultValue prop, and let the input handle everything ("uncontrolled"), or you can pass in value and onChange and handle things yourself ("controlled"). You can design your table component as either a controlled component or uncontrolled component too.

Doing it as an uncontrolled component, you might pass in a prop that sets the initial sorting, but afterwards everything is handled by the table. The parent won't get notified and won't update its state:

const Parent = () => {
  const [items, setItems] = useState(/* some array */);

  return <MyTable items={items} defaultSort="asc" />
}

const MyTable = ({ items, defaultSort }) => {
  const [sort, setSort] = useState(defaultSort ?? 'asc');
  const sortedItems = useMemo(() => {
    if (sort === 'asc') {
      return [...items].sort(/* insert sort function here */)
    } else {
      return [...items].sort(/* insert sort function here */)
    }
  }, [items, sort]);

  return (
    <>
      <button onClick={() => setSort(sort === 'asc' ? 'dsc' : 'asc')}>
        Change Sort
      </button>
      {sortedItems.map(() => /* etc */)}
    </>
  )
}

If instead you do a controlled component, then the parent is in charge of the state, and the child just notifies the parent of relevant changes

const Parent = () => {
  const [items, setItems] = useState(/* some array */);
  const [sort, setSort] = useState('asc');
  const sortedItems = useMemo(() => {
    if (sort === 'asc') {
      return [...items].sort(/* insert sort function here */)
    } else {
      return [...items].sort(/* insert sort function here */)
    }
  }, [items, sort]);

  return <MyTable items={sortedItems} onSortToggled={() => setSort(sort === 'asc' ? 'dsc' : 'asc')} />
}

const MyTable = ({ items, onSortToggled}) => {
  return (
    <>
      <button onClick={onSortToggled}>
        Change Sort
      </button>
      {items.map(() => /* etc */)}
    </>
  )
}

If you add in some extra code to check for undefineds, it's possible to make your table support both controlled and uncontrolled modes, based on which set of props it is passed. But it should just be one or the other; you shouldn't try to have both components be managing state simultaneously, as this just adds opportunities for the states to get out of sync and bugs to be introduced.

the state that stores the items must be changed which is outside of my table component

If this is one of your requirements, then you're basically doing the controlled component version, and thus you must accept a function from the parent component which describes how to do so. The parent component is the only one who knows what state they have and how to update it.

Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
  • Thank you for your reply. Now I understand the concept better. Trying to manage state from two different components is really inconvenient. – abdllhcay Feb 22 '22 at 18:06