Dispatching actions in Redux best practices? - reactjs

I have a large project I am working on at work and wondering about the proper way to dispatch actions.
In my container for my component I map this function.
const mapDispatchToProps = (dispatch) => {
return {
ackMessage:(convo)=> {
chatSocketService.messageAcknowledge(convo, dispatch);
}
}
You can see I am passing the dispatch function to my Service.
I need to pass dispatch so in case the socket event fails I can dispatch an error action inside the service.
Is this ok to do? Or would it be better to always keep the dispatching in the containers. If I turned that socket service function into a promise I could do this then but then we may be adding too much logic to the dispatch function in the container?
Is passing the dispatch object around ok to do?
Edit: Here is a snippet of my socket service. On error event from my socket emit I need to dispatch an error:
const chatSocketService = {
messageAcknowledge(convo, dispatch) {
const socketConnection = getSocket();
socketConnection.emit(socketMessages.ACKNOWLEDGE_MESSAGE, {convoID:convo.convoID, msgID:convo.lastMsg.msgID },
(response)=> {
socketError(response, convo, dispatch);
});
}
}
const socketError = (response, convo, dispatch) => {
if (response.error === CHAT_SESSION_EXPIRE) {
sessionExpire(dispatch);
} else if(response.error) {
dispatch(convoError(convo.convoID, true));
}
};
const sessionExpire = (dispatch)=> {
dispatch(disconnectedMessage('Session has expired. Please log out and log back in'));
dispatch(socketDisconnected(true));
};
Then in my actions.js I have these actions:
export const CONVO_ERROR = 'CHAT_CONVO_ERROR';
export const convoError = (convoID, error) => ({
type:CONVO_ERROR,
convoID,
error
});
export const SOCKET_DISCONNECTED = 'CHAT_SOCKET_DISCONNECTED';
export const socketDisconnected = (disconnected)=> ({
type:SOCKET_DISCONNECTED,
disconnected
});

I think you should keep the dispatch function inside the container and separate out the async api call in a different file and import that function to use in this file. Also show us how you are making those async calls like chatSocketSevice using redux-thunk or redux-saga.. I feel like then I could be more helpful.

Related

where is dispatch coming from in this line? export const setAlert = (msg, alertType,timeout=5000) =>dispatch =>

I came across this code. He/she Is creating an action for redux in react-redux app. I don't understand where dispatch is coming from . Can you explain please also is this a good practice?
import uuid from 'uuid';
import {SET_ALERT,REMOVE_ALERT} from './types';
export const setAlert = (msg, alertType,timeout=5000) =>dispatch =>{
const id = uuid.v4();
dispatch ({
type:SET_ALERT,
payload:{msg,
alertType,
id}
});
setTimeout(()=>dispatch({type:REMOVE_ALERT,payload:id}),timeout)}
I guess it is using redux-thunk, which works as a middleware. setAlert returns the function, which is being called with the dispatch as first parameter of that function. It maybe helps more to understand if you clean it up a bit
export const setAlert = (msg, alertType,timeout=5000) => {
const id = uuid.v4();
return (dispatch) => {
dispatch ({
type:SET_ALERT,
payload:{
msg,
alertType,
id
}
});
};
I generally use it in cases I need to work with async functions inside an action, such as ajax requests.
It is coming from mapDispatchToProps:
// action
export const action = (params) => async (dispatch) => // it can be async btw
actionCodeHere()
// component
import {action as importedAction} from 'action'
const component = ({action}) => {
React.useEffect(() => { action(params) }, [])
return <div/>
}
const mapDispatchToProps = {
action: importedAction
} // just object
export default connect(null, mapDispatchToProps)(component)
Is it good practice?
I think it's better than many other ways I saw (redux saga especially, senseless and merciless), this has less code than when mapDispatchToProps is a function.
Easier is only to import dispatch from store directly in actions file and not using mapDispatchToProps at all, but I never saw this approach, seems like it is not good practice.

React Redux-Thunk with Typescript

So I am trying to type my Redux Actions with Redux Thunk
Here is an Action
export function logout() {
return async(dispatch) => {
try {
const value = localStorage.removeItem("token");
dispatch(cons.updateLoginStatus(false));
return true;
} catch (err) {
console.error(err);
}
}
}
Here is the connect file
const mapStateToProps = (store: IAppState) => {
return {
isLoggedIn: store.employee.isLoggedIn
}
}
const mapDispatchToProps = {
logout
}
export default connect(mapStateToProps, mapDispatchToProps)(Auth);
When I call await this.props.logout(); I would like to access the returned true But typescript only sees the function being returned.
So how do I type the function to await for returned value?
Or is there any better way to do this?
What you're attempting seems a bit, anti-pattern.
The function is meant to be fire and forget, so that you don't hang the UI.
The login function should dispatch to your reducer that state has now gone from
loggedIn: false
to
loggedIn: true
If you need some magic with response aka throw an exception if login fails, then I would recommend something like
this.props.login().catch(err => console.log(err)).then(() => doSomething));
However, I strongly recommend you just dispatch and listen to ComponentDidUpdate lifecycle hook to redirect or whatever you may need to do.
You need to invoke the logout function by dispatching the appropriate action

How to pass argument to React Redux middleware inside mapDispatchToProps

The situation is I am creating a single board which will hold a collection of note cards (each note has an id, title and body), and each note card will have a button to delete it. Also the application will be syncing with firebase, so my main question is how to pass arguments to middlewares AND do it inside of mapDispatchToProps. The following is my code to point out where my success with middleware and where I am currently blocked.
To hydrate the app on startup, I dispatch a middleware function that gets the data from firebase, and then dispatches actions handled by reducers and finally gets updated by the container/presentation component.
Middleware function:
export function hydrateApp(dispatch) {
dispatch({type: 'PENDING'});
fireBaseDBRef.once('value').then(snapshot => {
let firebaseNotes = snapshot.val()
let notes = [];
// populate notes using firebaseNotes, nothing exciting
dispatch({ type: 'DONE', notes: notes });
// the 'DONE' action.type is handled by the reducer and passes data
// to the container component successfully
}).catch(e => {
dispatch({type: 'ERROR', error: e});
});
}
Container component:
const mapStateToProps = state => {
return {
notes: state.boardReducer.notes
};
};
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
}
};
};
const BoardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(BoardPresentation);
So far so good, and this is what I added to the same middleware and container component files to handle delete scenarios.
Middleware function:
export function deleteNote(id) {
return (dispatch) => {
dispatch({type: 'PENDING'});
//firebase stuff happening here
dispatch((type: 'DONE'});
}
}
Container component:
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
},
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote(id));
}
};
};
The problem is that deleteNote gets called non-stop on startup, I don't even need to click the button.
I know the code presented may not make a whole bunch of sense, but the crux of my problem is that I need to some how pass an id to the middleware function when the user clicks on the button, and because I'm passing the function as a prop, it for some reasons decides to just call it a million times.
I could call boardMiddleware.deleteNote function inside the presentation component just like the examples in the official redux page do, but I'm wondering if there is a way of doing it the way I'm trying to do.
I also thought about binding the argument into the middleware function, but that also doesn't feel right, something like this
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote.bind(id));
}
Thanks for any help in advance!

