What the Heck is Inversion of Control?
I heard the term used in a Twitter Space recently and was clueless. Turns out it's a well-known programming concept that I found use for immediately.
Photo by Noah Buscher on Unsplash
Recently, I was listening in on a Twitter Space while building a React component. This egghead.io space was focused on learning and teaching React, and some very experienced folks like Kent C. Dodds were there to spread their wealth of knowledge about the framework.
At one point Kent mentioned inversion of control, something I had never heard of. I barely even made out what he was saying, so I began Googling what I thought it sounded like. Luckily I was close enough, and came across his post that covers the concept in detail.
If I understand it correctly (and if I don't, please tell me ๐ญ), inversion of control is a programming concept that allows us to abstract away logic from a component so that it fits more use cases, "inverting control" of that logic.
Writing reusable components is a valuable skill in React, and I happened to be running into a problem in that moment that inversion of control might solve.
The reusable table component โป๏ธ
The component I was building happened to be pretty complex, and I wanted to make it fit for multiple modules in the project I was working on. The component makes use of a table package called React-Table, which is a hooks-based table library that comes with all the functionality you'd want in a table and no actual UI elements.
Sidenote: This was Very Exciting for me because sometimes I loathe pulling pre-defined styles into a project only to re-style them to fit the UI.
My problem arose while trying to wrap some React Router anchor <Link>
tags around <td>
elements:
<td {...cell.getCellProps()}>
{
<Link to={`/employees/edit/${row.values.id}`}>
// render the cell contents
cell.render('Cell')
</Link>
}
</td>
React Router provides us a great way to handle the dynamic route, but how do we make the link itself dynamic? We need to be able to swap out the entire string if we want to reuse this table. We also don't know for sure that the interpolated JS is always going to be rows.value.id
, so that needs to be dynamic too!
Let's try "inverting control" ๐น๏ธ
As I read in the aforementioned write-up by Kent C. Dodds, we can invert control by passing a function down as a prop from the parent component. Note that this isn't a one-to-one comparison of the example he used in the article, but I believe the concept is useful in my case as well.
The function prop allows us to pass the row data into logic that's going to be defined in the parent.
const ReusableTable = ({ linkFn }) => {
return (
// the beginning of the table...
{rows.map(row => (
<td {...cell.getCellProps()}>
{
<Link to={linkFn(row)}>
// render the cell contents
cell.render('Cell')
</Link>
}
</td>
))}
// the rest of the table...
)
}
Now in the parent, an anonymous function is passed to our ReusableTable as linkFn
, just like we'd see for an onClick handler, for example. The function will take one argument, which is going to be the row we're passing in. All we'll have it do is return the string interpolation after gaining access to the row data within the table.
<ReusableTable linkFn={passedRow => (
`/employees/edit/${passedRow.values.id}`
)} />
Now when we render this table in other modules, we'll be able to provide whatever path necessary with whatever interpolated JS we need:
// employees module
<ReusableTable linkFn={passedRow => (
`/employees/edit/${passedRow.values.id}`
)} />
// projects module
<ReusableTable linkFn={passedRow => (
`/projects/${passedRow.values.project_id}`
)} />
// customers module
<ReusableTable linkFn={passedRow => (
`/customers/edit/${passedRow.values.customer_id}`
)} />
I've literally just scratched the surface of this concept, and am excited to become more familiar with it especially in practice. The above is an extremely simple example, sure, but I'm convinced the concept is going to be appearing again and again as I dig deeper into this project and into React.