Action must be plain objects. Use custom middleware for async actions - reactjs

I was trying make my react-native code prittier and I tried use Actions as new actions in following way:
actions:
import { Action } from "redux";
export const actionType = {
INCREMENT_ACTION: "example/INCREMENT_ACTION",
DECREMENT_ACTION: "example/DECREMENT_ACTION"
};
export class ExampleIncrementAction implements Action {
type = actionType.INCREMENT_ACTION;
}
export class ExampleDecrementAction implements Action {
type = actionType.DECREMENT_ACTION;
}
export type ExampleActions = ExampleIncrementAction | ExampleDecrementAction;
so reducer can looks like that:
export default function exampleReducer(
state = initialState,
action: ExampleActions
) {
switch (action.type) {
...
But now the strange errors appear: When I try use action in following way:
const increment = () => ({ type: 'example/INCREMENT_ACTION' });
const decrement = () => new ExampleDecrementAction();
const mapDispatchToProps = (dispatch: any) => {
return bindActionCreators({ increment, decrement }, dispatch);
};
calling the increment action is fine, but calling decrement action caused error: Action must be plain objects. Use custom middleware for async actions.
My question: Is there a way to use new class object as action? What I'm doing wrong?

Redux by design only accepts plain objects as actions to avoid various possible mistakes. For example passing a Promise as an action.
Is there a way to use new class object as action?
To use some class instances as actions you could either implement a custom middleware to overcome this limitation (this might lead to errors in future if someone say dispatches a Promise as action)
const useCustomActions = state => next => action => next({ ...action })
Or do it in action creator per action level
const decrement = () => ({...new ExampleDecrementAction()});

Related

How do I use Redux Toolkit with multiple React App instances?

We have written a React app using Redux via Redux Toolkit. So far so fine.
Now the React app shall be rendered into multiple different elements (each element shall get a new app instance) on the same page.
The rendering part is straight forward: We just call ReactDOM.render(...) for each element.
The Redux part again brings some headache.
To create a new Redux store instance for each app instance, we call the configureStore function for each React app instance. Our slices look similiar to this:
import { createSlice } from '#reduxjs/toolkit'
import type { RootState } from '../../app/store'
// Define a type for the slice state
interface CounterState {
value: number
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0,
}
const counterSlice = createSlice({
name: 'counter',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
}
},
});
export const increment = (): AppThunk => async (
dispatch: AppDispatch
) => {
dispatch(indicatorsOrTopicsSlice.actions.increment());
};
export const decrement = (): AppThunk => async (
dispatch: AppDispatch
) => {
dispatch(indicatorsOrTopicsSlice.actions.decrement());
};
// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value
export default counterSlice.reducer
Please note, that currently we create and export each slice statically and only once. Here comes my first question: Is this actually valid when creating multiple store instances or do we actually need to create also new slice instances for each app/store instance?
For the simple counter example provided, doing not so, seems to work, but as soon as we use an AsyncThunk as in the example below the whole thing breaks.
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)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], isLoading: false, hasErrors: false },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
builder.addCase(fetchUserById.pending, (state, action) => {
state.isLoading = true;
});
builder.addCase(fetchUserById.rejected, (state, action) => {
state.isLoading = false;
state.hasErrors = true;
});
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload);
state.isLoading = false;
state.hasErrors = true;
});
},
});
I believe the breaking starts here because of interdifferences between the events fired from dispatching the AsyncThunk.
Thereby I think the solution is to call the createAsyncThunk function for each app/store/slice instance. Are there any best practices for doing so? Of course this breaks the beauty and functionality of static exports and requires kind of a mapping, hence I'm asking.
My original suspicion that the AsyncThunk-part was responsible for the interferences between the stores of the different React app instances was wrong.
The source was something different not visible in the examples provided in my question.
We use memoized selectors via createSelector from reselect. Those were created and exported like the rest statically which in fact is a problem when working with multiple store/app instances. This way all instances use the same memoized selector which again doesn't work correctly thereby, since in the worst scenario the stored values of the dependency selectors are coming from the use from another store/app instance. This again can lead to endless rerenderings and recomputations.
The solution I came up with, is to create the memoized selectors for each app instance freshly. Therefore I generate a unique id for each app instance which is stored permanently in the related Redux store. When creating the store for an app instance I create also new memoized selectors instances and store them in a object which is stored in a static dictionary using the appId as the key.
To use the memoized selectors in our components I wrote a hook which uses React.memo:
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { selectAppId } from "../redux/appIdSlice";
import { getMemoizedSelectors } from "../redux/memoizedSelectors";
// Hook for using created memoized selectors
export const useMemoizedSelectors = () => {
const appId = useSelector(selectAppId);
const allMemoizedSelectors = useMemo(() => {
return getMemoizedSelectors(appId);
}, [appId]);
return allMemoizedSelectors;
};
Then the selectors can be used in the components like this:
function MyComponent(): ReactElement {
const {
selectOpenTodos,
} = useMemoizedSelectors().todos;
const openTodos = useSelector(selectOpenTodos);
// ...
}
and the related dictionary and lookup process would look like this:
import { createTodosMemoizedSelectors } from "./todosSlice";
/**
* We must create and store memoized selectors for each app instance on its own,
* else they will not work correctly, because memoized value would be used for all instances.
* This dictionary holds for each appId (the key) the related created memoized selectors.
*/
const memoizedSelectors: {
[key: string]: ReturnType<typeof createMemoizedSelectors>;
} = {};
/**
* Calls createMemoizedSelectors for all slices providing
* memoizedSelectors and stores resulting selectors
* structured by slice-name in an object.
* #returns object with freshly created memoized selectors of all slices (providing such selectors)
*/
const createMemoizedSelectors = () => ({
todos: createTodosMemoizedSelectors(),
});
/**
* Creates fresh memoized selectors for given appId.
* #param appId the id of the app the memoized selectors shall be created for
*/
export const initMemoizedSelectors = (appId: string) => {
if (memoizedSelectors[appId]) {
console.warn(
`Created already memoized selectors for given appId: ${appId}`
);
return;
}
memoizedSelectors[appId] = createMemoizedSelectors();
};
/**
* Returns created memoized selectors for given appId.
*/
export const getMemoizedSelectors = (appId: string) => {
return memoizedSelectors[appId];
};

