How to refactor this Ramda async pipe to be more succinct - functional-programming

I'm new to programming with Ramda. I refactored an async function to the below point-free style, however, I feel I could clean it up and make it more succinct was I more familiar with Ramda utility functions.
currentPercentage: R.pipeWith(R.andThen)([
async sloId => ({ sloId, slo: await SLOs.read(sloId) }),
async ({ sloId, slo }) => ({ slo, stream: await Streams.findBySLOId(sloId) }),
async ({ slo, stream }) => ({ slo, events: await Streams.readEvents(stream.id) }),
async ({ slo, events }) => currentPercentage(slo.denominator)(interpret(events)),
]),
What are your recommendations for further using Ramda utilities to rewrite this code in an idiomatic functional style?
I'm looking to make it simpler to understand by reducing the number of variables you need to juggle mentally.
A pseudo-code example of what I have in mind (might be the wrong direction to refactor towards):
currentPercentage: R.pipeWith(R.andThen)([
wrap,
merge("slo", SLOs.read),
merge("stream" Streams.findBySLOId),
merge("events", Streams.readEvents),
async ({ slo, events }) => currentPercentage(slo.denominator)(interpret(events)),
]),
I have a hunch I should be using the functions R.merge and R.pick, but not sure how to use them usefully in this context. Or maybe there's another utility that'll do the job.
The answer may be this is as simple as it gets.
update
After some experimenting, was able to refactor my code to:
currentPercentage: asyncPipe(loadSLOandResults(SLOs, Streams), currentPercentage),
const loadSLOandResults = (SLOs: SLOsAPI, Streams: StreamsAPI) =>
asyncPipe(
SLOs.read,
async slo => ({ slo, stream: await Streams.findBySLOId(slo.id) }),
async ({ slo, stream }) => ({ slo, results: interpret(await Streams.readEvents(stream.id)) })
)
I changed the function signature of currentPercentage to line up with the return value of loadSLOandResults.
I created an asyncPipe utility function to make code slightly less verbose.

Related

changing state with RTK Query

