Hi again ebo! Thanks for the thoughtful discussion.
I love the rule of 3 -- write everything twice WET, and then try to refactor DRY on the third attempt.
Ignoring all the code smells, I still think this was still the wrong abstraction.
Let's give the benefit of the doubt to the devs and say it worked before the pagination requirement came down.
My issue is that they didn't organize the code along logical lines for state management. That's what broke everything in the end.
It's a React codebase, so it makes sense to refactor purely presentational elements (display components) with minimal business logic.
However, they moved the business logic to the Table and tried to share it between 9 unrelated data types, without documenting.
In React, we usually lift state up to the parent component to share between sibling components, unless we're using a global state store.
They WERE using Mobx as a global state store, but instead of passing the state via Mobx or lifting it to the parent, they refactored to pass refs around.
I'm sure this happened because of the complexities of the data -- they were trying to simplify and check if a row was checked or not, but it was a total fail.
I would rather have had 9 duplicate Tables buried in the parent component that worked than the 1 deeply-integrated Table that didn't.
So I think the objective way to know in advance if something is the wrong abstraction is to start with keeping state in a single place.
For example, the source of truth here was the backend, copied to the Mobx store, copied into the parent component.
Any of those levels should have been tasked with monitoring the user behavior (checked or unchecked table row).
Instead, the focus was on trying to make a generic Table that could handle any ref without knowing the data.
I think that was a hasty abstraction because of how you would write it from scratch, with just the parent component.
The parent would have protoTable and protoChart and the data in state, so we should note "checked or not" in the parent's state or in the global state store.
Again, it may come back to code smells and readability: why did we make a 300 line Table component if it didn't improve readability and its behavior is unclear?
Similarly, if we're expecting side effects from the Table to propagate up and back down in state, why didn't we just use the global state store?
So the "wrong" abstractions are too long / unreadable / undocumented, mix presentational and business logic, and/or complicate state management.