Redux getState in action not working

I'm getting a bit confused with getState() in redux. I am using the thunk middleware.
I have an auth action which is an async action. But I have an action which runs before which checks if a token exists in state and if its still valid.
My problem is i can't seem to check the state when I have called the action. Thought I could just use getState but that doesn't seem to be a function.
container.js
componentDidMount() {
this.props.authCheck()
}
...
function mapDispatchToProps(dispatch) {
return {
authCheck: () => checkApiStatus()(dispatch)
}
}
Action.js
export const checkApiStatus = () => (dispatch, getState) => {
const expires_at = getState().api.expires_at
if (!expires_at || isDateGreater(expires_at)) {
// dispatch async action
dispatch(apiAuth())
}
return
}
Anyone have any ideas. Or perhaps better way of implementing something like this?
Thanks
The problem is you explicitly calling the returned function in you mapDispatchToProps method and passing only one argument. Instead call dispatch(checkApiStatus()) then redux-thunk will take care of passing the right arguments to the returned method. Should look like this
function mapDispatchToProps(dispatch) {
return {
authCheck: () => dispatch(checkApiStatus())
}
}

react-redux not dispatching thunk api call

I'm taking a working web version with redux and Api calls and porting them to a React Native app. However I notice when trying to dispatch a thunk to make an API call, I can't seem to see a console log in my thunk to confirm the dispatch. This makes me think something is not connected properly but I just don't see what that is. What am I missing?
I create a store with an initial state: When I log store.getState() everything looks fine.
const initialState = {
config: fromJS({
apiUrl: "http://localhost:3000/account-data",
})
}
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(thunk),
)
)
I use mapDispatchToProps and I see the functions in my list of props
export function mapDispatchToProps(dispatch) {
return {
loadProducts: () => dispatch(loadProducts())
};
}
However, when I inspect my loadProducts function, I do not see a console log confirming the dispatch. What's going on here? Why is loadProducts not dispatching? On the web version I'm able to confirm a network request and logs. On React Native I do not see a network request or these console logs.
export function loadProductsCall() {
console.log('in RN loadProductsCall') //don't see this
const opts = constructAxpOpts();
return {
[CALL_API]: {
types: [
LOAD_REQUEST,
LOAD_SUCCESS,
LOAD_FAILURE
],
callAPI: (client, state) =>
client.get(`${state.config.get('apiUrl')}/members`, opts),
shouldForceFetch: () => false,
isLoaded: state => !!(state.core.resources.products.get('productsOrder') &&
state.core.resources.products.get('productsOrder').length),
getResourceFromState: (state) => state.core.resources.products.toJS(),
isLoading: state => !!state.core.resources.products.get('isLoading'),
getLoadingPromise: state => state.core.resources.products.get('loadingPromise'),
payload: {}
}
};
}
export function loadProducts() {
console.log('in loadProducts') //don't see this
return (dispatch) =>
console.log('in loadProducts dispatched 2') //don't see this either
dispatch(loadProductsCall())
.then((response) => {
return response;
});
}
This code is missing custom API middleware that handles three action types. Also, in mapDispatchToProps a function is wrapping the dispatch. This function need to either be unwrapped and return a promise or called somewhere else in the code.

Resources