I'm learning about RTK Query and really confused. I'd be happy if someone could point me towards the right direction. My question is how one can manipulate the state of the application store the same way as it is done when using createAsyncThunk and setting up extraReducers.
export const asyncApiCall = createAsyncThunk("api/get_data", async (data) => {
const config = {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
};
const res = await axios.get( "http://apiserver/path_to_api",data,config );
return res['data']
} )
export const mySlice = createSlice({
name:"mySliceName",
initialState:{
data: [],
loadInProgress: false,
loadError: null,
extraData: {
// something derived based on data received from the api
}
},
extraReducers: {
[asyncApiCall .pending]: (state) => {
state.loadInProgress = true;
},
[asyncApiCall .fulfilled]: (state,action) => {
state.loadInProgress = false;
state.data = action.payload;
state.extraData = someUtilFunc(state.data)
},
[asyncApiCall.rejected]: (state) => {
state.loadInProgress = false;
state.loadError= true;
},
}
})
Now I'm replacing it with RTK Query. My current understanding is that RTK Query automatically generates hooks for exposing data received from the api and all the query-related info like if it's pending, if an error occurred etc.
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: builder => ({
getData: builder.query({
query: () => '/get_data'
}),
setData: builder.mutation({
query: info => ({
url: '/set_data',
method: 'POST',
body: info
})
})
})
})
export const { useSendDataMutation, useGetDataQuery } = apiSlice
If I want to store some additional data that may be affected by the api calls should I create another slice that will somehow interact with the apiSlice, or is it possible to incorporate everything in this existing code? I'm sorry for possible naivety of this question.
The short answer is that RTK Query is focused on purely caching data fetched from the server. So, by default, it stores exactly what came back in an API call response, and that's it.
There are caveats to this: you can use transformResponse to modify the data that came back and rearrange it before the data gets stored in the cache slice, and you can use updateQueryData to manually modify the cached data from other parts of the app.
The other thing to note is that RTK Query is built on top of standard Redux patterns: thunks and dispatched actions. Every time an API call returns, a fulfilled action gets dispatched containing the data. That means you can also apply another suggested Redux pattern: listening for that action in other reducers and updating more than one slice of state in response to the same action.
So, you've got three main options here:
If the "extra data" is derived solely from the server response values, you could use transformResponse and return something like {originalData, derivedData}
You could just keep the original data in the cache as usual, but use memoized selector functions to derive the extra values as needed
If you might need to update the extra values, then it's probably worth looking at listening to a query fulfilled action in another slice and doing something with it, like this silly example:
import { api } from "./api";
const someExtraDataSlice = createSlice({
name: "extraData",
initialState,
reducers: {/* some reducers here maybe? */},
extraReducers: (builder) => {
builder.addMatcher(api.endpoints.getPokemon.matchFulfilled, (state, action) => {
// pretend this field and this payload data exist for sake of example
state.lastPokemonReceived = action.payload.name;
}
}
})

Why my dispatch function in Redux gets a function instead of an action?

There is such kind of code that I have:
const mapStateToProps = (state, ownProps) => ({
historyData: getHistoryForSavedVariants(state)[ownProps.savedVariant.variantId],
isHistoryLoading: getHistoryLoading(state),
})
const mapDispatchToProps = (dispatch, ownProps) => ({
loadData: () => {
-----> dispatch(loadHistoryForSavedVariant(ownProps.savedVariant))
},
})
export default connect(mapStateToProps, mapDispatchToProps)(HistoryButton)
In another file loadHistoryForSavedVariant is the following:
export const loadHistoryForSavedVariant = (savedVariant) => {
return (dispatch) => {
dispatch({ type: REQUEST_HISTORY })
const url = `/api/saved_variant/${savedVariant.variantId}/saved_variant_history`
new HttpRequestHelper(url,
(responseJson) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
},
(e) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
},
).get({ xpos: savedVariant.xpos, ref: savedVariant.ref, alt: savedVariant.alt, familyGuid: savedVariant.familyGuids[0] })
}
}
So, as can be seen dispatch ultimately gets a function - (dispatch) => {...} and not an action. Why? I don't understand how that works. On Redux official webpage I see everwhere that dispatch gets an action and not a function, so I am confused. The code is, of course, working fine, I am just interested in this particular mechanism, in whats happening here.
That is a "thunk function". Thunks are a Redux middleware that allow you to pass functions into dispatch(), which is useful for writing async logic separate from your components.
For more details, see these Redux tutorials:
https://redux.js.org/tutorials/fundamentals/part-6-async-logic
https://redux.js.org/tutorials/essentials/part-5-async-logic

A clean way for an action to fire multiple asynchronous actions with createAsyncThunk