How can I dispatch a change in the constructor?

I am trying to learn redux. I think I have reducers down pretty well, I can pull the data from the store and set it via props.
But I cannot wrap my head around actions and changing data on the state of the store.
I have this:
const mapDispatchToProps = (dispatch) => {
return{
what goes in here?
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentName);
I need to know how to dispatch a change inside the const. just a simple add text to an empty state. example: the state is apples:'', and I want to add 'red delicious' to that.
mapDispatchToProps provides you a way to connect your action creators to your components. Let's assume you have an action creator which increments a counter state
export const change = value =>({
type: 'CHANGE_FRUIT',
fruit : value
})
And you want value to be passed from one of your components. First use connect HOC in this component like you're already doing. Now you need to import incrementCounter from your actions folder
import { change as changeFruit } from './actions/fruit'
Now use mapDispatchToProps like this
const mapDispatchToProps = dispatch =>({
change : fruit => dispatch(changeFruit(fruit))
})
Now you have an action creator serialized inside your component's props and when you call props.increment(2) this will be the equivalent to call
dispatch(changeFruit('apple'))
Here is why you should always do props.increment instead of directly call dispatch inside your component.
So the full implementation inside your component could be like this
import { change as changeFruit } from './actions/fruit'
class Component extends React.component{
render(){
const { fruit, change } = this.props
return(
<>
<div>{fruit}</div>
<button onClick={() => change('orange')}>Change to orange</button>
</>
)
}
}
const mapStateToProps = state =>({
fruit : state.fruit
})
const mapDispatchToProps = dispatch =>({
change : fruit => dispatch(changeFruit(fruit))
})
export default connect(mapStateToProps, mapDispatchToProps)(Component)
Your reducer should look like this
const initialState = {
fruit: 'apple'
}
export const reducer = (state = initialState, action) =>{
switch(action.type){
case 'CHANGE_FRUIT' : return{
...state,
fruit : action.fruit
}
default : return state
}
}
An "action" is just a plain object that describes some sort of change you want to happen in your store. So in your case you want to add text to your string, such an action might look like:
{
type: 'ADD_TEXT',
value: 'red delicious'
}
An action creator is nothing more than a function that returns such a plain object. Notice we can generalise the action creator, so you can pass in the string to add, rather than have it hard coded as 'red delicious'.
const addText = value => ({ type: 'ADD_TEXT', value })
In order to 'send' this action to the reducers, it needs to be passed to dispatch function that Redux provides. E.g. this would dispatch the action:
const action = addText('red delicious') // Create the plain action object
dispatch(action) // Send it to the reducers
That can get kind of tedious to write out manually all the time, so mapDispatchToProps helps with that. It's a function you provide to connect that does the above all in one place, leaving the rest of your component code un-cluttered. Redux calls it internally to generate the props for your connected component. So when you do,
const mapDispatchToProps = dispatch => ({
addText: value => {
// Same code as above:
const action = addText('red delicious')
dispatch(action)
}
})
In your component you can call
props.addText('red delicious')
look when use the connect function in the app it takes 2 arguments the first is the state current and the second argument is the dispatch that specifies what type of action that will be implemented is the specific action the dispatch depended on the despatch will be called the specific action that link by reducer that implements in provider in-store when use
<Provider store={store}>
which the reducer that created by the const store=createStore(rootReducer)
const mapDispatchToProps = (dispatch) => {
return{
example look this is dispatch that well happened it will call specif action bu use key type
this == store.dispatch(constants.WIDITHROUP)
the first is key or name of the handle when you want to implement :(input that enters fro this dispatch and=> it call store.dispatch(name of Handel) )
widthroup: (balance) => dispatch({ type: constants.WIDITHROUP ,balance}),
deposit: (balance) => dispatch({ type: constants.DEPOSIT,balance }) }
}
}
Basically I am answering for the part where you have confusion:
return {
what goes in here?
}
Ideally, if you think as class concept, let's say you have a class that facilitates to choose and save your favorite color selection.
So, all you want is to create a class component and define a method saveSelection that should save selection to store. And, who ever wants to get that selection or see your selected colors they can access from store.
Back to Redux, the rule says, you can not update the state of store directly. You have to dispatch an action.
So, if I already have a decided to create a method saveSelection for that purpose, what should I do, so that it should dispatch an action.
May be something like:
class ColorSelector {
saveSelection () {
dispatch(action); //dispatch is some way to dispatch an action
}
}
But, action is not just a command or text or a value, we need to specify, the type of action (because reducer need to identify which data it need to update based on action) and the data I want to save. That will make sense of an action.
So, I want something like:
//I pass my selected color to saveSelection method and that should save
saveSelection(dataToUpdate) {
dispatch({type: actionType, data: dataToUpdate})
}
One thing may be going inside your head -> How will I get dispatch and how did I just use it here?
That will get clear in a sec, but first lets replace the dispatch arguments which we can write like:
saveSelectionAction(dataToUpdate) {
return {type: actionType, data: dataToUpdate}
}
And our method would be like:
saveSelection(dataToUpdate) {
dispatch(saveSelectionAction(dataToUpdate))
}
And saveSelectionAction becomes the action creator part. Now comes the dispatch part. So, lets wrap this method with another method which provides dispatch and expose saveSelection
anotherMethodWhichPassesDispatch(dispatch) {
// unleash the power of closure
return {
saveSelection: saveSelection(dataToUpdate) {
dispatch(saveSelectionAction(dataToUpdate))
}
}
}
or more correctly
// Method to map dispatching action to components props
anotherMethodWhichPassesDispatch(dispatch) {
// return list of your created action you want as props
return {
// Name it whatever you want and that will be a void method passing data
saveSelection: (dataToUpdate) => {
// pass the action creator's result which is defined above
// Or, exported from a separate file
dispatch(saveSelectionAction(dataToUpdate))
}
}
}
Now pass this method as argument to connect HOC and that will map saveSelection to a props and provide dispatch to your method.
connect(mapStateToProps, anotherMethodWhichPassesDispatch)(<container>) or we can rename it as mapDispatchToProps.
Now go back to your class's saveSelection method and do like:
saveSelection = (selectedColor) => {
this.props.saveSelection(selectedColor);
}
That's it.

