Async dispatch in redux-toolkit - javascript

I'm using redux-tookit for state.
My action:
const updateSomething = (data: string) => async (dispatch) => {
await user.set({ data })
dispatch(updatedData(data))
}
In my view I want to do something like:
const dispatch = useDispatch()
await dispatch(updateSomething('Hi!'))

Update 5 July 2021
TL;DR
If typeof store.dispatch still doesn't give you the right typing with ThunkDispatch as one of the overloaded options, you may consider manually typing it, like so: -
export type AppDispatch = ThunkDispatch<AppState, null | undefined, AnyAction> &
Dispatch<AnyAction>;
Note: This is the correct typing with the default middleware, if you have added more middlewares you should try to figure out the possibilities.
Background
While my proposed solution (below) works in codesandbox, it doesn't work in my project, which I ported from vanilla redux to redux toolkit. Maybe some of the packages installed break the types, just a speculation but when I include redux-debounced in my codesandbox sample (link below), the type for store.dispatch is falled back to Dispatch<AnyAction>, even without including redux-debounced in middleware.
This is certainly a mystery that has to be resolved!!
I had the similar issue as TS, so I made a simple project in codesandbox and surprisingly it works with a minor tweak!
In my view, what TS meant is that updateSomething('Hi!') is a valid thunk created using createAsyncThunk() in redux toolkit, where dispatching the thunk should return a Promise. That's a feature in redux toolkit. But unfortunately, somehow typescript is returning AsyncThunkAction and invoking the following line:
await dispatch(updateSomething('Hi!'));
actually yields a typing error. Here's what I got in my codesandbox project:
In my case, fetchDynamicName() is a valid thunk and supposedly the type of dispatchReturn should be a Promise.
With a minor tweak found in this post, it actually works!!
All we need is to export the dispatch type of the store, like so:
export type AppDispatch = typeof store.dispatch;
and assign the type to the dispatch function before using it:
const dispatch: AppDispath = useDispatch();
And voilĂ ! See the screenshot below:
You can take a look at my codesandbox project at https://codesandbox.io/s/fast-cdn-08vpu?file=/src/App.tsx.

Related

Is the "TypedUseSelectorHook" necessary?

I am relatively new to React with Typescript so I was following the video which was made by Mark Erikson and Jason Lengstorf on the Redux Toolkit website. Here is the link incase you want to see it.
At approximately 45 minutes in, Mark mentions two constructs which can be seen in the image below. (lines 4 and 5).
See picture
I had some trouble understanding these constructs so I made my own counter example using redux toolkit. I did not have to use the typecasts as Mark did. I just want someone to confirm that what I did is also OK and perhaps explain the differences. My code can be found in this Code Sandbox Link.
Basically the exports in the store look like this :
// Export for provider in App.tsx
export const appStore = configureStore({ reducer: { counterReducer } });
// Export dispatch
export const appDispatch = appStore.dispatch;
export const appState = appStore.getState();
Here is how the state is consumed :
let counterValue = useSelector(
(state) => appStore.getState().counterReducer.value
);
And finally I use this code to dispatch the action:
appDispatch(add(1));
Thanks in advance.

Redux Toolkit + Redux sagas

