Is getState replaced by an enhancer not used within actions in redux? - redux

I'm replacing getState with an enhancer as follows:
interface ArtificialStateEnhancerProps {
getArtificialStateGetter: StateGetterFn;
}
export const getArtificialStateEnhancer = ({
getArtificialStateGetter
}: ArtificialStateEnhancerProps) => {
return (createStore: typeof createStoreOriginal) => {
return (rootReducer, preloadedState, enhancers) => {
const store = createStore(rootReducer, preloadedState, enhancers);
const { getState } = store;
if (getArtificialStateGetter) {
return { ...store, getState: getArtificialStateGetter(getState) };
}
return { ...store };
};
};
};
When using store.getState() somewhere in my code it works an my custom getStage method is used. However within an Action or Sage (using select()) the original, native getState from redux is used.
Example action:
export function myAction(
slug: string
): ReduxAction<any> {
return async (
dispatch: Dispatch,
getState: () => any // Here getState is used but the native version of redux
): Promise<any> => {
const state = getState();
const foo = ...do something with `state`
};
}
Is this intended behavior?

It most likely depends on the ordering of where the middleware enhancer is added vs your custom enhancer.
If the middleware enhancer is added after the custom enhancer, then it is dealing with the base-level store methods. Each enhancer kind of "stacks" on top of each other.
My guess is that you've got something like:
compose(myCustomEnhancer, applyMiddleware(saga))
meaning that the middleware enhancer is after the custom one.
You'd need to flip those if that's the case.
(As a side question: could you give more details as to why you're writing a custom enhancer? It's a valid technique, but very rarely used, and it's even more rare that you would ever need or want to override getState.)
Also, note that we generally recommend against using sagas in almost all use cases, and especially if you're looking at using sagas for basic data fetching. Today, Redux Toolkit's "RTK Query" data fetching and caching API does a vastly better job of handling data fetching for you, and the RTK "listener" middleware handles the same reactive logic use case as sagas but with a simpler API, smaller bundle size, and better TS support.
See my talk The Evolution of Redux Async Logic for comparisons and explanations of why we recommend against sagas.

Related

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.

Perform Ajax Fetch in a Redux Reducer?

I'm trying to wrap my head around accessing the state inside Redux actionCreators; instead did the following (performed ajax operation in the reducer). Why do I need to access the state for this — because I want to perform ajax with a CSRF token stored in the state.
Could someone please tell me if the following is considered bad practice/anti-pattern?
export const reducer = (state = {} , action = {}) => {
case DELETE_COMMENT: {
// back-end ops
const formData = new FormData();
formData.append('csrf' , state.csrfToken);
fetch('/delete-comment/' + action.commentId , {
credentials:'include' ,
headers:new Headers({
'X-Requested-With':'XMLHttpRequest'
}) ,
method:'POST' ,
body:formData
})
// return new state
return {
...state ,
comments:state.comments.filter(comment => comment.id !== action.commentId)
};
}
default: {
return state;
}
}
From the redux documentation:
The only way to change the state is to emit an action, an object describing what happened. Do not put API calls into reducers. Reducers are just pure functions that take the previous state and an action, and return the next state. Remember to return new state objects, instead of mutating the previous state.
Actions should describe the change. Therefore, the action should contain the data for the new version of the state, or at least specify the transformation that needs to be made. As such, API calls should go into async actions that dispatch action(s) to update the state. Reducers must always be pure, and have no side effects.
Check out async actions for more information.
An example of an async action from the redux examples:
function fetchPosts(subreddit) {
return (dispatch, getState) => {
// contains the current state object
const state = getState();
// get token
const token = state.some.token;
dispatch(requestPosts(subreddit));
// Perform the API request
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
// Then dispatch the resulting json/data to the reducer
.then(json => dispatch(receivePosts(subreddit, json)))
}
}
As per guidelines of redux.
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
If you are asking whether it is anti-pattern or not then yes it is absolutely.
But if you ask what is the solution.
Here you need to dispatch async-action from your action-creators
Use "redux-thunk" or "redux-saga" for that
You can access the state and create some async action
e.g inside your action-creator ( Just for example )
export function deleteCommment(commentId) {
return dispatch => {
return Api.deleteComment(commentId)
.then( res => {
dispatch(updateCommentList(res));
});
};
}
export function updateCommentList(commentList) {
return {
type : UPDATE_COMMENT_LIST,
commentList
};
}
Edit: You can access the state -
export function deleteCommment(commentId) {
return (dispatch, getState) => {
const state = getState();
// use some data from state
return Api.deleteComment(commentId)
.then( res => {
dispatch(updateCommentList(res));
});
};
}

Redux - Jest: Testing functions that have void return

New to Jest and Redux and I'm having trouble with testing functions that are dispatching to the store but don't yield a return value. I'm trying to follow the example from the Redux website does this
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
however I have several "fetchtodos" functions that don't return anything which causes the error TypeError:
Cannot read property 'then' of undefined due to returning undefined
I'm wondering what I can do to test that my mock store is correctly updating. Is there a way to dispatch the function, wait for it to finish and then compare the mock store with expected results?
Thanks
Edit: We're using typescript
action from tsx
export function selectTopic(topic: Topic | undefined): (dispatch: Redux.Dispatch<TopicState>) => void {
return (dispatch: Redux.Dispatch<TopicState>): void => {
dispatch({
type: SELECT_Topic,
payload: topic,
});
dispatch(reset(topic));
};
}
test.tsx
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Select Topic action', () => {
it('should create an action to select .', () => {
const topic: Topic = mockdata.example[0];
const expectedAction = {
type: actions.SELECT_TOPIC,
payload: topic,
};
const store = mockStore(mockdata.defaultState);
return store.dispatch(actions.selectTopic(topic)).then(() => {
expect(store.getState()).toEqual(expectedAction);
});
});
});
The action is what I'm given to test(and there are many other functions similar to it. I'm getting that undefined error when running the test code, as the function isn't returning anything.
In Redux, the store's dispatch method is synchronous unless you attach middleware that changes that behavior, ie: returns a promise.
So this is likely a redux configuration problem. Be sure you are setting up your test store with the same middleware that allows you to use the promise pattern in production.
And as always, be sure to mock any network requests to avoid making api calls in test.

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)

