redux-logic
redux-logic copied to clipboard
Docs: note that debounce/throttle apply to the action, not `process`?
So I had a logic like this:
createLogic({
type: 'DO_SOMETHING',
debounce: 250,
process: etc
});
From the docs I assumed that debounce would debounce the process function, not the action itself and I was confused why not all of my DO_SOMETHING actions were going through (other reducers are changed by DO_SOMETHING also). Was a misunderstanding on my part.
I think it would be helpful to note somewhere that it's the actual redux dispatch that gets debounced/throttled, rather than just the process function!
Another way to think of how I got confused: I was trying to "attach async logic to existing actions", when what redux-logic does is sort-of "turns a dispatch into an async dispatch with optional additional async processing"
@SneakyMax Thanks for writing! Yes, the debounce is in indeed happening on the front of the chain on the action before it goes to the reducers. This allows you to send all onchange actions from an input and only use the last one that fires after a pause rather than every key stroke.
I will definitely update the docs to indicate more specifically how this works. Sorry for the confusion.
had the same confusion when tried to listen redux-logic, debounce was actually causing whole application to freeze
import * as R from 'ramda'
import { createLogic } from 'redux-logic'
import { actionTypes } from 'redux-form'
import * as previewActions from '~/app/actions/preview'
import { createStoryFormId } from '~/app/containers/CreateStoryForm'
import { isFormAction } from '~/app/utils'
import createPage from '~/app/services/createPage'
const logic = createLogic({
type: actionTypes.CHANGE,
debounce: 1500,
latest: true,
validate({ action }, allow, reject) {
const valid = isFormAction(createStoryFormId, action)
if (valid) {
allow(action)
} else {
reject()
}
},
async process({ action }, dispatch, done) {
const { payload: link } = action
if (R.isEmpty(link)) {
dispatch(previewActions.setPage({}))
} else {
try {
dispatch(previewActions.setLoading(true))
const { data } = await createPage(link)
dispatch(previewActions.setPage(data))
dispatch(previewActions.setLoading(false))
} catch (e) {
dispatch(previewActions.setLoading(false))
}
}
done()
},
})
export default logic
then I've tried
import * as R from 'ramda'
import debounce from 'lodash.debounce'
import { createLogic } from 'redux-logic'
import { actionTypes } from 'redux-form'
import * as previewActions from '~/app/actions/preview'
import { createStoryFormId } from '~/app/containers/CreateStoryForm'
import { isFormAction } from '~/app/utils'
import createPage from '~/app/services/createPage'
async function process({ action }, dispatch, done) {
const { payload: link } = action
if (R.isEmpty(link)) {
dispatch(previewActions.setPage({}))
} else {
try {
dispatch(previewActions.setLoading(true))
const { data } = await createPage(link)
dispatch(previewActions.setPage(data))
dispatch(previewActions.setLoading(false))
} catch (e) {
dispatch(previewActions.setLoading(false))
}
}
done()
}
const debouncedProcess = debounce(process, 500)
const logic = createLogic({
type: actionTypes.CHANGE,
validate({ action }, allow, reject) {
const valid = isFormAction(createStoryFormId, action)
if (valid) {
allow(action)
} else {
reject()
}
},
process: debouncedProcess,
})
export default logic
but only first part of process was dispatching actions (part before await), it was because createLogic was scanning my process function to find how many arguments it is expecting
finally made it working with
process: (context, dispatch, done) => {
debouncedProcess(context, dispatch, done)
},
At the end it was looking like this :expressionless:
import * as R from 'ramda'
import debounce from 'lodash.debounce'
import { createLogic } from 'redux-logic'
import { actionTypes, getFormSyncErrors } from 'redux-form'
import { CancelToken } from 'axios'
import { isPropEmpty } from '~/app/utils'
import * as previewActions from '~/app/actions/preview'
import { createStoryFormId } from '~/app/containers/CreateStoryForm'
import createPage from '~/app/services/createPage'
const isFormChanged = R.pathEq(['meta', 'form'])
const isFieldChanged = R.pathEq(['meta', 'field'])
const isFieldValid = (formId, fieldName, state) => {
const getErrors = getFormSyncErrors(createStoryFormId)
const errors = getErrors(state)
return isPropEmpty(fieldName, errors)
}
let cancel = null
async function process({ getState, action }, dispatch, done) {
const { payload: link } = action
if (R.isEmpty(link)) {
dispatch(previewActions.setPage({}))
} else {
// we have to wait for validation to pass
const state = getState()
const isLinkValid = isFieldValid(createStoryFormId, 'link', state)
if (!isLinkValid) return
if (cancel) {
cancel()
cancel = null
}
try {
dispatch(previewActions.setLoading(true))
const cancelToken = new CancelToken(c => {
cancel = c
})
const { data } = await createPage(link, cancelToken)
dispatch(previewActions.setPage(data))
dispatch(previewActions.setLoading(false))
} catch (e) {
dispatch(previewActions.setLoading(false))
}
cancel = null
}
done()
}
const debouncedProcess = debounce(process, 500)
const logic = createLogic({
type: actionTypes.CHANGE,
validate({ action, getState }, allow, reject) {
const isCreateStoryFormChanged = isFormChanged(createStoryFormId, action)
if (!isCreateStoryFormChanged) return reject()
const isLinkChanged = isFieldChanged('link', action)
if (!isLinkChanged) return reject()
return allow(action)
},
process: (context, dispatch, done) => {
debouncedProcess(context, dispatch, done)
},
})
export default logic
Thanks for sharing @BjornMelgaard