React Redux Thunk trigger multiple actions on one call

I have an action which in turn must affect many other areas of my app state. In this case, when the user selects a website from a dropdown list, it must update many other components. I'm currently doing it like so:
setSelectedWebsite (websiteId) {
// The core reason for this component
this.props.setSelectedWebsite(websiteId);
// Fetch all associated accounts
this.props.fetchAllAccounts(websiteId)
// Several other "side effect" calls here...
}
In this interest of making one component serve one purpose, this feels like a bad practice.
What is the best practice for triggering multiple actions in one call from a component?
You could use redux-thunk.
Your component's method:
setSelectedWebsite(websiteId){
this.props.handleSetSelectedWebsite(websiteId) // this is a thunk
}
Your Redux file with action creators / thunks:
// this function is a thunk
export const handleSetSelectedWebsite = (websiteId) => (dispatch) => {
dispatch(setSelectedWebsite(websiteId))
dispatch(fetchAllAccounts(websiteId))
}
// these function are action creators (or could be other thunks if you style them the same way as the thunk above)
const setSelectedWebsite = (websiteId) => {
// your code
}
const fetchAllAccounts = (websiteId) => {
// your code
}
For handling complex side effects in a redux application, I would recommend looking at using Redux Sagas. I have seen its usage grow in popularity on projects large and small, and for good reason.
With sagas, in the example you have provided, you can emit a single action from a function provided through mapDispatchToProps and let a saga take care of the rest. For example: (following example assumes flux standard actions)
//import redux connect, react, etc
class SiteSelector extends React.Component {
render() {
const id = this.props.id;
return (
<button onClick={ () => this.props.action(id)>Click Me</button>
)
}
}
const mapStateToProps = (state) => ({
id: state.websiteId
})
const mapDispatchToProps = dispatch => ({
action: (id) => dispatch(setSelectedWebsite(id))
})
export connect(mapStateToProps, mapDispatchToProps)(SiteSelector)
Now you can handle the action emitted from setSelectedWebsite in a saga like so:
//import all saga dependencies, etc
export function* selectSite(action) {
const id = action.payload.id
yield put(Actions.selectWebsite(id))
const results = yield call(Api.fetchAllAccounts, id)
yield //do something complex with results
yield //do something else...
yield //and keep going...
}
// Our watcher Saga: spawn a new selectSite task every time the action from setSelectedWebsite is dispatched
export function* watchForSiteSelection() {
yield takeEvery('SITE_SELECTED_ACTION', selectSite)
}
For reference checkout the docs: Redux Sagas

How to make API calls using React, Redux in TypeScript

I'm using typescript-fsa and typescript-fsa-reducers packages to simply create actions and reducers in TypeScript React application.
const actionCreator = actionCreatorFactory();
export function signInHandler(state: UserState, action: Action): UserState {
// ????
return { ...state };
}
export const signIn = actionCreator.async<SignInRequest, RequestResponse<SignInResponse>>("USER_SIGNIN");
export const UserReducer = reducerWithInitialState({ signedIn: false } as UserState)
.casesWithAction([signIn.started, signIn.done], signInHandler)
.build();
Usage in component:
export default connect<StateProps, DispatchProps>(
(state: RootState) => ({} as StateProps),
(dispatch: Dispatch<RootState>) => {
return {
signIn: (userName: string, password: string) => dispatch(signIn.started(new SignInRequest(userName, password)))
};
}
)(SignIn);
And now I'm stuck. I don't know how to make HTTP calls to my API so I can send request when component dispatches action on dispatch next action when response from API arrives. I would like to use promises.
How to solve that?
In React without the typescript-fsa abstraction, you'd make async API callsat the action creator level, since actions are just dispatched POJOs and reducers are supposed to not have any side effects.
There are two projects that make it easy to do this, redux-thunk and redux-saga. I prefer redux-thunk because it is easier to wrap your head around. Basically your action creators get passed the dispatch function, and then they can be responsible for dispatching more than one thing... like so:
function asyncActionCreator(dispatch) {
dispatch(startAsyncAction());
doSomethingAsync()
.then(result => dispatch(completeAsyncAction(result))
.catch(err => dispatch(errorAsyncAction(err));
}
In your typescript-fsa world, there are some companion packages for both of these: typescript-fsa-redux-thunk and typescript-fsa-redux-saga.
It appears that typescript-fsa-redux-thunk takes a similar approach to the above example, using the concept of an "action worker", which coordinates the dispatching of actions via typescript-fsa. There is a really good example of doing this on the typescript-fsa-redux-thunk repo.

Handling Auth State using Redux

I have a chat-app that uses React, Redux and Firebase. I'm also using thunkmiddleware to do the async updates of the state with Firebase.
I successfully get everything I need, except that everything depends of a previously hard-coded variable.
The question is, how can I call inside my ActionCreators the getState() method in order to retrieve a piece of state value that I need in order to fill the rest of my states?
I currently have my auth: { uid = 'XXXZZZYYYY' }... I just need to call that like
getState().auth.uid
however that doesn't work at all.
I tried a lot of different questions, using mapDispatchToProps, etc. I can show my repo if needed.
Worth to mention that I tried following this other question without success.
Accessing Redux state in an action creator?
This is my relevant current code:
const store = createStore(
rootReducer,
defaultState,
applyMiddleware(thunkMiddleware));
And
function mapDispatchToProps(dispatch) {
watchFirebase(dispatch); // to dispatch async Firebase calls
return bindActionCreators(actionCreator, dispatch);
}
const App = connect(mapStateToProps, mapDispatchToProps)(AppWrapper);
Which I am exporting correctly as many other not pure functions work correctly.
For instance, this works correctly:
export function fillLoggedUser() {
return (dispatch, getState) => {
dispatch({
type: C.LOGGED_IN,
});
}
}
However as suggested below, this doesn't do a thing:
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};
In general your thunked action creator should look something like the below (I have used a post id as an example parameter):
const getPost = ( postId ) => ( dispatch, getState ) => {
const state = getState();
const authToken = state.reducerName.authToken;
Api.getPost(postId, authToken)
.then(result => {
// where postRetrieved returns an action
dispatch(postRetrieved(result));
});
};
If this is similar to what you have then I would log your state out and see what is going on with a simple thunk.
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};

Resources