MurakamiKennzo / rxjs-react

The right way When using RxJS in React.
MIT License
0 stars 0 forks source link

do this is a better way? #1

Open MurakamiKennzo opened 3 years ago

MurakamiKennzo commented 3 years ago

Here is another way when using rxjs in react:

import { Observable, merge, Subject, of, combineLatest } from 'rxjs'
import { map, mapTo, skip, startWith, scan } from 'rxjs/operators'
import React from 'react'
import ReactDOM from 'react-dom'

const createEventObservable = <T extends {} = any>(): [Observable<T>, (e: T) => void] => {
  let subject$ = new Subject<T>()

  const observable = subject$.asObservable()
  const handler = e => subject$.next(e)

  return [observable, handler]
}

const toState = <T extends {} = any>(reducer$: Observable<(state: T) => T>): Observable<T> => 
  reducer$.pipe(
    startWith(undefined as unknown),
    // @ts-ignore
    scan((x, f) => f && f(x)),
    skip(1),
  )

const App = (): Observable<JSX.Element> => {

  const [addVdom$, addReducer$] = Add()
  const [subtractVdom$, subtractReducer$] = Subtract()

  const reducer$ = of(() => 0)

  const state$ = toState( merge(addReducer$, subtractReducer$, reducer$) )

  return combineLatest(state$, addVdom$, subtractVdom$).pipe(
    map(([count, addVdom, subtractVdom]) => (
      <>
        <p>Current count: { count }</p>
        <p>{ addVdom } { subtractVdom }</p>
      </>
    ))
  )
}

const Add = (): [Observable<JSX.Element>, Observable<(x: number) => number>] => {
  const [observable, handler] = createEventObservable<React.MouseEvent<HTMLButtonElement, MouseEvent>>()

  return [
    of(<button onClick={handler}>add count</button>),
    observable.pipe(mapTo((x: number) => x + 1)),
  ]
}

const Subtract = (): [Observable<JSX.Element>, Observable<(x: number) => number>]  => {
  const [observable, handler] = createEventObservable<React.MouseEvent<HTMLButtonElement, MouseEvent>>()

  return [
    of(<button onClick={handler}>subtract count</button>),
    observable.pipe(mapTo((x: number) => x - 1)),
  ]
}

const vdom$ = App()

vdom$.subscribe(
  vdom => {
    ReactDOM.render(vdom, document.getElementById('app'))
  }
)

Do this is a better way?

MurakamiKennzo commented 3 years ago

and this?

import { Observable, Subject } from "rxjs";
import { map, startWith, pluck } from 'rxjs/operators'
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

type React$<T> = Observable<[T, React.ReactElement]>

const Input$ = ((): React$<string> => {
  const event$ = new Subject<React.ChangeEvent<HTMLInputElement>>()

  return event$.pipe(
    pluck('target', 'value'),
    startWith(''),
    map(s => [
      s,
      <input value={s} onChange={e => event$.next(e)} />
    ])
  )
})();

const App$: React$<string> = Input$.pipe(
  map(([s, vdom]) => [
    s,
    <>
      <p>the current value is: {s}</p>
      {vdom}
    </>
  ])
)

const runApp = <T extends {} = any>(element: HTMLElement | null, App$: React$<T>) => {
  const App = () => {
    const [vdom, setVdom] = useState<React.ReactElement | null>(null)

    useEffect(() => {
      const subscription = App$.pipe(map(x => x[1])).subscribe(setVdom)

      return () => {
        subscription.unsubscribe()
      }
    }, [])

    return vdom
  }

  ReactDOM.render(<App />, element)
}

runApp(document.getElementById('app'), App$)