We're delaying the rendering of our React-Redux web app until several asynchronous app initialization tasks in the Redux store have been completed.
Here's the code that sets up the store and then fires off the initialization action:
export const setupStoreAsync = () => {
return new Promise((resolve, reject) => {
const store = setupStore()
store
.dispatch(fetchAppInitialization())
.then(unwrapResult)
.then(_ => resolve(store))
.catch(e => reject(e.message))
})
}
The promise rejection is very important since it's used to render an error message for the user in case the app cannot be properly set up. This code is very nice to read and works wonderfully.
The issue is with the action creator:
export const fetchAppInitialization = createAsyncThunk(
'app/initialization',
(_, thunkApi) =>
new Promise((resolve, reject) =>
Promise.all([thunkApi.dispatch(fetchVersionInfo())]).then(results => {
results.map(result => result.action.error && reject(result.error))
})
)
)
This code works beautifully. If any of these actions fail, the promise is rejected and the user sees an error message. But it's ugly - It's not as pretty as our normal action creators:
export const fetchVersionInfo = createAction('system/versionInfo', _ => ({
payload: {
request: { url: `/system/versionInfo` },
},
}))
We will at some point fire more than one fetch request in fetchAppInitialization, so the Promise.all function is definitely required. We'd love to be able to use Redux-Toolkit's createAction syntax to fire multiple promisified actions in order to shorten this action creator, but I have no idea if that's even possible.
Note: I'm using redux-requests to handle my axios requests.
Is createAsyncThunk even required?
Since I wasn't using the fetchAppInitialization action for anything but this single use case, I've simply removed it and moved the logic straight into the setupStoreAsync function. This is a bit more compact. It's not optimal, since the results.map logic is still included, but at least we don't use createAsyncThunk any more.
export const setupStoreAsync = () => {
return new Promise((resolve, reject) => {
const store = setupStore()
new Promise((resolve, reject) =>
Promise.all([store.dispatch(fetchVersionInfo())]).then(results => {
results.map(result => result.action.error && reject(result.error))
resolve()
})
)
.then(_ => resolve(store))
.catch(e => reject(e.message))
})
}
Update: I was able to make the code even prettier by using async/await.
export const setupStoreAsync = async () => {
const store = setupStore()
const results = await Promise.all([store.dispatch(fetchVersionInfo())])
results.forEach(result => {
if (result.action.error) throw result.error
})
return store
}

redux-observable dispatch actions

I need to dispatch some actions in some order using redux-observable however, it takes just last action to dispatch. Please see example:
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mapTo(fetchClientsPending(true))
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
});
});
fetchClientsSuccess is dispatched with clients but fetchClientsPending not, I totally do not get it why. I could use dispatch because I get it in params, but I feel it is not good solution(?). It should be done in the stream I guess. I am starting with RxJs and redux-observable. Is it possible to do?
Operators are chains of Observables where the input of one stream is the output of another. So when you use mapTo you're mapping one action to the other. But then your mergeMap maps that Pending action and maps it to that other inner Observable that does the ajax and such, effectively throwing the Pending action away. So think of RxJS as a series of pipes where data flows through (a stream)
While there is no silver bullet, in this particular case what you want to achieve can be done by using startWith at the end of your inner Observable
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
.startWith(fetchClientsPending(true)); // <------- like so
});
This is in fact the same thing as using concat with of(action) first, just shorthand.
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return Observable.concat(
Observable.of(fetchClientsPending(true)),
ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
);
});
That said, I would recommend against synchronously dispatching another action to set the state that fetching is pending and instead rely on the original fetchClients action itself for the same effect. It should be assumed by your reducers that if such an action is seen, that some how the fetching still start regardless. This saves you the boilerplate and helps a bit on micro-perf since you don't need to run through the reducers, epics, and rerender twice.
There's no rules though, so if you feel strongly about this, go for it :)

what should each chained function return in redux-observable?

answering my own question:
it makes sense that the epic (at the end) should return a stream of its own
but what about the chained function calls in between? Can I return plan objects and then return a stream at the end?
do I need to return the observable itself or the subscription object?
for example:
is this idiomatic "rxjs" or redux-observable?
const epic = ($action, store) =>
action$
.filter(filterFunction)
.map(action => processAction(action, store))
.map(processResult)
.flatMap(apiCall)
.map(res => ({ type: 'DONE', payload: res }))
const processAction = (action, store) =>
Observable.create(
obs => {
const result = // do something with store and action
return obs.next(result)
})
const processResult = result =>
result.subscribe(res => {
const newRes = // do some other stuff
return Observable.from({ newRes })
})
epic: takes actionStream, filters for X type, maps each type to a diff operation, packages and sends request body to server, informs reducer that server call was successful
processAction: receives actions of X type, map each action to a process that compares snapshots (from the store) and outputs the cumulative DIFF between state trees.
processResult: receives DIFF and creates payload request body
That would not be idiomatic rxjs because map is supposed to be side effect free; in your example it's being abused quite heavily. I don't believe it entirely does what you intend.
I'm happy to suggest some patterns if you want to describe what you'd like to do :)

Resources