Simplest Redux-React app in ES5: why aren't props being passed down?

I'm trying to build the most trivial possible Redux app. I have an initial state, I make a Redux store, I pass the store to ReactRedux.Provider, and I have my app as a child of the Provider.
However, my APP view, written as a stateless functional component, is not receiving any props. (The same is true if I write my APP view using React.createClass and checking for this.props in the render method.)
What am I doing wrong?
var initialState = {
counter: 0
};
var rootReducer = function(state, action) {
if (!state) state = initialState;
switch (action.type) {
default: // don't do anything yet
return state;
}
};
var store = Redux.createStore(rootReducer, initialState);
var APP = function(props) {
return React.createElement(
'div',
{},
props.counter // props is not defined
);
};
ReactDOM.render(
React.createElement(
ReactRedux.Provider,
{store: store},
React.createElement(APP, null)
),
document.getElementById('app')
);
You need to use the connect() function provided by React-Redux to create a wrapped version of your "APP" component that is actually hooked up to the store. See http://redux.js.org/docs/basics/UsageWithReact.html .
You can write the equivalent logic yourself for subscribing to the store and passing updated props to a component, but generally there's not a good reason to do so.
For future reference, I am going to add here an example of a working case in codepen not using babel neither the integrated version of jsx.
https://codepen.io/kanekotic/pen/LxbJNJ
Solution ;TL;DR
As commented before there is missing the redux connect
var mapstateToProps = function(state) {
return{
counter: state.counter
}
}
function mapDispatchToProps(dispatch){
return Redux.bindActionCreators(ActionCreators, dispatch);
}
var connectedApp = ReactRedux.connect(mapstateToProps,mapDispatchToProps)(APP)
and use then in the component connectedApp and no APP

Resources