{"id":326,"date":"2020-03-09T23:07:59","date_gmt":"2020-03-09T23:07:59","guid":{"rendered":"http:\/\/rainforestqa.com\/2020-03-09-decompose-recompose-how-to-remove-recompose-and-replace-with-hooks\/"},"modified":"2023-02-15T00:40:57","modified_gmt":"2023-02-15T00:40:57","slug":"2020-03-09-decompose-recompose-how-to-remove-recompose-and-replace-with-hooks","status":"publish","type":"post","link":"https:\/\/www.rainforestqa.com\/blog\/2020-03-09-decompose-recompose-how-to-remove-recompose-and-replace-with-hooks","title":{"rendered":"How to remove Recompose and replace with Hooks"},"content":{"rendered":"\n<p>In our <a href=\"https:\/\/www.rainforestqa.com\/blog\/2020-03-09-replacing-recompose-with-react-hooks\/\">last post<\/a>, we explored the pros and cons of Recompose and why we decided to remove it from our codebase. This post includes the strategy we used to approach the large task of implementing that refactor. It\u2019s important to note that this strategy was created to fit our specific situation and is not a one size fits all approach to removing Recompose. Specifically, it was intended to work with our large codebase that is modified by our devs daily. It was very important to not break things(obviously\u2026 but this is magnified by the fact that we are a QA solution) and to minimize code conflicts and disruptions to other engineers. It\u2019s important to create your own implementation strategy, but hopefully ours will serve as a useful guideline.<\/p>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_83 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.rainforestqa.com\/blog\/2020-03-09-decompose-recompose-how-to-remove-recompose-and-replace-with-hooks\/#Implementation_Strategy\" >Implementation Strategy<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.rainforestqa.com\/blog\/2020-03-09-decompose-recompose-how-to-remove-recompose-and-replace-with-hooks\/#Guidelines_for_refactoring_a_component\" >Guidelines for refactoring a component<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.rainforestqa.com\/blog\/2020-03-09-decompose-recompose-how-to-remove-recompose-and-replace-with-hooks\/#Conclusion_and_Learnings\" >Conclusion and Learnings<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Implementation_Strategy\"><\/span>Implementation Strategy<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>First, identify all files that use Recompose. This is as simple as grepping your code base for \u201crecompose\u201d. We took this list of files and turned it into a checklist. We used this checklist to avoid any conflicts because other devs could be touching these files. We then went one-by-one and removed Recompose from a single file (on rare occasions, it needed to be removed from multiple due to them being tightly coupled). This produced small pull requests that only touched one (or a few) files, meaning bite-sized code reviews.<\/p>\n\n\n\n<p>At any given time, there was only one dev working on refactors. When they completed the refactoring of a component, it was checked off the list.<\/p>\n\n\n\n<p>Tackle simple refactors first (low-hanging fruit). Taking the simple refactors out first gets the momentum going and allows for more flexibility and experimentation in how exactly you want to refactor. It\u2019s also nice to get some experience under your belt before taking on the complicated ones.<\/p>\n\n\n\n<p>Next, attack highly-reused components. These have the longest reaching effect, as they are used by various other components and contribute the most to the \u201cDOMbloat\u201d and potentially to bloated snapshots.<\/p>\n\n\n\n<p>Now for the meat of the implementation \u2013 actually refactoring components!<\/p>\n\n\n\n<p>First, you need to decide the end goal of your refactor. The biggest question we debated was whether to refactor everything into class components or into hooks. Perhaps there is a middle ground? Referring back to the section on \u201cwhat went wrong\u201d in the last post, you\u2019ll recall that our major downfall was going overboard with Recompose. Just because you <em>can<\/em> use Recompose doesn\u2019t mean you <em>should<\/em>. The same argument could be made for hooks \u2013 should I <em>always<\/em> use hooks and just do away with class components?<\/p>\n\n\n\n<p>Ultimately, that is a question you must decide for yourself. We\u2019ve decided to lean towards hooks with a healthy dose of skepticism after being burned by our overuse of Recompose. I won\u2019t go into a deep dive on this, but the TLDR is to read the <a href=\"https:\/\/reactjs.org\/docs\/hooks-faq.html\" target=\"_blank\" rel=\"noopener\">Hooks FAQ<\/a> and decide what\u2019s best for you team. Our main takeaway was that hooks are the future of React, so it\u2019s safe to get onboard with them.<\/p>\n\n\n\n<p>For the sake of this article, we\u2019ll be refactoring everything into hooks and avoiding class components completely.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Guidelines_for_refactoring_a_component\"><\/span>Guidelines for refactoring a component<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The examples below address specific HOC =&gt; translations. In reality, your components will likely have multiple HOCs composed together. It depends on the complexity of these HOCs, but in general you\u2019ll want to start with the \u201clowest\u201dcomponent and work your way up.<\/p>\n\n\n\n<p>For example, consider:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const Foo = () => {\n return &lt;div>...&lt;\/div>;\n};\n\nconst WrappedComp = compose(\n hocA,\n hocB,\n hocC,\n hocD,\n hocE\n)(Foo);<\/code><\/pre>\n\n\n\n<p>Props propagate from hocA down to hocE. This means we can convert hocE while leaving the others in place:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const Foo = () => {\n \/\/ imaginary hook\/function to replace hocE's logic\n return &lt;div>...&lt;\/div>;\n};\n\nconst WrappedComp = compose(\n hocA,\n hocB,\n hocC,\n hocD\n)(Foo);<\/code><\/pre>\n\n\n\n<p>This allows you to incrementally refactor away the HOCs without breaking things. For example you can refactor a single HOC, run your specs to ensure no breakages, and then move onto the next. If you have highly complex HOCs like we did, you will likely run into some cases where this is unfortunately not possible. But it stands as a good rule of thumb.<\/p>\n\n\n\n<p>There are some direct HOC =&gt; hook translations that are easy to implement:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>withState =&gt; useState<\/code><\/li>\n\n\n\n<li><code>withHandlers =&gt; useCallback<\/code>&nbsp;(or a regular function)<\/li>\n\n\n\n<li><code>withStateHandlers =&gt; useState + useCallback<\/code><\/li>\n\n\n\n<li><code>withReducer =&gt; useReducer<\/code><\/li>\n\n\n\n<li><code>withContext =&gt; useContext<\/code><\/li>\n\n\n\n<li><code>withPropsOnChange =&gt; useMemo<\/code><\/li>\n\n\n\n<li>lifecycle methods<\/li>\n\n\n\n<li><code>componentDidMount =&gt; useEffect<\/code><\/li>\n\n\n\n<li><code>componentDidUpdate =&gt; useEffect<\/code><\/li>\n\n\n\n<li><code>componentWillUnmount =&gt; useEffect<\/code><\/li>\n<\/ul>\n\n\n\n<p>Other HOCs do not have direct replacements, so we\u2019ll touch of some of those later.<\/p>\n\n\n\n<p>Let\u2019s look at some examples of HOCs that are easily replaced with hooks.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">withState<\/h4>\n\n\n\n<p>withState makes it simple to add state with an updater function. useState has a very similar signature, as both accept an optional initial state and return the current state + a handler to update the state.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Recompose\nconst _ToggleButton = ({ isOpen, setIsOpen }) => (\n &lt;div>...&lt;\/div>\n);\n\nconst ToggleButton = withState('isOpen', 'setIsOpen', false)(_ToggleButton);\n\n\/\/ Hooks\nconst ToggleButton = () => {\n const &#91;isOpen, setIsOpen] = useState(false);\n return &lt;div>...&lt;\/div>>;\n};<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">withHandlers<\/h4>\n\n\n\n<p>withHandlers is easily replaced by useCallback, or simply with a regular function. Using useCallback can be unnecessary, a premature optimization, or could possibly even perform poorly compared to a regular function. If referential equality is important, then useCallback is a great idea. If not, you\u2019re probably better off with a regular function. You can find a more thorough explantation in <a href=\"https:\/\/kentcdodds.com\/blog\/usememo-and-usecallback\" target=\"_blank\" rel=\"noopener\">this blog post<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Recompose\nconst _Button = ({ onClick }) => &lt;button onClick={onClick}>Click Me&lt;\/div>;\nconst Button = withHandlers({\n onClick: () => () => {\n   \/\/ do stuff\n }\n})(_Button);\n\n\n\/\/ Hooks\nconst Button = () => {\n const onClick = useCallback(() => {\n   \/\/ do stuff\n });\n return &lt;button onClick={onClick}>Click Me&lt;\/div>;\n}\n\n\n\/\/ No Hook\nconst Button = () => {\n const onClick = () => {\n   \/\/ do stuff\n };\n return &lt;button onClick={onClick}>Click Me&lt;\/div>;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">withStateHandlers<\/h4>\n\n\n\n<p>withStateHandlers is replaced by a combination of useState and useCallback. In this case, we could probably just use a regular function \u2013 but we\u2019ll use the hook.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Recompose\nconst _Input = ({ value, onChange }) =>\n &lt;input type=\"text\" value={value} onChange={onChange} \/>;\n\nconst Input = withStateHandlers(\n { value: '' },\n onChange: ({ value }) => () => ({ value });\n)(_Input);\n\n\n\/\/ Hooks\nconst Input = () => {\n const &#91;value, setValue] = useState('');\n const onChange = useCallback(({ value }) => {\n   setValue(value);\n });\n return &lt;input type=\"text\" value={value} onChange={onChange} \/>;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">componentDidMount, componentDidUpdate, and componentWillUnmount<\/h4>\n\n\n\n<p>These lifecycle methods are easily replaced with useEffect. Notice that in the componentDidUpdate example, the omission of the dependency array causes the function to be invoked on every render.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ componentDidMount\nuseEffect(() => {\n \/\/ this code executes on mount only\n}, &#91;]);\n\n\n\/\/ componentWillUnmount\nuseEffect(() => {\n \/\/ this code executes on mount only\n return () => {\n   \/\/ this code executes on unmount only\n }\n}, &#91;]);\n\n\n\/\/ componentDidUpdate\nuseEffect(() => {\n \/\/ this code executes on EVERY render\n return () => {\n   \/\/ this code executes on unmount only\n }\n})<\/code><\/pre>\n\n\n\n<p><strong>Important note!<\/strong> These are definitely contrived examples. In practice, <a href=\"https:\/\/overreacted.io\/a-complete-guide-to-useeffect\/#dont-lie-to-react-about-dependencies\" target=\"_blank\" rel=\"noopener\">you won\u2019t want to declare an empty array of dependencies<\/a>. Thinking in terms of synchronization with effects is also subtly different from the mount\/update\/unmount mental model. I highly recommend reading Dan Abramov\u2019s post (linked above) for a deep-dive into useEffect.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">componentWillReceiveProps<\/h4>\n\n\n\n<p>Replacing componentWillReceiveProps is a bit of a pain \u2013 there\u2019s no hook equivalent, and comparing incoming props to previous props requires a bit of a workaround.<\/p>\n\n\n\n<p>In order to access previous props, we can leverage the useRef hook. A great example of this is the <a href=\"https:\/\/usehooks.com\/usePrevious\/\" target=\"_blank\" rel=\"noopener\">usePrevious<\/a> hook. We\u2019ll use it to replace componentWillReceiveProps<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const Foo = ({ number }) => {\n const prevNumber = usePrevious(number);\n useEffect(() => {\n   if (number !== prevNumber) {\n     \/\/ do something\n   }\n }, &#91;number]);\n return &lt;div>...&lt;\/div>\n}<\/code><\/pre>\n\n\n\n<p>Hopefully we\u2019ll see usePrevious (or something that allows us to access previous props) as a native React hook in near future!<\/p>\n\n\n\n<p>Some Recompose HOCs cannot be replaced by a single hook, but we can certainly still replace them with vanilla JS functions, React components, and\/or custom hooks. Examples include mapProps, withProps, branch, and pretty much every other HOC.<\/p>\n\n\n\n<p>It also turns out that the need for some of these HOCs is caused by the existence of other HOCs. We found that removing most of the HOCs meant that we no longer needed most instances of mapProps and withProps, which are largely used to manipulate props before they are passed down to their children.<\/p>\n\n\n\n<p>Popular libraries are shipping their own hooks, such as <a href=\"https:\/\/react-redux.js.org\/next\/api\/hooks\" target=\"_blank\" rel=\"noopener\">React Redux<\/a> and <a href=\"https:\/\/github.com\/ReactTraining\/react-router\/blob\/master\/packages\/react-router\/docs\/api\/hooks.md\" target=\"_blank\" rel=\"noopener\">React-Router<\/a>. While it\u2019s not <em>necessary<\/em> to remove all of your HOCs, we found ourselves quickly moving away from them and replacing all HOCs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Conclusion_and_Learnings\"><\/span>Conclusion and Learnings<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Have a strategy<\/h3>\n\n\n\n<p>It\u2019s important to have a strategy for removing a library like Recompose before you jump in and start refactoring code. Break the refactor into manageable chunks. Depending on the architecture of your codebase, it\u2019s very possible that there will be side-effects that you did not anticipate if you try to bite off more than you can chew.<\/p>\n\n\n\n<p>Removing Recompose from most components is pretty straightforward. Complications arise from custom HOCs that contain complicated logic and\/or are tightly coupled to other HOCs. Consider refactoring other HOCs into hooks, including replacing third party HOCs like connect (if your react-redux version makes useSelector and useDispatch available), but keep in mind that it is not completely necessary.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ensure you understand hooks<\/h3>\n\n\n\n<p>Consider the subtle differences between thinking in terms of the \u201clifecycle\u201d mental model and thinking in terms of synchronization with effects.<\/p>\n\n\n\n<p>There are not a ton of best practices established around hooks yet, but <a href=\"https:\/\/overreacted.io\/\" target=\"_blank\" rel=\"noopener\">Dan Abramov\u2019s blog<\/a> has a ton of great insight and I highly recommend digging into it before attempting to refactor into hooks.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In our last post, we explored the pros and cons of Recompose and why we decided to remove it from our codebase. This post includes the strategy we used to approach the large task of implementing that refactor.<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","inline_featured_image":false,"footnotes":""},"categories":[6],"tags":[],"class_list":["post-326","post","type-post","status-publish","format-standard","hentry","category-engineering"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts\/326","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/comments?post=326"}],"version-history":[{"count":2,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts\/326\/revisions"}],"predecessor-version":[{"id":1067,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/posts\/326\/revisions\/1067"}],"wp:attachment":[{"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/media?parent=326"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/categories?post=326"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rainforestqa.com\/blog\/wp-json\/wp\/v2\/tags?post=326"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}