Reputation: 2326
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
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