Getting simple epic working in redux-observable - redux

Trying to implement my first simple epic using redux-observable but running into two issues:
After I hit my break point, I get an error that reads "Cannot read property 'closed' of undefined" and after that I don't hit the break point in my epic -- see below
Once I hit my epic, I want to dispatch myAction which is an action creator. Not sure how to add dispatch. Initially, I didn't add anything and got dispatch not defined error. Then I noticed that my IDE automatically added import { dispatch } from 'rxjs/internal/observable/pairs';. Not sure if this is the way to add dispatch to my epic. This could also be the reason for the error.
The fact that I'm initially hitting my epic when a particular action is fired, tells me that it's successfully added into the middleware pipeline but clearly something is not right.
Here's what my epic looks like:
import { Observable } from 'rxjs';
import { ofType } from 'redux-observable';
import { filter, map, mapTo } from 'rxjs/operators';
import { dispatch } from 'rxjs/internal/observable/pairs'; // ??? Is this how I access dispatch?
import * as types from '../../actions/action-types';
import { myAction} from '../../actions/my-actions';
export const mySimpleEpic = (action$, state$) => action$.pipe(
ofType(types.SOMETHING_HAS_HAPPENED),
map(() => {
debugger
dispatch(myAction("Hello World!"))
})
)
What am I doing wrong?

Epics don't dispatch actions, they emit actions. It's redux-observable who dispatches the actions emitted from your epics. The basic epic structure should be something like:
export const mySimpleEpic = action$ =>
action$
.ofType(types.SOMETHING_HAS_HAPPENED)
.map(() => myAction("Hello World!"));

Related

Redux-Toolkit Middleware custom: Uncaught RangeError: Maximum call stack size exceeded

I am studying Redux-Toolkit studio, and in particular, I am approaching custom middleware.
Below I report the definition of my store within my classic application example:
import { combineReducers, configureStore} from '#reduxjs/toolkit';
import {todosSlice} from '.. /features/todos/todosSlice';
import { filterStore } from '.. /features/todos/filterSlice';
import { myLog } from '.. /reduxtoolkitmiddlewarecustom/myLog';
const rootReducer = combineReducers({
todos: todosSlice.reducer,
filter: filterStore.reducer
});
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware(). concat(myLog),
});
As you see in the store amount, the middleware "custom" defined in '.. /reduxtoolkitmiddlewarecustom/myLog';
The definition of middleware is as follows:
export const myLog = (store) => {
console.log('Init myLog');
return function myDispatch(next) {
return function myAction(action) { //action is the action received from the store.
store.Dispatch({ type: 'INIT_MYLOG', payload: null });
console.log(action);
//return next(action);
}
}
};
Well, if I emit a Dispatch, I do not return next(action), is that the return I still get an error of "Uncaught Rangeerror: Maximum call stack size exceeded".
I thought I understood, but evidently, I don't. The only way to make the code work is, for example, to make a console.log
What am I tripping about?
If found the error, in general, if the middleware makes a Dispatch in the store, must a corresponding reducer/action creator exist?
Thank you so much
your middleware dispatches INIT_MYLOG on every action. and that dispatched INIT_MYLOG then triggers your middleware. and that then dispatches. an endless circle.
Solution? Don't dispatch in a middleware except if you do it as a reaction to one specific action.

React-redux dispatch action onclick using hooks and redux toolkit

Fairly new to redux, react-redux, and redux toolkit, but not new to React, though I am shaky on hooks. I am attempting to dispatch an action from the click of a button, which will update the store with the clicked button's value. I have searched for how to do this high and low, but now I am suspecting I am thinking about the problem in React, without understanding typical redux patterns, because what I expect to be possible is just not done in the examples I have found. What should I be doing instead? The onclick does seem to capture the selection, but it is not being passed to the action. My goal is to show a dynamic list of buttons from data collected from an axios get call to a list of routes. Once a button is clicked, there should be a separate call to an api for data specific to that clicked button's route. Here is an example of what I currently have set up:
reducersRoutes.js
import { createSlice } from "#reduxjs/toolkit";
import { routesApiCallBegan } from "./createActionRoutes";
const slice = createSlice({
name: "routes",
initialState: {
selected: ''
},
{... some more reducers...}
routeSelected: (routes, action) => {
routes.selected = action.payload;
}
},
});
export default slice.reducer;
const { routeSelected } = slice.actions;
const url = '';
export const loadroutes = () => (dispatch) => {
return dispatch(
routesApiCallBegan({
url,
{...}
selected: routeSelected.type,
})
);
};
createActionRoutes.js
import { createAction } from "#reduxjs/toolkit";
{...some other actions...}
export const routeSelected = createAction("routeSelection");
components/routes.js:
import { useDispatch, useSelector } from "react-redux";
import { loadroutes } from "../store/reducersRoutes";
import { useEffect } from "react";
import { routeSelected } from "../store/createActionRoutes";
import Generic from "./generic";
const Routes = () => {
const dispatch = useDispatch();
const routes = useSelector((state) => state.list);
const selected = useSelector((state) => state.selected);
useEffect(() => {
dispatch(loadroutes());
}, [dispatch]);
const sendRouteSelection = (selection) => {
dispatch(routeSelected(selection))
}
return (
<div>
<h1>Available Information:</h1>
<ul>
{routes.map((route, index) => (
<button key={route[index]} className="routeNav" onClick={() => sendRouteSelection(route[0])}>{route[1]}</button>
))}
</ul>
{selected !== '' ? <Generic /> : <span>Data should go here...</span>}
</div>
);
};
export default Routes;
Would be happy to provide additional code if required, thanks!
ETA: To clarify the problem - when the button is clicked, the action is not dispatched and the value does not appear to be passed to the action, even. I would like the selection value on the button to become the routeSelected state value, and then make an api call using the routeSelected value. For the purpose of this question, just getting the action dispatched would be plenty help!
After writing that last comment, I may actually see a couple potential issues:
First, you're currently defining two different action types named routeSelected:
One is in the routes slice, generated by the key routeSelected
The other is in createActionRoutes.js, generated by the call to createAction("routeSelection").
You're importing the second one into the component and dispatching it. However, that is a different action type string name than the one from the slice - it's just 'routeSelection', whereas the one in the slice file is 'routes/routeSelected'. Because of that, the reducer logic in the slice file will never run in response to that action.
I don't think you want to have that separate createAction() call at all. Do export const { routeSelected } = slice.actions in the slice file, and dispatch that action in the component.
I'm also a little concerned about the loadroutes thunk that you have there. I see that you might have omitted some code from the middle, so I don't know all of what it's doing, but it doesn't look like it's actually dispatching actions when the fetched data is retrieved.
I'd recommend looking into using RTK's createAsyncThunk API to generate and dispatch actions as part of data fetching - see Redux Essentials, Part 5: Async Logic and Data Fetching for examples of that.

