Adding an error state with Axios and Redux - javascript

I'm having a bit of trouble getting my application to work how I'd like it. I'm fairly new to React and Redux so bear with me.
Right now I can call this.props.fetchData('usernamehere') to fetch some data about a user, when it's successful it updates this.props.profile which I use to update the React component. The problem with this is handling errors.
I've tried to handle this by adding a FETCH_DATA_ERROR reducer, but the problem I'm having is that when it 404's it still replaces this.props.profile with an empty object instead of just updating this.props.error. Ideally I want to keep the current profile until it successfully finds a new one, and then show the user what the error was with the username they entered by updating this.props.error.
Right now I have the following action creator setup:
import axios from 'axios';
import { FETCH_DATA, FETCH_DATA_ERROR } from './types';
export const ROOT_URL = 'https://api.github.com/users'
export function fetchData(user) {
const request = axios.get(`${ROOT_URL}/${user}`)
.catch(function (error) {
return {
type: FETCH_DATA_ERROR,
payload: error
}
});
return {
type: FETCH_DATA,
payload: request
}
}
And reducer:
import { FETCH_DATA, FETCH_DATA_ERROR } from '../actions/types';
const INITIAL_STATE = { profile: null, error: null }
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case FETCH_DATA:
return { ...state, profile: action.payload.data };
case FETCH_DATA_ERROR:
return { ...state, error: action.payload };
default:
return state;
}
}
If anyone has any suggestions they would be greatly appreciated. I feel like I'm on the right path but can't seem to figure out where I'm going wrong.

So far you have an action that signals the beginning of the request (FETCH_DATA) and one that signals that the request failed (FETCH_DATA_ERROR). Typically this is modelled with a third one, that signals that the request resulted in a positive response (maybe FETCH_DATA_SUCCESS).
You would need to rewrite your action creator using something like https://github.com/gaearon/redux-thunk so that it first dispatches only FETCH_DATA, and then in the then/catch-handlers of axios.get you dispatch the success or failure actions:
import { FETCH_DATA, FETCH_DATA_SUCCESS, FETCH_DATA_ERROR } from '../actions/types';
// ASYNC ACTION CREATOR
export const fetchData = user => (dispatch) => {
dispatch({
type: FETCH_DATA
});
return axios.get(`${ROOT_URL}/${user}`)
.then(response => dispatch({
type: FETCH_DATA_SUCCESS,
payload: response
}))
.catch(error => dispatch({
type: FETCH_DATA_ERROR,
payload: error
}));
};
// REDUCER
const INITIAL_STATE = {
profile: null,
error: null
};
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
// Start of request - discard old data and reset old errors.
case FETCH_DATA:
return {
// It's important to set all of these to properly model the request lifecycle
// and avoid race conditions etc.
profile: null,
error: null
};
// End of request - save profile and signal that there was no error.
case FETCH_DATA_SUCCESS:
return {
profile: action.payload.data,
error: null
};
// End of request - discard old profile and save error, to display it to the user for example.
case FETCH_DATA_ERROR:
return {
profile: null,
error: action.payload
};
default:
return state;
}
}

Related

How do I pass a response data I am getting back from createasyncthunk in redux toolkit to a component?

I have a login component, a verification component and a userSlice file which has createAsyncThunk.
userSlice.js:
export const loginUser = createAsyncThunk(
'users/login',
async ({ email, password }, thunkAPI) => {
try {
const response = await fetch(
'my_url',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
}),
}
);
let login_response = await response.json();
console.log('response login', login_response);
if (response.status === 200) {
console.log('success from here')
// localStorage.setItem('token', data.token);
return login_response;
} else {
return thunkAPI.rejectWithValue(data);
}
} catch (e) {
console.log('Error', e.response.data);
thunkAPI.rejectWithValue(e.response.data);
}
return login_response;
}
);
Here I get a response back when I post data. I have named it as login_response.
I simply need to pass it to my verification component.
I searched for it online but I did not understand the solutions which were there.
In my verification component I have imported loginUser like so
import { loginUser } from '../features/user/userSlice';
I know that loginUser has returned login_response. Now how do I access login_response in the verification component ?
I guess you can access it using :
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(loginUser.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(loginUser(123))
according to doc at https://redux-toolkit.js.org/api/createAsyncThunk :
createAsyncThunk returns a standard Redux thunk action creator. The thunk action creator function will have plain action creators for the pending, fulfilled, and rejected cases attached as nested fields.
const usersSlice = createSlice({
name: 'users',
initialState: {
userData: []
},
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
builder.addCase(loginUser.fulfilled, (state, action) => {
// Add user to the state
state.userData.push(action.payload)
})
},
})
now you can access it in your verification component
do not forger to export slice and add it to your store
and then in verification component:
const user_data = useSelector(state => state.users.userData)
console.log it to verify if you are getting right data.

