!! Disclaimer : no value judgement !! !! Disclaimer : I'm not an expert !!
Components are the building block of a React app. There have been historically many ways of defining a component. But there are two main approach :
- class component
- "stateless functional components", or more simple functional component (the "stateless" part is going away soon)
-
class :
- can have state, props, lifecycle methods, custom methods
- easily handles change, interaction, data fetching
- internal state is mutable
- tend to be bigger (more boilerplate code)
- harder to reuse in different contexts (especially when doing data fetching)
- Better performance in the case of PureComponent (at AirBnb's scale, and in 2017, though https://medium.com/airbnb-engineering/recent-web-performance-fixes-on-airbnb-listing-pages-6cd8d93df6f4)
-
functional :
- has props
- pure function (the same input always return the same result)
- props in => markup out. Ideal for design elements.
- tend to be smaller
- extremely easy to compose (higher order components, render props) and unit-test
- almost 100% classes
- big components with complex behaviours baked in, a lot to read, not the most easy to navigate (back and forth between multiple render methods and state management)
- Aside : we use Redux. The ability to connect component to a central immutable state, encourages the use of functional components (because you don't need internal state if you can pass it as props)
- In a perfect world the only internal state we would need would be UI state (typically show/hide), all data state handled by Redux
- Objective : write less code, make less mistakes, make PRs easier to read and review, make your code more reusable / maintainable.
- Added bonus : thinking a bit more about the why before the how
- Before starting to build something new / extend something old :
- does it need data from an external source (side effect) ?
- no : functional
- yes :
- can I pass it from a parent component via props? (lifting up the state https://reactjs.org/docs/lifting-state-up.html)
- no : class (fetching data through static load or in componentDidMount)
- yes : functional
- can I pass it from a parent component via props? (lifting up the state https://reactjs.org/docs/lifting-state-up.html)
- will it change? (i.e. does it need lifecycle methods / event handlers)
- no : functional
- yes : class
- does it need data from an external source (side effect) ?
-
initial situation :
- Introduction of TetheredList remove the need for state management in SubMenu
- Becomes therefore a pure UI component (data in, markup out)
- Markup generated through a chain of renderXXX methods : renderMenuItems -> renderMenuItem -> renderModalLink / renderExportButton / renderNavLink -> markup
-
working with it / coming in with fresh eyes :
- verbose (class syntax)
- hard to navigate (bouncing between class methods)
- unclear where the main render is happening
- unclear what is rendered when (vague-ish typings)
-
objective :
- breaking off the render methods of Submenu.tsx as their own components (they are basically already FCs)
- refactor Submenu.tsx itself as an FC rendering SubmenuItem FCs based on the props it is passed.
- clarify the flow : Submenu(SubmenuItems[]) => Submenu UI
-
result :
- no more state management in a component that is not directly concerned with it (TetheredList has it)
- smaller units of information
- clearer responsability for each of these units
- clearer typing (precisely type each kind of SubmenuItem)
- hopefully easier to add new types of Submenu items
- FC allows us to have clearer repsonsablilities for each of our components
- Separating concerns rule of thumb:
- business / data logic : state -> class
- presentation logic : props -> function
- In case you don't know this article : https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 !! read cautiously as loose guideline (not strict rules), also a little dated !!
- Coming soon : hooks. Arguably no reason to use classes any more: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889. Additive! No need to refactor everything (or anything). The objective is to have only one way of defining components, and functions are it for the React team. No need to panic though, strong backwards compatibility philosophy, classes will not be removed.