Is it possible to manually dispatch thunk state in createAsyncthunk

Hey fellow programmers,
Been having fun learning react-redux lately, but I do have one question that bothers me.
My understanding is that, by using createAsyncThunk it will automatically generates action type constants. (pending, fulfilled, and rejected)
What I wanted to know is that is there any way to manually dispatch action type during createAsyncthunk , so that we can have more flexibility in our code.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId).then(
...
dispatch(fulfilled) // is this possible ?
).catch(
dispatch(rejected) // is this possible ?
)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
...,
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[fetchUserById.fulfilled]: (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
}
}
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
The point of createAsyncThunk is that it generates those action types, _and dispatches them for you automatically. You definitely do not need to dispatch(fulfilled()) yourself, because that's what createAsyncThunk does for you - you just need to return a promise that either resolves or reject, and it dispatches the fulfilled/rejected action types based on that.
You do get access to thunkAPI.dispatch, so you can dispatch other actions if necessary, but you don't need to worry about the fulfilled/rejected actions yourself.

When to save the state tree?

The Redux manual says every reducer should be a pure function and even no API call should be made, I then curious to know, then, when should I get chance to save my App state tree to an external storage or the backend?
You can save your redux store using and action with the Redux Thunk middleware.
Lets say you want to want to save the store when the user clicks save. First, define an action to do the save:
actions/save.js
import fetch from 'isomorphic-fetch'
export const save = state => {
return () => {
fetch('/api/path/to/save', {
body: JSON.stringify(state),
headers: {
'content-type': 'application/json'
}
method: 'POST'
}
}
}
Then in your component:
components/SaveButton.js
import React from 'react'
import { connect } from 'react-redux';
import { save } from '../actions/save'
const SaveButton = props => {
let { onSave, state } = props
return <button onClick={onSave(state)}>Save</button>
}
const mapStateToProps = state => {
return {state}
}
const mapDispatchToProps = dispatch => {
return {
onSave: state => dispatch(save(state))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SaveButton)
You shouldn't do that as part of your reducer.
Instead, whenever you want to save some part of your state, you should dispatch an asynchronous action (with the help of middleware like redux-thunk) perhaps called SAVE_XYZ with it's payload being the part of the store you want to save.
dispatch(saveXYZ(data))
saveXYZ needs to be an async action creator that will dispatch the API call to persist your data, and handle the response accordingly.
const saveXYZ = payload => dispatch => {
dispatch(saveXYZPending());
return apiCallToStore(...)
.then(data => saveXYZDone())
.catch(err => saveXYZError());
}
You can read more on async actions and how to handle them.
Two basic approaches:
Use store.subscribe(callback), and write a callback that gets the latest state and persists it after some action has been dispatched
Write a middleware that persists the state when some condition is met
There's dozens of existing Redux store persistence libraries available that will do this work for you.

Using redux-loop with thunk action creators

I'm using redux-loop to call action creators from my reducers. This works great normally.
However, I'm also using thunk for some of my action creators. If I take a regular action creator and convert it to a thunk, it's no longer usable in redux-loop.
Is there a way to call thunks from redux-loop in reducers?
I recommend that you pass applyMiddleware prior to install in your enhancers.
createStore(reducer, initialState, compose(
applyMiddleware(thunk),
install()
));
applyMiddelware will catch actions passed to store.dispatch() before redux-loop tries to dispatch them. Right now for the next version of redux-loop I'm planning to have install() accept the other enhancers for the store before applying its own modifications so that this doesn't end up being a problem.
I had no luck combining redux-loop and redux-thunk as-is. The problem is that if you you call applyMiddleware(thunk) first and redux-loop's install() afterwards, actions dispatched by thunks will not have their effects evaluated (because the dispatch passed to thunks by the middleware isn't enhanced by redux-loop yet); while if you swap the two around, effects are not able to dispatch thunks (because the version of dispatch redux-loop uses for effects is not enhanced with the thunk middleware).
To work around this problem, I needed to write the following pretty hacky store enhancer:
import { applyMiddleware, compose } from 'redux'
import { install } from 'redux-loop'
export default function loopWithThunk(createStore) {
let enhancedStore
const storeSaver = (createStore) => (reducer, initialState) => {
enhancedStore = createStore(reducer, initialState)
return enhancedStore
}
const thunkMiddleware = () => next => action => {
return (typeof action === 'function')
? action(enhancedStore.dispatch, enhancedStore.getState)
: next(action)
}
return compose(
storeSaver,
install(),
applyMiddleware(thunkMiddleware)
)(createStore)
}
You can use it like this:
const store = createStore(reducer, loopWithThunk)

Resources