Dispatch in middleware leads to action with wrong type - redux

I'm trying to create a simple middleware to handle socket events.
const join = (channel) => (dispatch) => {
dispatch({
type: 'ACTION-1',
socketChannel: {...},
events: [...],
});
};
I dispatch this action that triggers it. And now when the dispatch method was called in my middleware with type 'ACTION-2' and received socketData as a payload, I see in my console what 'ACTION-1' was triggered twice and in the last time it is came with my socketData payload.
I wonder why 'ACTION-1' was registered instead 'ACTION-2' and how I can fix it? I would appreciate your help.
import { socket } from 'services/socket';
const socketMiddleware = ({ dispatch }) => next => (action) => {
const {
channel,
events, // an array of events for the channel
...rest
} = action;
if (typeof action === 'function' || !channel) {
return next(action);
}
const {
type,
name,
} = channel;
const channelInstance = socket.instance[type](name);
events.forEach((event) => {
const handleEvent = (socketData) => {
dispatch({ type: 'ACTION-2', socketData, ...rest });
};
channelInstance.listen(event.name, handleEvent);
});
return next(action);
};
export {
socketMiddleware
};

looks like you are not pathing the channel in your initial dispatch and you are failing your middleware finishes inside this if:
if (typeof action === 'function' || !channel) {
return next(action);
}
in order to fix this you should add channel in your dispatch:
const join = (channel) => (dispatch) => {
dispatch({
type: 'ACTION-1',
socketChannel: {...},
events: [...],
channel: { type: '...', name: '...' }
});
};

Related

Refactoring with createSlice reduxtoolkit

I'm having trouble refactoring with createSlice, I'm a beginner with redux-toolkit and have looked through the documentation but still having problems.if someone could point me in the right direction that would be fantastic. This is the working code
const SET_ALERT = 'setAlert';
const REMOVE_ALERT = 'alertRemoved';
export const setAlert =
(msg, alertType, timeout = 5000) =>
(dispatch) => {
const id = nanoid();
dispatch({
type: SET_ALERT,
payload: { msg, alertType, id },
});
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
};
const initialState = [];
export default function alertReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_ALERT:
return [...state, payload];
case REMOVE_ALERT:
return state.filter((alert) => alert.id !== payload);
default:
return state;
}
}
Your current setAlert action creator creates a thunk action (an action which takes dispatch as an argument) so it cannot be an action creator that is automatically generated by createSlice.
createSlice
You can keep the setup very similar to what you have now. You would have two separate actions for setting and removing an alert and a thunk for dispatching both. The underlying basic actions can be created with createSlice.
import { createSlice, nanoid } from "#reduxjs/toolkit";
const slice = createSlice({
name: "alerts",
initialState: [],
reducers: {
addAlert: (state, action) => {
// modify the draft state and return nothing
state.push(action.payload);
},
removeAlert: (state, action) => {
// replace the entire slice state
return state.filter((alert) => alert.id !== action.payload);
}
}
});
const { addAlert, removeAlert } = slice.actions;
export default slice.reducer;
export const setAlert = (msg, alertType, timeout = 5000) =>
(dispatch) => {
const id = nanoid();
dispatch(addAlert({ msg, alertType, id }));
setTimeout(() => dispatch(removeAlert(id)), timeout);
};
CodeSandbox
createAsyncThunk
This next section is totally unnecessary and overly "tricky".
We can make use of createAsyncThunk if we consider opening the alert as the 'pending' action and dismissing the alert as the 'fulfilled' action. It only gets a single argument, so you would need to pass the msg, alertType, and timeout as properties of an object. You can use the unique id of the thunk which is action.meta.requestId rather than creating your own id. You can also access the arguments of the action via action.meta.arg.
You can still use createSlice if you want, though there's no advantage over createReducer unless you have other actions. You would respond to both of the thunk actions using the extraReducers property rather than reducers.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export const handleAlert = createAsyncThunk( "alert/set", (arg) => {
const { timeout = 5000 } = arg;
return new Promise((resolve) => {
setTimeout(() => resolve(), timeout);
});
});
export default createReducer(initialState, (builder) =>
builder
.addCase(handleAlert.pending, (state, action) => {
const { alertType, msg } = action.meta.arg;
const id = action.meta.requestId;
// modify the draft state and don't return anything
state.push({ alertType, msg, id });
})
.addCase(handleAlert.fulfilled, (state, action) => {
const id = action.meta.requestId;
// we are replacing the entire state, so we return the new value
return state.filter((alert) => alert.id !== id);
})
);
example component
import { handleAlert } from "../store/slice";
import { useSelector, useDispatch } from "../store";
export const App = () => {
const alerts = useSelector((state) => state.alerts);
const dispatch = useDispatch();
return (
<div>
{alerts.map((alert) => (
<div key={alert.id}>
<strong>{alert.alertType}</strong>
<span>{alert.msg}</span>
</div>
))}
<div>
<button
onClick={() =>
dispatch(
handleAlert({
alertType: "success",
msg: "action was completed successfully",
timeout: 2000
})
)
}
>
Success
</button>
<button
onClick={() =>
dispatch(
handleAlert({
alertType: "warning",
msg: "action not permitted"
})
)
}
>
Warning
</button>
</div>
</div>
);
};
export default App;
CodeSandbox

