I've got a bunch of actions that all work except for one. The setFriend function runs but for some reason it never makes a call to reducer and therefore the store does not update.
function mapDispatchToProps(dispatch){
return {
actions: {
authenticate: (userId, password, handler) => {
dispatch(AuthActions.authenticate(userId, password, handler));
},
register: (userId, password, userName, handler) => {
dispatch(AuthActions.register(userId, password, userName, handler));
},
updateGotUserSuccess: (user, isSuccess) => {
dispatch(AuthActions.updateGotUserSuccess(user, isSuccess));
},
setFriend: (friend) => {
debugger;
dispatch(FriendsActions.setFriend(friend));
}
}
};
}
All the functions listed above work, except setFriend which is the problem function. But when I debug, as shown below, it hits my first debugger but not the second one after the call to dispatch.
static setFriend(friend){
return (dispatch) => {
dispatch({type: FriendsActions.GOT_FRIEND_SUCCESS, payload: friend});
};
}
This is my reducer
export default function getFriendReducer(state = {}, action){
switch(action.type){
case FriendsActions.GOT_FRIEND_SUCCESS:
debugger;
return ObjUtils.cloneObj(action.payload);
case FriendsActions.GOT_FRIEND_FAIL:
debugger;
return ObjUtils.cloneObj(action.payload);
default:
return state;
}
}
Related
On a React page, I have a method called goOut. This method calls upon a Redux action > Node controller > Redux reducer. I can confirm that the correct data values are returned inside the Redux action, the controller method, and the reducer. However, nonetheless, at point 1 below inside the goOut method, it returns undefined.
What am I doing wrong / how could it return undefined if the the reducer is returning the correct values? It is as if the await inside the goOut method is not working...
React page:
import { go_payment } from "../../appRedux/actions/paymentAction";
<button onClick={this.goOut}>
Button
</button>
async goOut(ev) {
try {
const data = { user: parseInt(this.state.userId, 10) };
let result = await this.props.go_payment({data});
console.log(result);
// 1. RETURNS UNDEFINED. As if it tries to execute this line before it has finished the previous line.
{
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{go_payment}, dispatch
);
};
Redux Action:
export const go_payment = (data) => {
let token = getAuthToken();
return (dispatch) => {
axios
.post(`${url}/goController`, data, { headers: { Authorization: `${token}` } })
.then((res) => {
if (res.status === 200) {
// console.log confirms correct data for res.data
return dispatch({ type: GO_SUCCESS, payload: res.data });
})
}
}
Node controller method:
Returns the correct data in json format.
Reducer:
export default function paymentReducer(state = initial_state, action) {
switch (action.type) {
case GO_SUCCESS:
// console.log confirms action.payload contains the correct data
return { ...state, goData: action.payload, couponData: "" };
}
}
I am getting data from local storage and wanna dispatch a redux function.
But I think the action is not calling the try block in the function.
In the redux
export function testLogin(loginStatus) {
return async dispatch => {
try {
alert('me here')
dispatch({
type: TEST_LOGIN_STATUS,
payload: loginStatus,
});
} catch (error) {
console.log('not logged in');
}
};
}
export const authReducer = (state = initialState, action) => {
switch (action.type) {
case LOGGED_IN:
return {...state, token: action.payload, loggedIn: true};
case TEST_LOGIN_STATUS:
return {...state, loggedIn: action.payload};
default:
return state;
}
};
as you can see I am getting the status as param for testLogin action function.
Here is what I am doing in the Home screen.When user open the app. I need to test if the user is logged in or not by checking the local storage
useEffect(() => {
async function getStorageValue() {
let value;
try {
value = await AsyncStorage.getItem('isLoggedIn');
if (value === 'true') {
dispatch(testLogin(true));
} else {
dispatch(testLogin(false));
}
} catch (e) {
// handle here
} finally {
}
}
getStorageValue();
}, []);
Since Async storage accept only strings in am testing the value and returning true or false.
The thing is even if I am logged in. The when I check the redux loginStatus I am always logged out . Is there anything wrong with dispatch function?
It looks like testLogin is a higher order function. i.e. it returns a function that accepts dispatch as an argument.
In your useEffect try block try the following:
testLogin(value === 'true')(dispatch)
(replacing the if/else)
Correct me if I am wrong. But are you targeting the testLogin correctly?
For example I've seen this issue before and people simply forgot to target the Action correctly.
For example an object named LoginActions.
const LoginActions = {... testLogin(loginStatus) { ...} }
And then in the Code where I want to dispatch
import { LoginActions } from "../store/LoginActions" //<-- Import here
useEffect(() => {
async function getStorageValue() {
let value;
try {
value = await AsyncStorage.getItem('isLoggedIn');
if (value === 'true') {
dispatch(LoginActions.testLogin(true)) //<-- Dispatch here.
} else {
dispatch(LoginActions.testLogin(false));
}
} catch (e) {
// handle here
} finally {
}
}
getStorageValue();
}, []);
Again, I might have misunderstood but I was just wondering if this was the case?
I'm fetch some data from my API and it correctly works. But when a double dispatch on the same page the API doesn't work anymore. It's better code to explain it:
Server:
router.get("/", (req, res) => {
let sql = "SELECT * FROM design_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
router.get("/", (req, res) => {
let sql = "SELECT * FROM food_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
They work.
action.js
export const fetchDesignCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/designcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_DESIGN_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
export const fetchFoodCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/foodcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_FOOD_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
Both of them work perfectly.
reducer.js
const initalState = {
db: [],
loading: true,
designcat: [],
foodcat: [],
}
export default (state = initalState, action) => {
switch (action.type) {
// different cases
case FETCH_DESIGN_CAT:
return {
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
food: action.payload,
loading: false,
}
}
The reducer updates the states perfectly.
Page settings.js
const Settings = ({ designcat, foodcat, loading }) => {
const dispatch = useDispatch()
// ... code
useEffect(() => {
dispatch(fetchDesignCat()) // imported action
dispatch(fetchFoodCat()) // imported action
// eslint-disable-next-line
}, [])
// ... code that renders
const mapStateToProps = state => ({
designcat: state.appDb.designcat,
foodcat: state.appDb.foodcat,
loading: state.appDb.loading,
})
export default connect(mapStateToProps, { fetchDesignCat, fetchFoodCat })(
Settings
)
Now there's the problem. If I use just one dispatch it's fine I get one or the other. But if I use the both of them look like the if the second overrides the first. This sounds strange to me.
From my ReduxDevTools
For sure I'm mistaking somewhere. Any idea?
Thanks!
Your reducer does not merge the existing state with the new state, which is why each of the actions just replace the previous state. You'll want to copy over the other properties of the state and only replace the ones your specific action should replace. Here I'm using object spread to do a shallow copy of the previous state:
export default (state = initalState, action) => {
switch (action.type) {
case FETCH_DESIGN_CAT:
return {
...state, // <----
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
...state, // <----
food: action.payload,
loading: false,
}
}
}
Since the code is abbreviated, I'm assuming you're handling the default case correctly.
As an additional note, since you're using connect with the Settings component, you don't need to useDispatch and can just use the already connected action creators provided via props by connect:
const Settings = ({
designcat,
foodcat,
loading,
fetchDesignCat,
fetchFoodCat,
}) => {
// ... code
useEffect(() => {
fetchDesignCat();
fetchFoodCat();
}, [fetchDesignCat, fetchFoodCat]);
// ... code that renders
};
There's also a race condition in the code which may or may not be a problem to you. Since you start both FETCH_DESIGN_CAT and FETCH_FOOD_CAT at the same time and both of them set loading: false after finishing, when the first of them finishes, loading will be false but the other action will still be loading its data. If this case is known and handled in code (i.e., you don't trust that both items will be present in the state if loading is false) that's fine as well.
The solution to that would be either to combine the fetching of both of these categories into one thunk, or create separate sub-reducers for them with their own loading state properties. Or of course, you could manually set and unset loading.
So, I have this login() action that redirects the user to the feed if the login is successful. I also have this register() action that creates a new user and calls the login() action after.
The problem is that login() isn't receiving the props when called from register(), so I can't call the this.navigation.navigate('Feed') from there.
userActions.js
function register(user) {
return dispatch => {
dispatch(request(user));
let { username, password } = user
userService.register(user)
.then(
user => {
dispatch(success(user));
dispatch(login(username, password)) //calls login() after the user is created
},
error => {
dispatch(failure(error.toString()));
}
);
};
function request(user) { return { type: userConstants.REGISTER_REQUEST, user } }
function success(user) { return { type: userConstants.REGISTER_SUCCESS, user } }
function failure(error) { return { type: userConstants.REGISTER_FAILURE, error } }
}
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
this.navigation.navigate('Feed') //this is throwing "undefined is not an object" when login() is dispatched from register()
},
error => {
dispatch(failure(error.toString()));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
What am I missing here?
I think your code needs to be fixed a little.
It's not good solution to deal navigation in redux actions. To avoid this, Saga is recommended to be used with Redux.
However, with this your code, you didn't pass navigation property to action so you need to pass props variable to register action first.
Please try like this.
function register(user, props) {
...
dispatch(login(username, password, props))
...
function login(username, password, props) {
...
props.navigation.navigate('Feed')
Then navigation will work.
Hope this helps you to understand)
I recommend to use Redux and Saga as a stack according to my experience.
So I am trying to use redux together with the react-native-fbsdk package to login a user, but no mather how I go about it, the permissions always gets denied, even after granting them in the login screen. The console logs can be seen below:
Here you see my action for the authFailure:
export function authFailure(authError) {
return {
type: AUTH_FAILURE,
action.authError.message
}
}
Here is the function that gets executed onPress dispatching the action authStarted() and then calling the function _fbAuthAPI() which handles the fbsdk. This can be seen below this one.
export function _fbAuth() {
return (dispatch) => {
dispatch(authStarted())
const values = []
_fbAuthAPI().then((result) => {
values.concat(result.accessToken)
return _getUserInformationAPI(result.accessToken)
}).then((profile) => {
values.concat(profile)
dispatch(authSuccess(...values))
}).catch((error) => {
dispatch(authFailure(error))
setTimeout(() => {
dispatch(authFailureRemove())
}, 4000)
})
}
}
export function _fbAuthAPI() {
return new Promise((resolve, reject) => {
LoginManager.logInWithReadPermissions(['public_profile', 'email']).then((result) => {
if (result.isCancelled) {
throw new Error('Login was cancelled')
} else if (result.declinedPermissions) {
throw new Error('Permissions were declined')
} else {
return AccessToken.getCurrentAccessToken()
}
}).then((result) => {
resolve(result)
}).catch((error) => {
reject(error)
})
})
}
As for the reducer:
export default function authReducer(state = initialState, action) {
switch (action.type) {
case AUTH_STARTED:
return Object.assign({}, state, {
authenticating: true
})
break
case AUTH_SUCCESS:
return Object.assign({}, state, {
authenticating: false,
authError: false,
facebookToken: facebookToken,
facebookProfile: facebookProfile
})
break
case AUTH_FAILURE:
return Object.assign({}, state, {
authenticating: false,
authError: authError
})
break
...
default:
return state
}
}
Setup:
React Native 0.45.1
React Native FBSDK "^0.6.1"
Redux "^3.7.1"
MacOS Sierra 10.12.6
I can't help with the Facebook SDK and authentication, but the authError being undefined is because in this section, authError is truly undefined
case AUTH_FAILURE:
return Object.assign({}, state, {
authenticating: false,
authError: authError // Where is this coming from?
})
break
I think what you intended to have there is authError: action.authError
Okey, so I managed to solve the issue.
Basically, the when you call declinedPermissions on the result from the logInWithReadPermissions it always returns true, as it is an array.. Then, even if you dont have any declined permissions, it takes it as true.
The simple way to get around it, is just to see whats in the array and determine what to do based on that:
// Returns an empty array, therefore evaluates to true
if (result.declinedPermissions) {
throw new Error('Permissions were declined')
}
// The first index of the array is empty
if (result.declinedPermissions[0] === "") {
throw new Error('Permissions were declined')
}