1

React documentation seems to be very insistent on the idea that in almost every situation, deriving state from props is a bad idea, an anti-pattern, verbose, likely to cause bugs, hard to understand, and all-around probably going to place a curse on one's lineage for a thousand years.

My use case isn't that weird, so I'm probably doing something wrong, but the suggested patterns for not needing getDerivedStateFromProps() (i.e. making Your object fully controlled or fully uncontrolled) don't seem like good solutions.

The situation: I have a Table component, that takes in an array rows as a prop. Table is used in many different places in my app. I want the Table to be able to handle row-sorting. It is obviously a bad idea to to make whichever parent component controls Table to have to control the sorting*, so fully controlled is out. Making Table fully uncontrolled with a key, also seems like it doesn't make a lot of sense unless the key is the row-data itself-- but my understanding is that key is meant to be simple data (like an id), and actually having to compare all of the rows, which are typically fairly complicated objects, would be pretty inefficient**. Using memoize-one is also not an option as I am working in a closed system and can't import any new libraries.

My current solution: Table has a state variable sortedRows which is updated either whenever sort() is called or whenever props.rows is updated (via getDerivedStateFromProps), by:

  1. Making a shallow copy of props.rows,
  2. in-place sorting that copy and
  3. updating state.sortedRows on that value.

As I see it, there is still only one source of truth here (which is from props), and the state is always just storing a sorted version of that truth (but always dependent on and in sync with it).

Is this solution bad? If so why? What would be a better way to implement this?

Thanks!

Note: I didn't include my code because I am massively simplifying the situation in this prompt-- in reality Table element already exists, and is pretty complicated.

Note 2: I going to ask if I'd run into issues once I want to be able to modify elements in the tables, but I think I'm actually ok, since Table doesn't manage its elements, just arrange and display them, and the buttons for adding and removing elements from a table are not contained within Table, so all that processing is happening at the level of the parent's logic as passed down as part of props.rows

*Having something like <Table rows={sort(rowsFromParent)}/>every time I call Table is repetitive and error-prone, and since clicking on a table header determines sorting column, we'd actually have to have the parent element passing down an onClick() function in every case, which quickly and unnecessarily ramps up complexity).

**There is also a secondary problem to do with rebuilding an element. The table has an infinite scroll, such that when You reach a certain element more rows are loaded in. Using key will destroy the Table component and create a new one, scrolling the user to the top of the new table (which could potentially have many thousands of rows). Something also feels wrong about having to set key in each use of Table, even though resetting based on changes to props.rows seems like it should be intrinsic to how Table works, rather than something that has to be configured each time.

Edit: I have React 15.4, which is before getDerivedStateFromProps was added and using a later version is not an option, so I guess even if I happened to find a valid use case for getDerivedStateFromProps, an alternative would be nice...

David Lalo
  • 155
  • 7
  • Maybe I'm misunderstanding what you're trying to accomplish, but this (sorting table rows *during* a render) runs counter to the point of React. The *defining* characteristic of React is this: your views are *pure functions* of your app state. The *contents* of your rows (not the JSX elements but the data) should be stored in an array in state in order and if that order changes React will reconstruct the table using the logic you give it. Trying to sort during a single render cycle is the kind of thing you only do when you've exhausted every alternative. – Jared Smith Jun 23 '22 at 17:18
  • This is a fair point. I'm not sorting *during* the render, only whenever sorting parameters change (say sorting by a different column, or the table is suddenly populated with new data), but it is still happening within the react element, yes. Probably right that this is not an ideal way of doing things, but it's the code base I've been given to work with... – David Lalo Jun 23 '22 at 17:33
  • But going off of Your point regarding table should receive a presorted array -- where in my react app would that sorting happen if not some other react component? Besides, there is a certain kind of encapsulation I expect from my table: I just feed it data, and it knows how to do things like sort by column without having to resort to using a global store or without defining pass-down update functions into it – David Lalo Jun 23 '22 at 17:36
  • 1
    Not trying to argue, want to understand how to actually implement Your advice – David Lalo Jun 23 '22 at 17:36
  • 1
    Again, object orientation is the **wrong way** to think about using React. The correct mental model is that React is a *function* (a *pure* function at that) `data => view`. Data goes in, DOM pops out. The table does not 'own' the data. If you want to encapsulate the data the UI is not the place to do it: that would be an undesirable coupling between the data and the UI. Frequently you will want to render the same data multiple ways (e.g. one tabbed view with table rows and another with a graph) so it's counter-productive to have the data managed by a UI widget. – Jared Smith Jun 23 '22 at 18:23
  • Sorting is a property of the *data*, **not** the *view*. The table can have interactive controls that control the sorting of the data but the sorting happens at the level of the render tree that owns the data, not the level that shows it. Pass down handler callbacks or if that's too onerous use the context API. – Jared Smith Jun 23 '22 at 18:25
  • I completely agree with everything in the first comment -- I guess the part I was missing was the perspective that sorting is a property of the data rather than the view. As I was seeing it, precisely because, for example, there might be different views of the same data, (say 2 Tables with the same data but sorted differently), having the sorting as a property of the data seems strange, if not impossible (we would need to duplicate the data, which would lead to possible conflicting sources of truth). – David Lalo Jun 23 '22 at 19:12
  • As far as handler callbacks -- I think that might work well for my use case (context API/global store causing lack of clarity was exactly the issue I'm trying to fix). I'm not sure I follow completely, though. Where in my app should the handler callbacks be handling the data sorting if not within a react component? – David Lalo Jun 23 '22 at 19:15

0 Answers0