# State Container Libraries Our team has favored small, lightweight, modularized state containers for managing our React state vs. heavier frameworks like Redux. The current options we utilize are: 1. Class-based React higher order components. 2. [Recompose](https://github.com/acdlite/recompose) `withStateHandlers` composed with `mapProps`. 3. [React State Patterns](https://github.com/procore/react-state-patterns) # Pros/Cons Of Each ### React Class HOC #### Pros: + No need for external library dependency + Lots of control and can access React lifecycle methods. + Can use React hooks #### Cons: + Lots of boilerplate code + Do not scale in terms of code management ### Recompose #### Pros: + Heavily used by many projects + Lots of utility functions. + Highly battle tested and known to be stable + Very clean interface and much less boilerplate code + Great support docs and API #### Cons: + [No longer maintained](https://github.com/acdlite/recompose#a-note-from-the-author-acdlite-oct-25-2018) + Difficult to use with React hooks + State handlers cannot call eachother unless using `mapProps`. ### React State Patterns #### Pros: + Lightweight single responsibility library + Clean interface and much less boilerplate code. (Has syntax that is extremely similar to Recompose `withStateHandlers`) -> See [here](https://github.com/procore/react-state-patterns#using-statehook-util). + Very easy to use with react hooks. + Convenience util to organize state/handlers into `store = { state, handlers }` pattern without need for recompose's `mapProps`. See: [stateHook](https://github.com/procore/react-state-patterns/blob/master/API.md#statehook). + Easy to make updates to since this is a Procore repo. + Very easy to create instances of multiple provider patterns -- not just the decorator pattern. See [here](https://github.com/procore/react-state-patterns/blob/master/API.md#statePatterns) #### Cons: + Not heavily used, has not been battle tested. + May be unknown issues that have not been found? + Support docs and API are not as detailed and well-formed as recompose's. + Can only use with React versions that support hooks: `^16.8.0` + Not an industry standard and so the technology skills learned using it may not transfer to other companies. + Ugly npm package name 😅 + State handlers cannot call eachother when using [stateHook](https://github.com/procore/react-state-patterns/blob/master/API.md#statehook) -- would need to use the [Directly From Hook](https://github.com/procore/react-state-patterns#directly-from-hook) pattern. # Examples Useages The following are examples of a simple Counter app using the three outlined approaches. Each state container will wrap the component: ```jsx const Displayer = ({ counter: { state, handlers } }) => ( <> <div>{state.count}</div> <button onClick={() => handlers.decrementBy(1)}>Decrement</button> <button onClick={() => handlers.incrementBy(1)}>Increment</button> </> ); ``` ### React Class HOC ```jsx const Container = (WrappedComponent) => class extends React.Component { constructor(props) { super(props); this.state = { count: props.initialValue || 0 }; } incrementBy(value) { this.setState({ count: this.state.count + value, }); } decrementBy(value) { this.setState({ count: this.state.count - value, }); } render() { return ( <WrappedComponent counter={{ handlers={ incrementBy, decrementBy }, state={ ...this.state, } }} {...this.props} /> ); } }; const App = Container(Displayer); // <App initialValue={5} /> ``` ### Recompose ```jsx import { withStateHandlers, mapProps, compose } from "recompose"; const Container = withStateHandlers( (props) => ({ count: props.initialValue || 0 }), { incrementBy: ({ count }) => (value) => ({ count: count + value }), decrementBy: ({ count }) => (value) => ({ count: count - value }), } ); const App = compose( Container, mapProps( ({ incrementBy, decrementBy, ...props }) => ({ counter: { handlers: { incrementBy, decrementBy }, state: { count: props.count }, }, ...props, }) ) )(Displayer); // <App initialValue={5} /> ``` ### React State Patterns ```jsx import statePatterns, { stateHook } from "@procore/react-state-patterns"; const Container = statePatterns(props => stateHook( { count: props.initialValue || 0 }, state => ({ incrementBy: value => ({ ...state, count: state.count + value }), decrementBy: value => ({ ...state, count: state.count - value }) }), "counter" )(props) ); const App = Container.withState(Displayer); // <App initialValue={5} /> ``` ### State Containers Needing Updated The following state containers do not follow the modularized state container pattern (i.e.) ```jsx fooStore = { state, handlers }; ``` _Task: Update these to use `mapProps` to achieve the pattern above._ 1. https://github.com/procore/dev-portal/blob/master/app/frontend/react/common/marketplace_form/StateContainer.js 2. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/common/ProductionManifestsTable/StateContainer.jsx~~ 3. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/common/SandboxManifestsTable/StateContainer.jsx~~ 4. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/developer_apps/show/DeleteAppCard/StateContainer.jsx~~ 5. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/developer_apps/show/StateContainer.jsx~~ 6. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/pages/app_analytics/index/Container.jsx~~ 7. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/top-nav/TopNavReference/StateContainer.jsx~~ 8. https://github.com/procore/procore/blob/master/hydra_clients/company_admin_settings/src/views/AppManagement/AppShow/containers/AppShowContainer.jsx 9. https://github.com/procore/procore/blob/master/hydra_clients/company_admin_settings/src/views/AppManagement/AppsIndex/AppsIndexContainer.jsx 10. https://github.com/procore/procore/blob/master/hydra_clients/company_admin_settings/src/views/AppManagement/AppsIndex/components/AutoConnectModal/StateContainer.jsx The following do not follow the pattern *and* are the old React Class HOC Pattern _Task: Update these to use the library we decide to use, and ensure they follow the `fooStore = { state, handlers };` pattern._ 1. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/admin/developer_apps/index/StateContainer.jsx~~ 2. ~~https://github.com/procore/dev-portal/blob/master/app/frontend/react/admin/developer_apps/show/StateContainer.jsx~~ ~~4. https://github.com/procore/dev-portal/blob/master/app/frontend/react/admin/notifications/form/FormStateContainer.jsx~~ ~~5. https://github.com/procore/dev-portal/blob/master/app/frontend/react/admin/notifications/index/StateContainer.jsx~~ ~~6. https://github.com/procore/dev-portal/blob/master/app/frontend/react/containers/NotificationReaderStateContainer.jsx~~ ~~7. https://github.com/procore/dev-portal/blob/master/app/frontend/react/containers/NotificationsStateContainer.jsx~~