UNPKG

reason-react-brunch

Version:

React bindings for Reason, modified to work with brunch and bucklescript

82 lines (63 loc) 3.04 kB
--- title: Subscriptions Helper --- In a large, heterogeneous app, you might often have legacy or interop data sources that come from outside of the React/ReasonReact tree, or a timer, or some browser event handling. You'd listen and react to these changes by, say, updating the state. For example, Here's what you're probably doing currently, for setting up a timer event: ```reason type state = { timerId: ref(option(Js.Global.intervalId)) }; let component = ReasonReact.reducerComponent("Todo"); let make = _children => { ...component, initialState: () => {timerId: ref(None)}, didMount: self => { self.state.timerId := Some(Js.Global.setInterval(() => Js.log("hello!"), 1000)); }, willUnmount: self => { switch (self.state.timerId^) { | Some(id) => Js.Global.clearInterval(id); | None => () } }, render: ... }; ``` Notice a few things: - This is rather boilerplate-y. - Did you use a `ref(option(foo))` type correctly instead of a mutable field, as indicated by the [Instance Variables section](instance-variables.md)? - Did you remember to free your timer subscription in `willUnmount`? For the last point, go search your codebase and see how many `setInterval` you have compared to the amount of `clearInterval`! We bet the ratio isn't 1 =). Likewise for `addEventListener` vs `removeEventListener`. To solve the above problems and to codify a good practice, ReasonReact provides a helper field in `self`, called `onUnmount`. It asks for a callback of type `unit => unit` in which you free your subscriptions. It'll be called before the component unmounts. Here's the previous example rewritten: ```reason let component = ReasonReact.statelessComponent("Todo"); let make = _children => { ...component, didMount: self => { let intervalId = Js.Global.setInterval(() => Js.log("hello!"), 1000); self.onUnmount(() => Js.Global.clearInterval(intervalId)); }, render: ... }; ``` Now you won't ever forget to clear your timer! ## Design Decisions **Why not just put some logic in the willUnmount `lifecycle`**? Definitely do, whenever you could. But sometimes, folks forget to release their subscriptions inside callbacks: ```reason let make = _children => { ...component, reducer: (action, state) => { switch (action) { | Click => ReasonReact.SideEffects(self => { fetchAsyncData(result => { self.send(Data(result)) }); }) } }, render: ... }; ``` If the component unmounts and _then_ the `fetchAsyncData` calls the callback, it'll accidentally call `self.send`. Using `let cancel = fetchAsyncData(...); self.onUnmount(() => cancel())` is much easier. **Note**: this is an **interop helper**. This isn't meant to be used as a shiny first-class feature for e.g. adding more flux stores into your app (for that purpose, please use our [local reducer](state-actions-reducer.md#actions-reducer)). Every time you use `self.onUnmount`, consider it as a simple, pragmatic and performant way to talk to the existing world.