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')
}
Related
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?
In a previous scenario to Update Profile values, i created a new store slice (updatedProfileDetails) and stored a new object there, but i think this isn't the best practice to solve my problem (since i have now two slices profileDetails & updateProfileDetails),
Now I found in redux documentation that we can make immutable update reducers to change data in the same slice without mutate states but in this scenario (set conversation unseen to false) , the API don't send an object as a response but just a success message,
SO, I'm trying to passe my conversation ID from Action To reducer to check it and change a single value of this conversation[id]
but i'm unable to make it correctly
My code:
1- Action: where I'm supposed to send id after action success
export const updateSeenConversation = (id) => async (dispatch, getState) => {
try {
dispatch({
type: SEEN_CONVERSATIONS_REQUEST,
})
const {
userLogin: { userInfo },
} = getState()
const config = {
headers: {
// headers
},
}
const { data } = await axios.put(
`${process.env.REACT_APP_API_KEY}/conversation/unseen/${id}`,
"",
config
)
// if success data : {success:true , message:"..."}
dispatch({
type: SEEN_CONVERSATIONS_SUCCESS,
payload: id, // usually i send data sent from API as dispatch payload,, to check for the exact conversation to update i tried to send id
})
} catch (error) { //error...})
}
}
2- my reducer:
export const conversationsListReducer = (
state = { conversations: {} },
action
) => {
// get conversations works Fine and return a slice conversations{..} that i need to update
switch (action.type) {
case GET_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case GET_CONVERSATIONS_SUCCESS:
return { loading: false, conversations: action.payload }
case GET_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
// here ERROR
// i cant access the exact value i want "unseen" **NB:CHECK store in IMAGE BELLOW**
case SEEN_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case SEEN_CONVERSATIONS_SUCCESS:
return {state.conversations.conversation.map((conversation) => {
if (conversation._id !== action.payload) {
return conversation // return without change if !== myid
}
return {
...conversation, // make a copy
unseen: false, // change this value to false/true
}
})}
case SEEN_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
case USER_LOGOUT:
return { conversations: {} }
default:
return state
}
}
Redux store Slice Image Link
Thank You.
Ok, I still don't see where in your post you say what isn't working but I'm gathering it is something in the SEEN_CONVERSATIONS_SUCCESS reducer case since that's where you focused some comments.
From what I can tell in this reducer case, you are mutating the state invariant from { conversations: {} } to { [] } since mapping returns an array, and isn't a valid object. When updating state you need to shallow copy the state object and also any nested state being updated.
case SEEN_CONVERSATIONS_SUCCESS:
return {
...state, // <-- shallow copy state
conversations: {
...state.conversations // <-- shallow copy state.conversations
conversation: state.conversations.conversation.map( // <-- shallow copy conversation array
conversation => conversation._id === action.payload
? {
...conversation, // <-- shallow copy conversation
unseen: false
}
: conversation
),
}
};
Since this reducer case is accessing nested state properties that are possibly undefined, you should also fully declare your state.
const initialState = {
conversations: {
conversation: [], // <--- now access this deep won't throw error
},
loading: false,
error: null,
};
export const conversationsListReducer = (
state = initialState,
action
) => {
...
case USER_LOGOUT:
return initialState; // <-- reset to initial state
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.
I think I understand where the error is occurring but I am able to work out the correct handling flow for a Promise returned from fetch()
My Messages reducer module: -
import { fetchMessages } from '_helpers/api'
import { Map, fromJS } from 'immutable'
const FETCHING_MESSAGES = 'FETCHING_MESSAGES'
const FETCHING_MESSAGES_FAILURE = 'FETCHING_MESSAGES_FAILURE'
const FETCHING_MESSAGES_SUCCESS = 'FETCHING_MESSAGES_SUCCESS'
const ADD_MESSAGES = 'ADD_MESSAGES'
const ERROR_MESSAGE = 'There has been an error'
export const fetchAndHandleMessages = () => {
return (dispatch, getState) => {
dispatch(fetchingMessages())
fetchMessages()
.then((r) => {
if (!r.ok) {
dispatch(fetchingMessagesFailure(ERROR_MESSAGE))
}else{
return r.json()
}
})
.then((b) => {
dispatch(fetchingMessagesSuccess(b))
})
.catch(() => {
dispatch(fetchingMessagesFailure(ERROR_MESSAGE))
})
}
}
function fetchingMessagesSuccess(messages) {
return {
type: FETCHING_MESSAGES_SUCCESS,
messages,
lastUpdated: Date.now(),
}
}
function fetchingMessagesFailure(errMsg) {
return {
type: FETCHING_MESSAGES_FAILURE,
error: errMsg
}
}
const fetchingMessages = () => {
return {
type: FETCHING_MESSAGES,
}
}
const initialState = fromJS({
messages: [],
isFetching: true,
error: '',
})
export const messagesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCHING_MESSAGES :
return state.merge({
isFetching: true,
})
case FETCHING_MESSAGES_SUCCESS :
return state.merge({
error: '',
isFetching: false,
messages: action.messages
})
case FETCHING_MESSAGES_FAILURE:
return state.merge({
error: action.error,
isFetching: false
})
default :
return state
}
}
export default messagesReducer
fetchMessages() simply returns a promise: -
export const fetchMessages = () => {
return fetch(baseUrl + 'messages')
}
I am not going to post the component code here because it is not relevant to the issue.
So if I call fetchMessages() with an invalid URL to return a 404, state.messages becomes undefined in my component. This would seem to be being caused by this part of the function: -
if (!r.ok) {
dispatch(fetchingMessagesFailure(ERROR_MESSAGE))
}else{
return r.json()
}
I think I might be confused regarding how to properly check and deal with potential errors in the returned Promise. According to the docs for fetch(), a 404 is not considered to be an error as (unlike regular AJAX) only network issues are considered to be a catch() type of error.
Can anyone pinpoint for me what is wrong with this part of my code? should I be using exit after dispatch(fetchingMessagesFailure(ERROR_MESSAGE)) to stop the following .then()? Also, even with just a 404, the .catch() block is also being run. This seems to be against what the docs suggest.
Any help greatly appreciated. Thanks.
I see you are using the same action on !r.ok and catch... so I would recommend to break the chain in case of !r.ok via throwing an error:
fetchMessages()
.then((r) => {
if (!r.ok) {
throw true; // just go to .catch()
}
return r.json()
})
.then((b) => dispatch(fetchingMessagesSuccess(b)))
.catch(() => dispatch(fetchingMessagesFailure(ERROR_MESSAGE)))
I am using React and Redux to create a login system with Google Firebase. I am trying to understand how to use thunks. I am calling my action createUser from my React component however, I'm not able to handle the callback successfully.
Here is the component function I am calling the action from:
registerUser() {
let email = this.state.user.email;
let pw= this.state.user.password;
this.props.actions.createUser(email, pw)
.then((user) => {
debugger; // The async request is successful but execution doesn't pause here
})
.catch((error) => {
debugger; // Instead I receive an error here that says, "Uncaught (in promise) RangeError: Maximum call stack size exceeded"
});
}
Here are the actions:
export function createUserSuccess(user) {
debugger;
return { type: types.CREATE_USER_SUCCESS, payload: { registeredUser: user, registerMsg: 'Successful Registration!' }};
}
export function createUserError(error) {
return { type: types.CREATE_USER_ERROR, payload: { registeredUser: {}, registerMsg: error.message }};
}
export function createUser(email, pw) { // async thunk
debugger;
return (dispatch) => {
debugger;
return firebase.auth().createUserWithEmailAndPassword(email, pw)
.then((user) => {dispatch(createUserSuccess(user))}) // todo: figure out why this won't resolve
.catch(error => dispatch(createUserError(error)));
}
}
And my Reducer:
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function registerReducer(state = initialState.registeredUser, action) {
debugger;
switch (action.type) {
case types.CREATE_USER_SUCCESS:
return [
...state, // es6 spread operator - explodes all values in array
Object.assign({}, action.payload)
];
case types.CREATE_USER_ERROR:
return [
...state,
Object.assign({}, action.payload)
];
default:
return state;
}
}
I know the actual request to Google firebase is OK because the createUserSuccess action creator gets fired. Why isn't execution stopping at the appropriate place in my React Component?
You can check here this implementation
The Service when we read the user auth and set the value to Redux
https://github.com/x-team/unleash/blob/develop/app/services/authService.js
The reducer when set the user state to the redux state object
https://github.com/x-team/unleash/blob/develop/app/reducers/userReducer.js
The action creators
https://github.com/x-team/unleash/blob/develop/app/actions/UserActions.js
The most important part is the authService, let me know any question