Passing the return value of a useAppSelector to a variable in React-Redux

I might have missed something super obvious when refactoring my implementation of Redux in a React application, but when I'm trying to access the value of one of my slices I get thrown some errors by the Typescript Compiler about not being able to assign a (func) => string to a parameter of type string.
For context, here's my implementation:
Slice:
export const environmentSlice = createSlice({
name: 'environment',
initialState,
reducers: {
updateEnvironment: (state, action:PayloadAction<string>) => {
state.value = action.payload
}
}
});
export const { updateEnvironment } = environmentSlice.actions;
export const selectEnvironment = (state: RootState) => state.environment.value;
How i've defined the interface for my environment:
// Defining type for state
interface EnvironmentState {
value: string,
};
// define the initial state using that type
const initialState: EnvironmentState = {
value: 'live',
}
How RootState is defined in my store:
export const store = configureStore({
reducer: {
loggedIn: loggedInReducer,
environment: environmentReducer,
token: tokenReducer,
},
})
export type RootState = ReturnType<typeof store.getState>;
How I'm trying to get the value into one of my React Components:
let environment = useAppSelector((state: RootState) => {
return state.environment.value
});
I've also tried following the implementation in the redux docs here but had no luck with that: https://react-redux.js.org/tutorials/typescript-quick-start#use-typed-hooks-in-components
When assigning this value, i'm using useAppDispatch() assigned to a variable inside of the response section of a fetch request:
fetch('/api/authenticate', requestOptions)
.then(async response => {
if (response.status === 200) {
let data = await response.json();
dispatch({ type: toggle });
dispatch({ type: updateToken, payload: data.token });
webHelpers.get('/api/user', 'default', 'auth', data.token, (data: any) => {
dispatch({ type: updateUser, payload: data.full_name })
});
//
navigate('../management/staff');
Please note: The environment isn't updated upon sign-in but only once the user selects an option from a drop-down menu in the DOM. It's directly after this sign-in and navigation that the application crashes, however, as it states it cannot read the 'value' on the following:
const token = useAppSelector(state => {
return state.token.value
});
The above is reached after the navigate('../management/staff'); is called.
Edit: Accidently included wrong code snippet when showing useAppSelector in use. Update to fix.
Edit2: Added in section about the dispatches that assigns these values.
Edit3: Managed to resolve the solution but not in the exact way I'd hoped so I'll leave this open. The issue appeared to be that the attempts to dispatch data via the slices I'd added to my store's reducer didn't work, having all of those methods on one sole slice resolved the issue. This isn't ideal as I'd wanted 3 separate slices to manage each of these states separately. There must be some issue in my redux store with setting these up to work independently.

Unhandled Rejection (TypeError): state.recipes is undefined

I am just trying to delete an item on my page. When I delete the item I get this Unhandled Rejection (TypeError): state.recipes is undefined message pointing to my reducer. When I refresh my page, the object is gone and the error disappears. The question is what is causing this error prior to the item deleting?
This is what happens after I click delete button, when I refresh the page the object is gone.
case 'DELETING_RECIPE_START':
return {
...state.recipes,
loading: true
}
case 'DELETE_RECIPE_SUCCESS':
This line -----> const recipes = state.recipes.filter(recipe => recipe.id !== action.payload.recipeId)
return {
...state, recipes,
loading: false
}
I was told in this case is to check your delete action on the backend. When I plugged in byebug, It is showing me which object am trying to delete, so hopefully its nothing there I need to worry about.
def destroy
recipe = Recipe.find(params[:id])
unless recipe.nil?
recipe.destroy
render json: recipe
else
render json: { error: "Property not found" }, status: 404
end
end
I did modify my delete action to the thunk asynchronous conventions, and I hope it's structured correctly. I will note when I run debugger before the return(dispatch) this issue with my error seems to happen after the return(dispatch) line.
export const deleteRecipe = (recipeId) =>{
const BASE_URL = `http://localhost:3001`
const RECIPES_URL =`${BASE_URL}/recipes`
debugger
return (dispatch) => {
dispatch({ type: "DELETING_RECIPE_START" })
fetch(`${RECIPES_URL}/${recipeId}`,{method: 'DELETE'})
.then(response =>{return response.json()})
.then(recipeId => dispatch({ type: 'DELETE_RECIPE_SUCCESS', payload: recipeId }))
.catch((error) => console.log.error(error))
};
}
Last here is my Recipe component with the delete button and the event handler associated.
class Recipe extends Component {
handleOnClick(){
this.props.deleteRecipe(this.props.recipe.id);
}
render(){
return(
<div>
<h3>Name: {this.props.recipe.name}</h3>
<p>Category:{this.props.recipe.category_id}</p>
<p>Chef Name: {this.props.recipe.chef_name}</p>
<p>Origin: {this.props.recipe.origin}</p>
<p>Ingredients: {this.props.recipe.ingredients}</p>
<button onClick={()=>this.handleOnClick()}>Delete</button>
</div>
)
}
}
export default Recipe
What can I do to correct this?
For those interested in the solution. I credit my cohort lead for this. There was some restructuring involved.
When debugger is placed in my it’ll indicate that a key is not provided for recipes…well here is what it meant.
My DELETE_RECIPE_START case was like this at first
case 'DELETING_RECIPE_START':
return {
...state.recipes,
loading: true
}
It needed to look likes this
case 'DELETING_RECIPE_START':
return {
recipe:[...state.recipes],
loading: true
}
The recipe is the key while its current state is the value
The next part blew my mind…The delete action did not need a json response. You are only telling it to delete an id and that's it.
export const deleteRecipe = (recipeId) =>{
const BASE_URL = `http://localhost:3001`
const RECIPES_URL =`${BASE_URL}/recipes`
return (dispatch) => {
fetch(`${RECIPES_URL}/${recipeId}`, { method: 'DELETE' })
.then(() => {
return dispatch({ type: 'DELETE_RECIPE_SUCCESS', payload: recipeId })
});
};
}
I am really trying to get better at this but I enjoy the fact that I am learning.

React/Redux handling API server errors in reducer to display on UI

I have created an uploader to upload a CSV file, this gets converted into json and sent to the API.
The API has validation if the data is not valid and it returns a response I can see in my debugger:
{"success":false,"errorCode":"880ddb963e40","errorMessage":"There are Ids which do not exist in system"}
My UI outputs a generic message that there was an issue as shown below from my reducer:
import {
PUT_UPLOAD_CSV,
PUT_UPLOAD_CSV_SUCCESS,
PUT_UPLOAD_CSV_FAILURE
} from 'constants/BulkUploads/ActionTypes';
const INIT_STATE = {
uploadLoader: false,
uploadResponse: '',
uploadError: ''
}
export default (state = INIT_STATE, action) => {
switch (action.type) {
case PUT_UPLOAD_CSV: {
return {
...state,
uploadLoader: true,
uploadResponse: '',
uploadError: ''
}
}
case PUT_UPLOAD_CSV_SUCCESS: {
return {
...state,
uploadLoader: false,
uploadResponse: 'CSV file uploaded successfully',
uploadError: ''
}
}
case PUT_UPLOAD_CSV_FAILURE: {
return {
...state,
uploadLoader: false,
uploadResponse: '',
uploadError: 'An error occurred uploading CSV file, please check the data and try again.'
}
}
default:
return state;
}
}
I want my reducers uploadError handle to be able to return the APIs errorMessage rather than my default one so that the error is more specific, and/or use the error code as a lookup to display an appropriate message.
If anybody can assist or point me to some documentation.
UPDATE - adding my actions
import {
PUT_UPLOAD_CSV,
PUT_UPLOAD_CSV_SUCCESS,
PUT_UPLOAD_CSV_FAILURE,
} from 'constants/BulkUploads/ActionTypes';
export const putUploadCSV = (payload) => {
return {
type: PUT_UPLOAD_CSV,
payload
};
};
export const putUploadCSVSuccess = (payload) => {
return {
type: PUT_UPLOAD_CSV_SUCCESS,
payload
}
};
export const putUploadCSVFailure = (payload) => {
return {
type: PUT_UPLOAD_CSV_FAILURE,
payload
};
};
My index.js
{uploadError ?
{uploadError}
:
{uploadResponse}
}
SAGA
function* putUploadCSVRequest(params) {
try {
const response = yield call(putUploadCSV, params.payload);
yield put(putUploadCSVSuccess(response));
} catch (error) {
console.error(error);
yield put(putUploadCSVFailure(error));
}
}
You are only using action.type in the given function. You may very well use another key preferably something like action.data or action.payload to identify the different types of PUT_UPLOAD_CSV_FAILURE action and adjust the reducer accordingly.
To give you an exact solution based on your specific scenario, I might need to understand how you are dispatching actions.
Update:
Since you are using action creators putUploadCSVFailure, you can create the action as
putUploadCSVFailure(response.errorMessage)
within the catch or response of your HTTP Client (axios? fetch? xhr?)
Now, the payload is the errorMessage, so update reducer as
case PUT_UPLOAD_CSV_FAILURE: {
return {
...state,
uploadLoader: false,
uploadResponse: '',
uploadError: action.payload || 'An error occurred uploading CSV file, please check the data and try again.'
}
}
Since I am not sure how you're invoking the action, I can give you a hint to help you with your problem.
You can pass the message parameter to the action and then consume it in your reducer.
Action call:
this.props.onUploadError({type: PUT_UPLOAD_CSV_FAILURE, errorMessage: jsonData.errorMessage});
Reducer case statement modification:
case PUT_UPLOAD_CSV_FAILURE: {
return {
...state,
uploadLoader: false,
uploadResponse: '',
uploadError: action.errorMessage
}
}
You can add check that if api sends error then show api error or show custom error. e.g.
const customError = 'An error occurred uploading CSV file, please check the data and try again.'
// in the reducer, assuming action.payload has response
uploadError: action.payload.errorMessage || customError

Successful PUT request, but React, Redux app crashes

I have a React, Redux app which should work as a CRUD application. And a part of a CRUD application is the ability to update stuff and that's what I currently have trouble with. The PUT request works (can see the changes in Robomongo), but my app crashes afterwards and the problem lies in my reducer; Unhandled Rejection (TypeError): Cannot read property 'item' of undefined (yeah, item is not the best naming, sorry).
I'd like to walk you through the process of the PUT request, because code > text after all.
I will start where my action is created, because I guess you can figure out I have a form as my legit starting point.
So, here's my action (sorry for the wall of code)
Action:
import axios from 'axios'
import settings from '../../../../settings'
import { merge } from 'lodash'
axios.defaults.baseURL = settings.hostname
export function updateSettings(id, updatedValues, controller, door) {
const custom_name = updatedValues.custom_name
const location = updatedValues.location
const open_duration = updatedValues.open_duration
return (dispatch, getState) => {
const state = getState()
const door = state.fetchDoors.doors.find(val => val._id === id.itemId)
const controller = state.fetchDoors.controllers.find(
controller => controller._id === door.controller
)
console.log('door', door) // Returns updated object
console.log('controller', controller) // Returns updated object
const doorPayload = {
...door,
custom_name,
location
}
const controllerPayload = {
...controller,
open_duration
}
axios
.put(`${settings.hostname}/locks/${id.itemId}`, doorPayload)
.then(res => {
dispatch({ type: 'DOOR_UPDATING' })
dispatch({
type: 'DOOR_UPDATED_SUCCESS',
doorPayload
})
})
axios
.put(
`${settings.hostname}/controllers/${door.controller}`,
controllerPayload
)
.then(res => {
dispatch({ type: 'CONTROLLER_UPDATING' })
dispatch({
type: 'CONTROLLER_UPDATING_SUCCESS',
controllerPayload
})
})
.catch(err => console.log(err))
}
}
And here's my reducer
Reducer:
const initialState = {
isLoading: false
}
export const settings = (state = initialState, action) => {
switch (action.type) {
case 'DOOR_UPDATING':
return { ...state, isLoading: true }
case 'DOOR_UPDATED_SUCCESS':
return { ...state, item: action.payload.item, isLoading: false } // Here's where the error occurs
case 'CONTROLLER_UPDATING':
return { ...state, isLoading: true }
case 'CONTROLLER_UPDATING_SUCCESS':
return { ...state, item: action.payload.item, isLoading: false }
default:
return state
}
}
So the error occur inside of my reducer (I've added a comment) and I really don't understand why, now when the PUT request changes the data inside of my database. I assume there's something silly I'm missing, but I can't fix it. All help is really appreciated and if more code/ info needed just let me know.
Thanks for reading.
Edit:
Here's how my door object looks like:
In your reducer you are expecting and action with the shape of:
{type: 'something', payload: 'something else'}
But when you dispatch the action you don't have a property of payload.
this is what you are dispatching:
{
...door, // this will spread all properties of door (which doesn't have a property with the name payload)
custom_name,
location
}
Then you are trying to access action.payload.item hence you get the error:
Cannot read property 'item' of undefined
payload is never defined in your action (by the way nor item was).

Categories