Lesbaa
Lesbaa

Reputation: 2326

equivalent of redux 'getState' in from an observable in rxjs

I'm having a play around with rxjs with the intention of getting to grips with reactive and functional programming in JS.

I have the following trivial app:

import Rx from 'rxjs'

const domContainer = document.querySelector('[data-container]')

const clickElement = document.querySelector('[data-init-fetch]')
const loader = document.querySelector('.loader')

// only emit when all the data is returned
const fetchData = (first, last) => Rx.Observable.range(first, last)
  .flatMap(async n => {
    const res = await fetch(`https://swapi.co/api/people/${n}`)
    return await res.json()
  })
  .reduce((acc, item) => [...acc, item], [])

const createEventStream = ({
  domNode,
  createRequestDataAction,
  createReceiveDataAction,
}) => {

  const handleButtonClick = Rx.Observable.fromEvent(domNode, 'click')

  const request = fetchData(1, 10)
  // i'd like to be be able to 'getState' in here to use correct offset and 
  // fetch the next ten entries

  const setIsFetching = handleButtonClick
    .map(createRequestDataAction)

  // fetch data on request
  const requestData = handleButtonClick
    .switchMapTo(request)
    .map(createReceiveDataAction)

  return Rx.Observable.merge(
    requestData,
    setIsFetching,
  )
}

const initState = {
  offset: 1,
  isFetching: false,
  chars: {},
}

const eventStream = createEventStream({
  domNode: clickElement,
  createReceiveDataAction,
  createRequestDataAction,
})
  .scan(reducer, initState)
  .subscribe(
    renderMarkup,
  )

function reducer (
  state = initState,
  {
    type,
    payload,
  }
) {
  // do some reducery stuff in here and return new state
}

function renderMarkup(state) {
  // renders the state to the DOM
}

function createReceiveDataAction(data) {
  return {
    type: 'apiReceive',
    payload: {
      chars: data,
    },
  }
}

function createRequestDataAction() {
  return { type: 'apiRequest' }
  // this action will update offset += 10 in the reducer
}

My question is this, if I were using redux (although I'm not here) when I dispatch my fetchData action I would simply call getState() to get offset from the current state and use that in the fetchData action.

Is there a way to do this with rxjs (and without redux) in the above code? eg I would want to use variables from state in the fetchData call.

Would I need to create another Observable to deal with offset? Or am I approaching this in entirely the wrong way?

Edited for clarity.

Upvotes: 1

Views: 539

Answers (1)

Picci
Picci

Reputation: 17762

It is not really clear to me what you are trying to achieve.

I have though few comments that may help you.

Use Observable instead of Promise

You can convert this code

const fetchData = (first, last) => Rx.Observable.range(first, last)
  .flatMap(async n => {
    const res = await fetch(`https://swapi.co/api/people/${n}`)
    return await res.json()
  })
  .reduce((acc, item) => [...acc, item], [])

to this

const fetchData = (first, last) => Rx.Observable.range(first, last)
  .mergeMap(n => {   // mergeMap is the new name of flatMap
    const promiseCall = fetch(`https://swapi.co/api/people/${n}`)
    return Observable.fromPromise(promiseCall).map(res => res.json())
  })
  .reduce((acc, item) => [...acc, item], [])

You may also look at this question to find other ways to execute many http requests and return only when all of them have completed.

In eventStream you store a subscription

If you run the following code you end up storing a Subscription in the variable eventStream. A Subcription instance can then be used to unsubscribe.

const eventStream = createEventStream({
  domNode: clickElement,
  createReceiveDataAction,
  createRequestDataAction,
})
  .scan(reducer, initState)
  .subscribe(
    renderMarkup,
  )

Use state information in fetchData call

I assume you store state information in the object referenced by the state variable.

If this is correct, I would proceed with something along these lines

const createEventStream = ({
  domNode,
  createRequestDataAction,
  createReceiveDataAction,
}) => {

  Rx.Observable.fromEvent(domNode, 'click')
    .map(createRequestDataAction)
    .concat(fetchData(initState.offset, initState.offset + 10))
    .map(createReceiveDataAction)

}

You could even think to remove the reduce function with something like this.

const createEventStream = ({
  domNode
}) => {

  Rx.Observable.fromEvent(domNode, 'click')
    .do(_ => state.fetching = true)
    .concat(fetchData(initState.offset, initState.offset + 10))
    .do(_ => initState.offset = initState.offset + 10)
    .do(_ => initState.fetching = false)

}

I could not test my recommendation, so I am not really sure there are no mistakes.

Upvotes: 1

Related Questions