Recently I've transitioned from using one optimistic action to adding two more to detect success/failure server responses.
With the optimistic approach I was able to just pass in my action the shorthand way and chain from the promise:
class Post extends Component {
onUpdateClick(props) {
this.props.updatePost(this.props.params.id, props)
.then(() => /* Action goes here */);
}
}
...
export default connect(mapStateToProps, { updatePost })(Post);
Now that I'm dispatching multiple actions and using mapDispatchToProps the action returns undefined.
Uncaught (in promise) TypeError: Cannot read property 'then' of undefined
What's going on here? Note that I'm using redux-promise.
function mapDispatchToProps(dispatch) {
return {
dispatch(updatePost(id, props))
.then(result => {
if (result.payload.response && result.payload.response.status !== 200) {
dispatch(updatePostFailure(result.payload.response.data));
} else {
dispatch(updatePostSuccess(result.payload.data));
}
});
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Post);
export function updatePost(id, props) {
const request = axios.put(`${ROOT_URL}/posts/${id}`, props);
return {
type: UPDATE_POST,
payload: request,
};
}
export function updatePostSuccess(activePost) {
return {
type: UPDATE_POST_SUCCESS,
payload: activePost,
};
}
export function updatePostFailure(error) {
return {
type: UPDATE_POST_FAILURE,
payload: error,
};
}
const initialState = {
activePost: { post: null, error: null, loading: false },
};
export default function(state = initialState, action) {
let error;
switch (action.type) {
case UPDATE_POST: {
return { ...state, activePost: { ...state.post, loading: true, error: null } };
}
case UPDATE_POST_SUCCESS: {
return { ...state, activePost: { post: action.payload, loading: false, error: null } };
}
case UPDATE_POST_FAILURE: {
error = action.payload || { message: action.payload.message };
return { ...state, activePost: { ...state.activePost, loading: false, error: error } };
}
}
}
The syntax of you mapDispatchToProps function seems to be incorrect.
It must returns an object containing methods as properties.
try to write something like that :
function mapDispatchToProps(dispatch) {
return {
updatePost() {
return dispatch(updatePost(id, props))
.then(result => {
if (result.payload.response && result.payload.response.status !== 200) {
return dispatch(updatePostFailure(result.payload.response.data));
}
return dispatch(updatePostSuccess(result.payload.data));
});
}
}
}
Related
Here i have my component code for SignIng Up user and check for Error. At first error is null.
let error = useSelector((state) => state.authReducer.error);
const checkErrorLoading = () => {
console.log("If error found"); //At first it gives null, but on backend there is error
toast.error(error);
console.log(loading, error);
};
const handleSubmit = async (e) => {
if (isSignup) {
dispatch(signup(form, history));
checkErrorLoading();
} else {
dispatch(signin(form, history));
checkErrorLoading();
}
};
Now at my singupForm, i provide wrong input or wrong data. The backend gives me error that is completely fine.
ISSUE => But when i click on Login button. At first attempt it does not provide any error message. After second attempt it works fine, but not at first attempt. At first attempt it gives me Error value NULL while there is still an error
Here is my action.
export const signup = (formData, history) => async (dispatch) => {
try {
const res = await api.signUp(formData);
dispatch({ type: authConstants.AUTH_REQUEST });
if (res.status === 200) {
const { data } = res;
console.log(data);
dispatch({
type: authConstants.AUTH_SUCCESS,
payload: data,
});
}
console.log(res.status);
history.push("/");
} catch (error) {
console.log(error.response);
dispatch({
type: authConstants.AUTH_FAILURE,
payload: error.response.data.error,
});
}
};
and than reducer.
const initialState = {
authData: null,
error: null,
loading: false,
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case authConstants.AUTH_REQUEST:
return { ...state, loading: true, error: null };
case authConstants.AUTH_SUCCESS:
localStorage.setItem("profile", JSON.stringify({ ...action?.payload }));
return { ...state, authData: action?.data, loading: false, error: null };
case authConstants.AUTH_FAILURE:
console.log(action.payload);
return { ...state, loading: false, error: action.payload };
}
You should use useEffect instead of local function (checkErrorLoading ) for such cases:
useEffect(() => {
console.log("If error found");
toast.error(error);
console.log(loading, error);
},[error]);
Currently what you doing is creating local function that closures error variable, which initially is null + state is updated asynchronously, so you cannot execute function right after dispatching (even if variable wouldn't be closured, you will not have fresh state there)
Bit of a weird one. In my component I am getting a "task" object from my "taskDetails" reducer. Then I have a useEffect function that checks if the task.title is not set, then to call the action to get the task.
However when the page loads I get an error cannot read property 'title' of null which is strange because when I look in my Redux dev tools, the task is there, all its data inside it, yet it can't be retrieved.
Here is the relevant code:
const taskId = useParams<{id: string}>().id;
const dispatch = useDispatch();
const history = useHistory();
const [deleting, setDeleting] = useState(false)
const taskDetails = useSelector((state: RootStateOrAny) => state.taskDetails);
const { loading, error, task } = taskDetails;
const successDelete = true;
const deleteTaskHandler = () => {
}
useEffect(() => {
if(!successDelete) {
history.push('/home/admin/tasks')
} else {
if(!task.title || task._id !== taskId) {
dispatch(getTaskDetails(taskId))
}
}
},[dispatch, task, taskId, history, successDelete])
REDUCER
export const taskDetailsReducer = (state = { task: {} }, action) => {
switch(action.type) {
case TASK_DETAILS_REQUEST:
return { loading: true }
case TASK_DETAILS_SUCCESS:
return { loading: false, success: true, task: action.payload }
case TASK_DETAILS_FAIL:
return { loading: false, error: action.payload }
case TASK_DETAILS_RESET:
return { task: {} }
default:
return state
}
}
ACTION
export const getTaskDetails = id => async (dispatch) => {
try {
dispatch({
type: TASK_DETAILS_REQUEST
})
const { data } = await axios.get(`http://localhost:5000/api/tasks/${id}`)
dispatch({
type: TASK_DETAILS_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: TASK_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message
})
}
}
In my reducer in the TASK_DETAILS_REQUEST case, I just had loading: false.
I had failed to specify the original content of the state, I did this by adding ...state.
export const taskDetailsReducer = (state = { task: {} }, action) => {
switch(action.type) {
case TASK_DETAILS_REQUEST:
return { ...state, loading: true }
case TASK_DETAILS_SUCCESS:
return { loading: false, success: true, task: action.payload }
case TASK_DETAILS_FAIL:
return { loading: false, error: action.payload }
case TASK_DETAILS_RESET:
return { task: {} }
default:
return state
}
}
For async requests, I'm using redux-saga.
In my component, I call an action to recover the user password, it its working but I need a way to know, in my component, that the action I dispatched was successfully executed, like this:
success below is returning:
payload: {email: "test#mail.com"}
type: "#user/RecoverUserPasswordRequest"
__proto__: Object
My component:
async function onSubmit(data) {
const success = await dispatch(recoverUserPasswordRequestAction(data.email))
if (success) {
// do something
}
}
My actions.js
export function recoverUserPasswordRequest(email) {
return {
type: actions.RECOVER_USER_PASSWORD_REQUEST,
payload: { email },
}
}
export function recoverUserPasswordSuccess(email) {
return {
type: actions.RECOVER_USER_PASSWORD_SUCCESS,
payload: { email },
}
}
export function recoverUserPasswordFailure() {
return {
type: actions.RECOVER_USER_PASSWORD_FAILURE,
}
}
My sagas.js
export function* recoverUserPassword({ payload }) {
const { email } = payload
try {
const response = yield call(api.patch, 'user/forgot-password', {
email
})
// response here if success is only a code 204
console.log('response', response)
yield put(recoverUserPasswordSuccess(email))
} catch (err) {
toast.error('User doesnt exists');
yield put(recoverUserPasswordFailure())
}
}
export default all([
takeLatest(RECOVER_USER_PASSWORD_REQUEST, recoverUserPassword),
])
In my reducer.js I dont have nothing related to recover the user's password, like a RECOVER_USER_PASSWORD_SUCCESS because like I said, the api response from my saga is only a code 204 with no informations
You should treat this as a state change in your application.
Add a reducer that receives these actions RECOVER_USER_PASSWORD_SUCCESS or RECOVER_USER_PASSWORD_FAILURE, then updates the store with information about request status. For example:
const initialState = {
email: null,
status: null,
}
const recoverPasswordReducer = (state=initialState, action) => {
//...
if (action.type === actions.RECOVER_USER_PASSWORD_SUCCESS) {
return {...initialState, status: True }
}
if (action.type === actions.RECOVER_USER_PASSWORD_SUCCESS) {
return {...initialState, status: False }
}
return state;
}
You can later have status as one of the fields selected in mapStateToProps when connect the component that needs to know about the status of the operation to the store.
function mapStateToProps(state) {
return {
/* ... other fields needed from state */
status: state.status
}
}
export connect(mapStateToProps)(ComponentNeedsToKnow)
The function below fetches a list of posts asynchronously and sends the received data to my app's Redux store.
The function handles both the fetching of the initial set of posts and that of subsequent posts that the user can trigger by clicking on a 'Load more' button.
export const fetchFilteredPosts = (filter, reset) => async(dispatch, getState, api) => {
if (reset) {
dispatch({
type: 'RESET_FILTERED_POSTS'
});
}
dispatch({
type: 'IS_FETCHING_FILTERED_POSTS'
});
try {
const currentPage = getState().filteredPosts.currentPage;
const nextPage = currentPage == 0 ? 1 : (currentPage + 1);
const filteredPosts = await api.get('/wp-json/wp/v2/posts?tag=' + filter + '&page=' + nextPage);
dispatch({
type: 'HAS_FETCHED_FILTERED_POSTS',
payload: {
data: filteredPosts.data,
currentPage: nextPage
}
});
} catch (error) {
dispatch({
type: 'FAILED_FETCHING_FILTERED_POSTS',
payload: error
});
}
}
Here's my Redux store:
import { filteredPostsPerPage } from '../config';
const initState = {
canFetchMore: false,
currentPage: 0,
data: null,
fetchingError: null,
isFetching: null,
perPage: filteredPostsPerPage
}
export default (state = initState, action) => {
switch (action.type) {
case 'IS_FETCHING_FILTERED_POSTS':
return {
...state,
isFetching: true,
fetchingError: false
}
case 'HAS_FETCHED_FILTERED_POSTS':
const posts = action.payload.data;
return {
...state,
data: state.data === null ? posts : state.data.concat(posts),
isFetching: false,
canFetchMore: posts.length >= state.perPage,
currentPage: action.payload.currentPage
}
case 'FAILED_FETCHING_FILTERED_POSTS':
return {
...state,
isFetching: false,
fetchingError: action.payload
}
case 'RESET_FILTERED_POSTS':
return initState;
default:
return state;
}
}
Suppose I have set 10 as the number of posts to display per page, and that the user has selected a category in which there are exactly 10 posts. If they're going to click on the Load More button, the app will throw this error:
{
"code": "rest_post_invalid_page_number",
"message": "The page number requested is larger than the number of pages available.",
"data": {
"status": 400
}
}
How can I listen for this exact error in the catch part of my function, so that I can display a message to the user, something like No more posts in this category? I guess I need to access the API request's response, but I'm not sure how to do that in this case.
You cant listen to a specific error, you have to listen for all.
You could use an if-statement:
try {
/* ... */
} catch (e) {
if (e.data.status === 400) {
/* handle your error */
} else {
}
}
Found it. This has something to do with using the Axios library, which I didn't mention I was using 'cause I didn't know that with Axios you need to work with error.response, not simply error. So if you use Axios you can catch the error as follows:
try {
/* ... */
} catch (error) {
if (error.response.data.status === 400) {
/* handle your error */
} else {
}
}
I try to implement a facebook login using react-native and redux but I'm face to a problem :
In my console, I have all information of the User but in the object for redux the authToken is undefined and I don't understand why ..
Here is my code
app/src/facebook.js
import {
LoginManager,
AccessToken,
GraphRequest,
GraphRequestManager,
} from 'react-native-fbsdk';
const facebookParams = 'id,name,email,picture.width(100).height(100)';
export function facebookLoginAPI() {
return new Promise((resolve, reject) => {
LoginManager.logInWithReadPermissions(['public_profile', 'user_friends', 'email'])
.then((FBloginResult) => {
if (FBloginResult.isCancelled) {
throw new Error('Login cancelled');
}
if (FBloginResult.deniedPermissions) {
throw new Error('We need the requested permissions');
}
return AccessToken.getCurrentAccessToken();
console.log(FBloginResult);
})
.then((result) => {
resolve(result);
console.log(result);
})
.catch((error) => {
reject(error);
console.log(error);
});
});
}
export function getFacebookInfoAPI() {
return new Promise((resolve, reject) => {
const profileInfoCallback = (error, profileInfo) => {
if (error) reject(error);
resolve(profileInfo);
};
const profileInfoRequest =
new GraphRequest(
'/me',
{
parameters: {
fields: {
string: facebookParams,
},
},
},
profileInfoCallback
);
new GraphRequestManager().addRequest(profileInfoRequest).start();
});
}
export function getFacebookFriends() {
return new Promise((resolve, reject) => {
const profileInfoCallback = (error, profileInfo) => {
if (error) reject(error);
console.log(profileInfo);
resolve(profileInfo);
};
const profileFriendsRequest =
new GraphRequest(
'/me/friends',
{
parameters: {
fields: {
string: facebookParams,
},
},
},
profileInfoCallback
);
new GraphRequestManager().addRequest(profileFriendsRequest).start();
});
}
the action (with all action types in another file)
import { facebookLoginAPI, getFacebookInfoAPI } from '../src/facebook';
import { getServerAuthToken } from '../src/auth';
import {
AUTH_STARTED,
AUTH_SUCCESS,
AUTH_FAILURE,
AUTH_ERROR,
AUTH_FAILURE_REMOVE,
LOGOUT
} from './types';
export function authStarted() {
return {
type: AUTH_STARTED,
};
}
export function authSuccess(facebookToken, facebookProfile, serverAuthToken){
return {
type: AUTH_SUCCESS,
facebookToken,
facebookProfile,
authToken: serverAuthToken,
};
}
export function authFailure(authError){
return {
type: AUTH_FAILURE,
authError,
};
}
export function authFailureRemove() {
return {
type: AUTH_FAILURE_REMOVE,
};
}
export function logout() {
return {
type: LOGOUT,
};
}
export function facebookLogin() {
return (dispatch) => {
dispatch(authStarted());
const successValues = [];
facebookLoginAPI()
.then((facebookAuthResult) => {
[...successValues, ...facebookAuthResult.accessToken];
return getFacebookInfoAPI(facebookAuthResult.accessToken);
}).then((facebookProfile) => {
[...successValues, ...facebookProfile];
return getServerAuthToken();
}).then((serverAuthToken) => {
[...successValues, ...serverAuthToken];
dispatch(authSuccess(...successValues));
}).catch((error) => {
dispatch(authFailure(error));
setTimeout(() => {
dispatch(authFailureRemove());
}, 4000);
});
};
}
And the reducer :
import {
AUTH_SUCCESS,
AUTH_FAILURE,
AUTH_STARTED,
AUTH_ERROR,
AUTH_FAILURE_REMOVE,
LOGOUT
} from '../actions/types';
const initialState = {
authenticating: false,
authToken: null,
authError: null,
facebookToken: null,
facebookProfile: null,
}
function authReducer(state = initialState, action) {
switch(action.type) {
case AUTH_STARTED:
return Object.assign({}, state, {
authenticating: true,
loginText: 'Connexion..'
});
case AUTH_SUCCESS:
return Object.assign({}, state, {
authenticating: false,
authToken: action.authToken,
facebookToken: action.facebookToken,
facebookProfile: action.facebookProfile,
});
case AUTH_FAILURE:
return Object.assign({}, state, {
authenticating: false,
authError: action.authError.message,
});
case AUTH_FAILURE_REMOVE:
return Object.assign({}, state, {
authError: null,
});
case LOGOUT:
return Object.assign({}, state, {
authenticating: false,
authToken: null,
facebookToken: null,
facebookProfile: null,
loginText: null,
});
default:
return state;
}
}
export default authReducer;
I need to understand what is the authToken, and why is he undefined in my case ? does the auth is success .. I don't know !
Thank you !
following code look little fishy to me
export function facebookLogin() {
return (dispatch) => {
dispatch(authStarted());
const successValues = [];
facebookLoginAPI()
.then((facebookAuthResult) => {
[...successValues, ...facebookAuthResult.accessToken]; //remove this line
return getFacebookInfoAPI(facebookAuthResult.accessToken);
}).then((facebookProfile) => {
[...successValues, ...facebookProfile]; //remove this seems of no use
return getServerAuthToken(); //I think you may need to pass something here
}).then((serverAuthToken) => {
[...successValues, ...serverAuthToken]; //pass this value in authSuccess below instead of ...successValues (it may still be [])
dispatch(authSuccess(...successValues));
}).catch((error) => {
dispatch(authFailure(error));
setTimeout(() => {
dispatch(authFailureRemove());
}, 4000);
});
};
}