is it possible to dispatch a redux function from a javascript object that is not part of react's component?
Usually when i wish to dispatch an action , i would do the following:
I'll map the dispatch function to the component's props :
const mapDispatchToProps = dispatch => {
return {
autoCheckState: () => dispatch(actions.authCheckState()),
}
}
And then i can just call the dispatch function like this:
async componentDidMount() {
await this.props.autoCheckState();
this.setState({
checking:false
})
}
However , now i wish to call the dispatch function from within my axiosinterceptor object , and they don't have "props"
Here is what i envisioned would be the way to use it , but ofcourse it does not work:
axiosInstance.interceptors.response.use(
response => {
return response
},error => {
######## bunch of code that is not relevant ##########
this.props.updateTokens(response.data.access,refreshToken) #<--- how i would call it if i based it off the first example
######## bunch of code that is not relevant ##########
return Promise.reject(error);
}
);
const mapDispatchToProps = dispatch => { ##<--- maping the dispatch to the 'props'
return {
updateTokens: () => dispatch(actions.authSuccess())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(axiosInstance);
Ofcourse the above would give me the error that this is not defined , since its not a class . May i know the correct way of using redux in such a situation?
You can make use of store.dispatch to dispatch an action from outside of your components or action creators
import store from '/path/to/store';
axiosInstance.interceptors.response.use(
response => {
return response
},error => {
store.dispatch(actions.authSuccess(response.data.access,refreshToken));
return Promise.reject(error);
}
);
Related
I got a React component which is trying to get some data, and is calling an onSuccess(result) call back upon a successful retrieval of data.
I need to save the data to redux. I created custom hooks which are using useDispatch, and I'm trying to do something like this:
<MyComponent onSuccess = {res => myCustomHook(res)} />
but I get an error because an hook can not be called inside a callback.
I know that hooks can only be called at the top level of a functional component.. So how can I achieve what I need?
The custom hook:
export function useSaveData(type, response)
{
if(!type|| !response)
{
throw new Error("got wrong parameters for useSaveData");
}
let obj= {
myData1: response.data1,
myData2: response.data2
};
sessionStorage.setItem(type, JSON.stringify(obj));
const dispatch = useDispatch();
dispatch(actionCreator.addAction(type, obj));
}
The parent component could pass dispatcher to useSaveData as follows.
export const useSaveData = (dispatch) => (type, response) =>
{
if(!type|| !response)
{
throw new Error("got wrong parameters for useSaveData");
}
let obj= {
myData1: response.data1,
myData2: response.data2
};
sessionStorage.setItem(type, JSON.stringify(obj));
dispatch(actionCreator.addAction(type, obj));
}
And parent component becomes;
function ParentComponent() {
const dispatch = useDispatch();
const myCustomHook = useSaveData(dispatch);
return <MyComponent onSuccess = {res => myCustomHook(ACTION_TYPE, res)} />
}
Your custom hook should return a function which can be passed as callback.
useMyCustomHook = () => {
// your hook
const onSuccess = () => {
// save data here
}
return { onSuccess };
}
In your component.
function MyComponent(props) {
const { onSuccess } = useMyCustomHook();
// your component code, you have onSuccess which can be bind with button or form as per your requirement.
}
Edit after seeing your custom hook.
You don't need to create custom hook here. you can simply pass dispatch to your callback.
const dispatch = useDispatch()
<MyComponent onSuccess={res => onSuccess(res, dispatch)} />
create onSucces function.
export function onSuccess(type, response, dispatch)
{
if(!type|| !response)
{
throw new Error("got wrong parameters for useSaveData");
}
let obj= {
myData1: response.data1,
myData2: response.data2
};
sessionStorage.setItem(type, JSON.stringify(obj));
dispatch(actionCreator.addAction(type, obj));
}
useDispatch is a React Redux hook, and can not be directly used anywhere outside of the functional component's body. You would not be allowed to even directly use it within another plain javascript function, even if it was getting invoked from the functional component's body. So, to use the useDispatch hook elsewhere, a const can be declared out of the useDispatch hook.Please note that, this should only happen from within your functional component like below:
export default function MyComponent {
const dispatch = useDispatch()
......
}
Now, this const dispatch can be passed around anywhere else, including a call back. Within your call back, you may access the passed dispatch argument and invoke redux APIs on it, like below:
export function useSaveData(type, response, dispatch)
{
if(!type|| !response)
{
throw new Error("got wrong parameters for useSaveData");
}
let obj= {
myData1: response.data1,
myData2: response.data2
};
sessionStorage.setItem(type, JSON.stringify(obj));
dispatch(actionCreator.addAction(type, obj));
}
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!
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())
}
}
If I make my own mapDispatchToProps function, it doesn't work. If I give a plain object for connect then it does work, but I need the dispatch functionality.. for eg loading of translations per page, Am I doing something wrong here?
const mapStateToProps = (state) => {
const { isFetching, lastUpdated, items, errors } = state.transactions; // fetch from redux state ;)
return {
translate: getTranslate(state.locale),
isFetching,
lastUpdated,
items,
errors
}
}
const mapDispatchToProps = dispatch => {
return {
fetchTransactionsIfNeeded,
invalidateList
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Transactions);
The code below works
const mapStateToProps = (state) => {
const { isFetching, lastUpdated, items, errors } = state.transactions; // fetch from redux state ;)
return {
translate: getTranslate(state.locale),
isFetching,
lastUpdated,
items,
errors
}
}
export default connect(mapStateToProps, {
fetchTransactionsIfNeeded,
invalidateList
})(Transactions);
According to the redux documentation
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it is
assumed to be a Redux action creator. An object with the same function
names, but with every action creator wrapped into a dispatch call so
they may be invoked directly, will be merged into the component’s
props.
If a function is passed, it will be given dispatch as the first
parameter. It’s up to you to return an object that somehow uses
dispatch to bind action creators in your own way. (Tip: you may use
the bindActionCreators() helper from Redux.)
In first case when you implement mapDispatchToProps, you are returning a plain object, but you need to use dispatch in it, since its not assumed as an action creator by itself by redux.
You would implement it like
const mapDispatchToProps = dispatch => {
return {
fetchTransactionsIfNeeded: (...args) => {
dispatch(fetchTransactionsIfNeeded(...args))
},
invalidateList: (...args) => {
dispatch(invalidateList(...args))
},
}
}
or else don't create it as a function but simply an object
const mapDispatchToProps = {
fetchTransactionsIfNeeded,
invalidateList
}
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.