jayphelps / react-observable-subscribe

<Subscribe> component to automatically consume observables declaratively in React JSX
MIT License
133 stars 9 forks source link

React 16+ changes #10

Open simonbuchan opened 6 years ago

simonbuchan commented 6 years ago

React 16.0 relaxed render() return types to anything renderable (which prop-types and @types/react call a "node"), so you should return the observer output as-is, and not wrap in <span/> (which can break layouts). See https://reactjs.org/blog/2017/09/26/react-v16.0.html

React 16.3 has deprecated componentWill* methods (except for componentWillUnmount), and will warn if they are used. See https://reactjs.org/blog/2018/03/29/react-v-16-3.html. In my version of this, I simply used the Did versions instead, which seems to be mostly fine, but in the new async rendering mode, these might run some time after render, which could be a problem for synchronous observable outputs. Further, WillUpdate may run multiple times before an update, though it's not actually described why. The create-subscription package the react blog mentions here might be a relevant reference (perhaps this library would work as a wrapper on it?)

typeofweb commented 6 years ago

@simonbuchan You might find useful the react-with-observable package I created: https://github.com/mmiszy/react-with-observable It allows you to use Observables declaratively in React. It's compatible with React 16.3+ and it uses create-subscription under the hood. It includes TypeScript definitions and has 100% integration tests coverage.

simonbuchan commented 6 years ago

That actually looks very good, barring minor quibbles like the extra wrapping component / defaulting to '' (rather than setting the initial state to null)!

I ended up building a component from scratch (though with an API that assumes map) based on the gist mentioned on the note in that last post I linked / create-subscription's readme, so it should be behaving effectively the same. Maybe it will be useful to someone?

My Subscribe.tsx ```ts import * as React from "react"; import * as rx from "rxjs"; export type SubscribeProps = { value$: rx.Observable; children: (value: T) => React.ReactNode; }; type SubscribeState = { value$: rx.Observable; node: React.ReactNode; }; // Based on https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3 export default class Subscribe extends React.Component< SubscribeProps, SubscribeState > { state: SubscribeState = { value$: this.props.value$, node: null, }; sub = rx.Subscription.EMPTY; static getDerivedStateFromProps( nextProps: SubscribeProps, prevState: SubscribeState, ) { if (nextProps.value$ !== prevState.value$) { return { dataSource: nextProps.value$, node: null, }; } return null; } render() { return this.state.node; } componentDidMount() { this.subscribe(); } componentDidUpdate( prevProps: SubscribeProps, prevState: SubscribeState, ) { if (this.state.value$ !== prevState.value$) { // Similar to adding subscriptions, // It's only safe to unsubscribe during the commit phase. this.sub.unsubscribe(); this.subscribe(); } } componentWillUnmount() { this.sub.unsubscribe(); } subscribe() { // Event listeners are only safe to add during the commit phase, // So they won't leak if render is interrupted or errors. const { value$ } = this.state; this.sub = value$.subscribe((value: T) => { this.setState(state => { // If this event belongs to the current data source, update. // Otherwise we should ignore it. if (value$ === state.value$) { const node = this.props.children(value); return { node }; } return null; }); }); } } ```
typeofweb commented 6 years ago

Thanks for the tips @simonbuchan, I was considering using that gist but decided to go simpler route in the end and I just used create-subscription. It's maintained by Facebook and I hope it'll be updated in case any breaking-changes are introduced in React 17 — so I don't have to worry about it.