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
Related
In my Login component i try to login on button click, corresponding function 'handleLogin' is then getting called. In this function i dispatch an async function with my user credentials as a payload. In my saga, i am making a request putting an error to store if the response got such field, else i set a user in there. By default the error field in store is 'false'. In my component where i dispatch an action, i want to know the state of error field in store right after the successfull/unsuccessful response. When i try to login in with wrong credentials and log the state of error into the console, i first get the 'old' (initial) value of error: false, instead of error: true. Only after second login try, the error get set to true. Is there a way to know the actual state of error in store, right after dispatching async action?
Reducer:
const initialState = {
userData: [],
error: false
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case SET_USER:
return {
...state,
userData: action.payload
}
case SET_ERROR:
return {
...state,
error: action.payload
}
default:
return state;
}
};
SAGAS:
function* handleUserLoad(payload) {
try {
const user = yield call(
loginUser,
payload
);
if(user.data.errors) {
yield put(setError(true))
} else {
yield put(setError(false))
yield put(setUser(user.data.data.loginUser));
}
} catch (error) {
console.log(error);
}
}
export default function* userSaga() {
while (1) {
const {payload} = yield take(GET_USER);
yield call(handleUserLoad,payload);
}
}
PART OF LOGIN COMPONENT:
const handleLogin = async() => {
setShouldValidate(true);
if (allFieldsSet) {
try {
loginUser(user)
// LOGIN ERROR HERE DISPLAYS THE PREVIOUS STATE 'false'
// INSTEAD OF ACTUAL TRUE AFTER loginUser
// IF I LOG WITH WRONG CREDENTIALS
console.log(loginError);
} catch (e) {
console.error(e);
}
}
};
loginUser(user) in your component is an async function, so you can't expect console.log(loginError); to log the value of loginError correctly in next line.
Solution:
First:
You can bind your login component to error state in redux. Once the state is modified by Saga based upon successful/unsuccesful response, your login component will re-render. So, in render if you will console.log then you may see the value of loginError coming correctly. So, in render only you can put the component what you want on the basis of "loginError" to be shown.
Second:
You can skip using Saga and directly await for your login API response(call API using fetch/axios from here itself) in Login Component at the current place itself. In that case you will be able to get the correct login response just after the await calls gets completed and than you can continue executing your code
I have made slight changes to default value of error in store. Now it is 'null' instead of false. And thus i can check if it was set to false/true in useEffect and do the other logic.
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())
}
}
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.
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.