Often we can end up with complex logic in components because the shape of the data determines how we write our component code. Sometimes we are not in control of that data at all.
If we can simplify that data before it reaches the component, our component code can become simpler and therefore easier to maintain. With the side effect of making our data easier to test and understand.
I like to call this data massaging 💆
Without data massaging
Say we had data about cars that need to be displayed in a table
That does not seem too complex but it's not very future proof, every time you add a new column this component needs to change, and the unit tests for that component need to be updated. If in the future we wanted to allow users to configure what columns were shown, then we would have to add conditions for when each td is shown and make sure the corresponding th is too.
This is were data massaging can make our perfect world data structure a reality. After you get the data from its source, the data massaging function parses it before passing it to the display component.
The data gets transformed as it flows:
Data Source (API) ➡️ Data Massager (generateTableFromCars) ➡️ Display Component (InfoTable)
functionApp(){const[cars, setCars]=useState([])useEffect(()=>{// Disclaimer: Not the real fetch APIfetch(/* ... */).then(({ data })=>{setCars(data)})})const tableData =generateTableFromCars(cars)return<InfoTable tableData={tableData}/>}
The table component then becomes simpler, more generic, and easier to unit test. Easier to test because you are not having to write unit tests for every unique data property.
As for the feature where users can select columns they want to be displayed, the data massager (generateTableFromCars) function can handle filtering to only the columns needed before it gets to the component.
The component code no longer has to change when there are changes to the data it needs to display.