I am trying to understand some socket and props behavior I'm seeing in my react-native app. I am using a redux store in this app. When the user manually signs out, I call the following:
if (this.props.authenticated) {
this.props.deauthenticateUser().then((res) => {
console.log('res: ', res); // {"payload": {"authenticated": false, "error": ""}, "type": "DEAUTHENTICATE_USER"}
console.log('this.props.authenticated 26: ', this.props.authenticated); // is true here, should be false
if (!this.props.authenticated) this.props.navigation.navigate('Login'); // Never happens because authenticated is still true
});
}
}
By the way, the redux section of that page/screen looks like this:
// Redux Mapping
const mapStateToProps = (state) => {
return { ...state.User };
};
const mapDispatchToProps = (dispatch) => ({
deauthenticateUser: async (user) => await dispatch(deauthenticateUser(user)),
});
export default connect(mapStateToProps, mapDispatchToProps)(LogoutScreen);
This is what the deauthenticateUser function looks like:
export const deauthenticateUser = () => {
console.log('deauthenticateUser() firing...'); // I see this
return async (dispatch) => {
await SocketDriver.Deauthenticate(); // This is successful
await LocalDataStore.Invalidate(); // This is successful
// Clear unauthorized application state
dispatch(clearOverview());
dispatch(clearSignature());
dispatch(clearUser());
console.log('finished clearing data...'); // I see this
return {
type: DEAUTHENTICATE_USER,
payload: { authenticated: false, error: '' },
};
};
};
This calls my User reducer, which looks like this:
case 'DEAUTHENTICATE_USER':
return { ...state, ...payload };
Even when I try a setTimeout to give plenty of time for deauthenticateUser() to complete, I find that authenticated never gets to a false state.
What am I potentially missing here? The logic seems to look solid to my eyes. Nothing bombs out, and when I console.log the data out at various points, it all looks as it should, EXCEPT for the props.authenticated property, which remains set to true.
Is it possible that my Logout screen doesn't get the final props update? And, if so, why would that be? Even when I try to call the deauthenticateUser() function multiple times -- just to test it -- I never see this.props.authenticated ever console.log as false.
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.
Hi guys I am quite new to programming and trying to understand one thing with redux and await/async functions. Basically I have this function:
//nh functions
const onSubmit = async data => {
try{
await dispatch(Login(data))
if (auth.logged != false){
addToast(content, { appearance: 'success', autoDismiss: true, })
history.push('/')
} else if (auth.logged == false){
addToast(content2, { appearance: 'error', autoDismiss: true, })
}
}finally{
console.log('Tada')
}
}
which should first authenticate an account and then push a notification. However, the await is not working at all, and it proceeds immediately to the if statement. Any tips?
Wht dave said is true. If you want to do something like that, you should dispatch your data and get the result in a props. Then you can use a useEffect to listen to this prop and do your things. Somethink like:
useEffect(() => {
// Do your things after your dispatch here
}, [yourProp]);
The "normal" pattern is to write asynchronous pseudo-actions: async function dispatching classical synchronous actions :
// reducer
// define 3 synchronous actions : loginActionBegin, loginActionSuccess,
// loginActionFailure which update your state at your convenience, for example setting a boolean flag to inform components that request is
// flying, or add user infos in store when loginActionSuccess is dispatched...
// async pseudo action
export const loginActionAsync = (loginInfos: any): any => {
return (dispatch: Dispatch, getState: any): any => {
dispatch(loginActionBegin());
return loginService.login(loginInfos)
.then(result: any): any => {
// request succeeded, add toast, user feedback...
dispatch(loginActionSuccess(result));
})
.catch((error: any) => {
// request failed, add toast, user feedback...
dispatch(loginActionFailure(error));
});
};
}
Then in a component:
// grab infos from store
const user = useSelector(state => state.user)
// on login form submit
dispatch(loginActionAsync({username:..., password:...}));
You will need async middleware to do so like https://github.com/reduxjs/redux-thunk
See :
https://redux.js.org/advanced/async-actions
https://www.digitalocean.com/community/tutorials/redux-redux-thunk
https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk
I'm assuming that auth is a prop that is mapped from the redux state to the component?
If so you could get around having to create an async functionality for the submit button and handling the redirect and/or state change when the component is updated with a new value from the store.
I would recommend this as you should rather be handling any error in the action itself, that way the component can be kept simple and mainly focuses on what to display.
useEffect(() => {
if (auth.logged != false){
addToast(content, { appearance: 'success', autoDismiss: true, })
history.push('/')
}
else if (auth.logged == false){
addToast(content2, { appearance: 'error', autoDismiss: true, })
}
}, [auth.logged]);
const onSubmit = data => dispatch(Login(data))
I'm trying to implement a login form for a website, it's my first project on React so I'm quite a beginner.
To do so, I use socket.io-client inside my redux reducer.
The thing is, it doesn't update the local props correctly.
Here's the code of my view:
const mapStateToProps = state => {
return {
profile: state.profileReducer.profile
}
}
const mapDispatchToProps = dispatch => {
return {
dispatch: action => {
dispatch(action)
}
}
}
...
handleConnection = () => {
const { profile } = this.props
this.props.dispatch({ type: 'CONNECT_USER' })
}
...
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage)
And here's the reducer's action:
import io from 'socket.io-client'
const host = [SERVER_URL]
const socketConnection = io.connect(host, {path: [PATH], secure: true})
const initialState = {
profile: {
token: null,
username: '',
password: ''
}
}
function profileReducer(state = initialState, action) {
switch(action.type) {
...
case 'CONNECT_USER':
let tempProfile = {...state.profile}
socketConnection.emit('login', tempProfile.username + ';' + tempProfile.password)
socketConnection.on('check', msg => {
if (msg !== null && msg !== '')
tempProfile.token = msg
return {
...state,
profile: tempProfile
}
})
return state
...
}
}
The 'check' socket action return a message containing the user connection token which I need to store to make sure the connection is done and allowed.
The thing is, it doesn't update the store value. If I update directly the reducer's state instead of the temporary profile, it partly works : the view props isn't properly updated but a 'console.log(profile)' in a 'setInterval' inside the 'handleConnection' function shows the token value (but the props inside the Chrome React Inspector isn't updated).
I really don't understand what's going on. I suppose the socket.io 'on' function isn't done before the 'return' of my action but I don't know how to handle it.
Does someone as any idea how I could solve this problem ?
Thanks !
Reducers are always synchronous in nature. If you want to perform an async operation (like the socket connection you are trying to establish) in your reducer then you need to use a middleware like Thunk or Saga to achieve the same.
In your case it is always returning the existing state from the last return statement.
I have simple flow in my app. After request is finished i'm calling an action with a payload:
export const setVoteFlagCurrentPoll = () => dispatch => {
dispatch({
type: SET_VOTE_FLAG_CURRENT_POLL,
payload: true
})
}
Then in reducer im changing one variable in a poll object which looks like this:
{
idsurvey: 10,
topic: "random poll question",
answers: Array(4),
voted: false
}
Reducer itself:
case SET_VOTE_FLAG_CURRENT_POLL:
return {...state, pollData: {...state.pollData, voted: action.payload}};
My issue is that variable 'voted' is not chaning its value. Its still the same which is 'false'. Interesting thing is I can just log that recuder as: console.log({...state, pollData: {...state.pollData, voted: action.payload}}); and it works.. its logging with voted as true. Why is this happening?
Ok, I figured it out. It seems like mapStateToProps function was badly written..
before (not working):
const = ({user}) => ({user}); // its returning whole user reducer
after (working now):
const mapStateToProps = state => {
return {
user: state.user //same as above (I didnt want to change the code in other components..)
pollData: state.user.pollData //pulling pollData directly
}
}
Hope it helps.
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.