shouldn't I dispatch an action inside a .then statement?

I found a code on git which I'm trying to understand and in the code the guy have this function:
export function startAddTodo(text) {
return (dispatch, getState) => {
const UID = firebase.auth().currentUser.uid;
const todo = {
text,
isDone: false,
isStarred: false
};
const todoRef = firebaseRef.child(`todos/${UID}`).push(todo);
dispatch(addTodo({
id: todoRef.key,
...todo
}));
todoRef.then(snapshot => {
return;
}, error => {
Alert.alert(JSON.stringify(error.message));
});
};
}
Why shouldn't it be like
const todoRef = firebaseRef.child(`todos/${UID}`).push(todo);
todoRef.then(snapshot => {
dispatch(addTodo({
id: snapshot.key,
...todo
}));
})
I think this because the promise may be rejected, but in the first code he may get an error when trying to call todoRef.key inside the dispatch method.

multiple dispatch in redux action

I wanted to dispatch an action from another action but not able to do so. When I try to do so it not able to found getAllUser method.
Below is my action class.
export const myActions = {
getAllUser() {
return (dispatch) => {
makeApiCall()
.then((response) => {
dispatch({
type: USER_SUCCESS,
payload: response,
});
})
.catch((error) => {
dispatch({
type: USER_FAILURE,
payload: error,
});
});
};
},
addUser(user) {
return (dispatch) => {
makeApiCall(user)
.then((response) => {
/*
Need help here :
wants to call above getAllUser()
.then(() =>
dispatch({
type: ADD_SUCCESS,
payload: response,
});
)
*/
};
},
};
I have tried various approaches like,
myActions.getAllUser()
.then((response) =>
dispatch({
type: ADD_SUCCESS,
payload: response,
});
);
and trying do dispatch directly,
const self = this;
dispatch(self.getAllUser());
dispatch({
type: ADD_SUCCESS,
payload: response,
});
One more way around this is after addUser success, update the reducer and than from UI call getAccount again to refresh the results, but just curious to know on how can I achieve this using multiple dispatch.
You can export the functions individually instead of wrapping it under the same object:
export const getAllUser = () => dispatch => { ... }
export const addUser = () => dispatch => {
...
dispatch(getAllUser());
}
You can still import them all if desired:
import * as myActions from '...';
Or you can declare getAllUser first then add to myActions, but the above solution is much cleaner.
const getAllUser = ...
const myActions = {
getAllUser,
addUser = ... { dispatch(getAllUser()) }
}

How to refactor redux + thunk actions/constants

In my react/redux/thunk application I use actions like:
function catsRequested() {
return {
type: CATS_REQUESTED,
payload: {},
};
}
function catsReceived(landings) {
return {
type: CATS_RECEIVED,
payload: landings,
};
}
function catsFailed(error) {
return {
type: CATS_FAILED,
payload: { error },
};
}
export const fetchCats = () => ((dispatch, getState) => {
dispatch(catsRequested());
return catsAPI.loadCats()
.then((cats) => {
dispatch(catsReceived(cats));
}, (e) => {
dispatch(catsFailed(e.message));
});
});
To deal with some data (simplified). Everything works but i have a lot of code for every data entity (and constants too).
I mean same functions for dogs, tigers, birds etc...
I see there are similar requested/received/failed action/constant for every entity.
What is right way to minify code in terms of redux-thunk?
You can keep your code DRY by creating a types and a thunk creators:
Type:
const createTypes = (type) => ({
request: `${type}_REQUESTED`,
received: `${type}_RECEIVED`,
failed: `${type}_FAILED`,
});
Thunk:
const thunkCreator = (apiCall, callTypes) => ((dispatch, getState) => {
dispatch({ type: callTypes.request });
return apiCall
.then((payload) => {
dispatch({ type: callTypes.received, payload }));
}, (e) => {
dispatch({ type: callTypes.failed, payload: e.message }));
});
});
Now you can create a fetch method with 2 lines of code:
export const fetchCatsTypes = createTypes('CATS'); // create and export the constants
export const fetchCats = (catsAPI.loadCats, fetchCatsTypes); // create and export the thunk
export const fetchDogsTypes = createTypes('DOGS'); // create and export the constants
export const fetchDogs = (dogsAPI.loadDogs, fetchDogsTypes ); // create and export the thunk
Note: you'll also use the types constant (fetchDogsTypes) in the reducers.

setInterval props don't update

i dispatch action in the code below. And in the action i have loggingIn === false, but this.props.loggingIn === true.
How to fix it?
component.js
updateProfile() {
return { user: Meteor.user(), loggingIn: Meteor.loggingIn() }
}
componentWillMount() {
this.props.loadUser(this.updateProfile());
debugger;
this.data = setInterval(() => {
if(this.props.loggingIn === true) {
this.props.loadUser(this.updateProfile());
}
}, 1000);
}
actions.js
export const loadUser = ({user, loggingIn}) => dispatch => {
dispatch({
type: 'USER_DATA',
payload: user
})
dispatch({
type: 'USER_LOGGING_IN',
payload: loggingIn
});
};
You should be using redux-thunk for async calls. Here is the tutorial for using redux-thunk
and an example code for handling user authentication in Meteor

Resources