I refactoring the store in my project with redux toolkit and we use also redux sagas;
So I have a slice with an action:
createTask: (state: myTypeState, action:PayloadAction<myTypeState>) => {
state.createTaskStatus = status.fetching
},
In my sagas I connect the two part like that:
takeLatest(createTask().type, mySagaFunction),
In my component I use it like that with 3 parameters, needed for my saga:
dispatch(createTask({
name,
pictureFile: picture,
jobstation,
}))
But in my action I didn't use the second parameters and so I have an eslint warning:
`Unused parameter action``
How can I fix that warning ?
ps: I have also try to connect toolkit and sagas like that:
export const createTaskAction = createAction(types.CREATE_TASK)
and
takeLatest(CREATE_TASK, createTask),
With that, i need to dispatch this createTaskAction and the goal is to remove as much code as possible ^^
what is the best way to do it?
This will work without Unused parameter action:
createTask: (state: myTypeState) => {
state.createTaskStatus = status.fetching
},

Using React and Redux Hooks, why is my action not firing?

Edit: SOLVED! Please see below.
I want my Blog component to fire the fetchBlog action creator every time the browser requests its URL, be it via a link or a refresh. I'd like to do it with the React useEffect Hook and with the React-Redux useDispatch and useSelector Hooks. However, my action only fires when following the link to the page; I do not understand why, even after reading several explanations (like the official docs).
Here is the code:
// Everything duly imported, or else VSC would yell at me
export default function Blog() {
const dispatch = useDispatch();
// slug is set here with useSelector, this always works
useEffect(() => {
dispatch(fetchBlog(slug))
}, [slug, dispatch]);
const blog = useSelector((state) => state.blogs[0]);
// return renders the blog information from the blog constant
// since the action does not fire, blog is undefined because state.blogs is an empty array
}
I know that, on refresh, fetchBlog does not fire because of Redux DevTools and also because I put a debugger there. (And the back-end logs don't show the request coming in.) The action creator itself and the reducer must be working; if they weren't, the page would not load correctly when visited through a link.
Edit: I have determined useSelector and useDispatch are not the root cause of the problem, as changing the code to use connect with mapStateToProps and mapDispatchToProps gives the same result. The issue seems to be with useEffect.
I think the problem is you are returning the call to dispatch. Functions returned from useEffect are clean up functions, so I don't think this would run on mount, or update - only before unmount. Try this:
export default function Blog() {
// ...
// Don't return from useEffect. Just call dispatch within the body.
useEffect(() => {
dispatch(fetchBlog(slug);
}, [slug, dispatch]);
// ...
}
https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
I'd like to clarify what the issue was, which #Trace guided me to finding.
useEffect wasn't being called on refresh because it gets called after the component renders/returns. When refreshing, the state - including the blog data - is lost; instead of returning, a TypeError is thrown because data.title doesn't exist. So useEffect never gets the chance of being called and fetch the blog's content.
The solution to that goes like this:
export default function Blog() {
// ...
useEffect(/* ... */)
const blog = useSelector((state) => state.blogs[0]);
if (!blog) {
return <p>Loading...</p>
}
// return actual blog contents here
}
So now fetchBlog does get called, updating blog and rendering the content.
It isn't clear to me where the slug comes from.
In theory useEffect runs after every render. In case of multiple parameters it will run the callback when one of the array parameters passed in the second argument changes.
Either create a useEffect with empty array as second argument to run it 'once' (e.g. when you refresh) or check the slug value.
Edits after checking the repo:
It's not going to work because useEffect is run AFTER the render (which was included in my answer although someone downvoted it). Dispatching the call will only happen after, or not at all if the exception was thrown before (in this case a nullpointer).
You can get the slug from react-router with match, may be handy for you to know.
export default function Blog({ match }) {
const slug = match.params.slug;
etc
The git repo shows how dispatch as is added as array parameter to useEffect, which is not necessary.

Sequencing two actions together using an epic in redux-observable

I am building an application with react-native using redux-observable.
Right now I am struggling to do a simple decoration over two actions in redux-observable by using an epic. What I am trying to achieve is simply to map two actions to one.
The error I am getting is:
You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
Here is the code I am using:
import { Observable } from 'rx';
export const startApplicationEpic = (action$: Observable) =>
action$
.ofType('START_APPLICATION')
.flatMap(() =>
Observable.of(
{ type: 'REGISTER_PUSH_NOTIFICATIONS'},
{ type: 'AUTHENTICATION_REQUEST' }
)
);
// index.js
store.dispatch({ type: 'START_APPLICATION' })
To me the RxJS code I am using is valid, and if epics are a stream than this should work, or is it only possible to dispatch a single action from an epic?
It appears you're importing rx (which is v4) instead of rxjs (which is v5). So your code is providing a v4 Observable to the flatMap of a v5 Observable (redux-observable internals by default give you v5). The two versions cannot interop by default, but there are temporary interop layers. I admit this is all pretty confusing.
The best way to use v4 with redux-observable is to include the adapter we created for it redux-observable-adapter-rxjs-v4. If if you are using v4 on accident or it otherwise doesn't make a difference, definitely use v5 instead.
Once you fix that, the code as provided works as expected, without errors: https://jsbin.com/xiqigi/edit?js,console

Redux Dev Tools not working for large action payload

UPDATE: I've narrowed down the issue quite a bit from this first post. please see the latest update. The problem appears to be to do with the size or complexity of the action payload rather than it being because the action is invoked following an async call.
I'm working on a react/redux application and am having a problem using the time travel feature in redux dev tools chrome extension.
When I replay the application in the slider monitor the first async call to a webapi action does not replay. All synchronous actions and async network calls except the first work just fine. Its just the first that doesn't render. I've tried using just redux-thunk for the async, but have also tried it with redux-saga (the current configuration). Im running the application in webpack-dev-server
The application itself is working function (all code is in typescript)
I've tried all kinds of configuration changes, but nothing seems to have any effect. Any ideas would be greatly appreciated.
Heres my configureStore file
function configureStore() {
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootreducer, compose(
applyMiddleware(invariant(), sagaMiddleware, thunk),
window.devToolsExtension ? window.devToolsExtension() : (f:any) => f
));
if (window.devToolsExtension) window.devToolsExtension.updateStore(store);
sagaMiddleware.run(logsSaga)
return store;
}
export default configureStore;
my saga
function* fetchLogs(logSearchParams: any) {
try {
const data = yield call(getLogTableData,
logSearchParams.params);
yield put({type: "ReceiveLogs",
data, logSearchParams:logSearchParams.params});
} catch (e) {
yield put({type: "LogsError", message: e.message});
}
}
export function* logsSaga() {
yield* takeEvery("RequestLogs", fetchLogs);
}
and the network call
return window.fetch('api/logs/gettable', {
method: 'post',
body: JSON.stringify(logSearchParams),
headers: headers
}).then(r => r.json());
Thanks for any help
EDIT: I'm using Redux-React and the connect decorator to connect Redux with the components. The action is called from an actionCreator
export let searchClicked = () => {
return (dispatch, getState) => {
let params = getSearchParms(getState());
return dispatch({type:'RequestLogs', params});
}
};
This is wired in to the components click handler using React-Redux mapDispatchToProps
Another two components receive the state via mapStateToProps, for example
function mapStateToProps(state) {
return state.logs;
}
When I debug this function isn't invoked when it should be (and is afterwards)
UPDATE:
I've tracked the problem down to a reducer for "ReceiveLogs", which is invoked by Redux-Saga. I have three reducers for this action. If I comment out this line
case "ReceiveLogs":
return {data:action.data.rows, selected:state.selected}
then other components which rely on reducers for this action work correctly and the dev tools replay works as expected. With this line, it fails. The problem appears to be "data:action.data.rows". rows is an array and if I change this to return an empty array, then replay works.
I think I'll give up for today.
UPDATE: It appears that the problem is possibly to do with the size of the array which is sent as part of the ReceiveLogs payload. if I restrict the size of the array by slicing e.g
return {data:action.data.rows.slice(0, 3), selected:state.selected}
then it works. If I include the 4th member of the array, it doesn't work. The 4th member of the array is significantly larger than the others since it has quite a large (and deep) and object included.
Is there some kind of size limit for action payloads and redux-dev-tools??? I'll carry on playing.
Check out Redux Devtools Excessive use of memory and CPU Troubleshooting:
That is happening due to serialization of some huge objects included in the state or action. The solution is to sanitize them.

Categories