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).
Related
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.
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.
I am using Vuex with axios to fetch data from my backend. But somehow the state property userName is not updating in my Vue Single File Component(SFC).
approot.js
state
const state = {
userName: 'foo'
};
getter
const getters = {
getUserName: (state) => state.userName
};
Single File Component
<template>
<div id="navbar">
//cut for brievity
<span>{{getUserName}}</span>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: 'navbar',
computed: mapGetters(['getNumberOfJobMessages','getUserName']),
//cut for brievity
}
</script>
<style scoped>
//cut for brievity
</style>
Action fetching data with axios from the backend
const actions = {
async fetchMenuData({ commit }) {
//fetch data from api controller
const response = await axios.get('../api/Menu/GetMenu');
console.log(response.data.userName); //not undefined
commit('setMenuData', response.data);
}
}
Mutation setting state variables
const mutations = {
setMenuData(state, menuData) {
console.log(menuData.userName); //not undefined
state.userName = menuData.userName;
console.log(state.userName); //not undefined
}
}
Problem
When my single file component calls getUserName it always renders 'foo', the hardcoded value. Im quite baffled by this, since the rest of my state variables are set with the same pattern, and my components have no problems getting them.
Anyone who knows whats going wrong or can see a flaw in my code? It would be highly appreciated.
Use mutations to only set data. and other things do on action. like:
Action:
const actions = {
async fetchMenuData({ commit }) {
const response = await axios.get('../api/Menu/GetMenu');
let userName = response.data.userName;
commit('setUserName', userName);
}
}
And mutations:
const mutations = {
setUserName(state, userName) {
state.userName = userName;
}
}
Dont forget to dispatch the function fetchMenuData
Properly not sure, why this happens. But, I faced this problem and solved by this way.
axios.get('../api/Menu/GetMenu')
.then(({ data }) => {
commit('setUserName', data.userName);
}).catch(error => { })
It is better to make a commit in then()
Basically I want to show message to user after he successfully submitted form. Like Thanks. Product added. And after few seconds I want this message to disappear.
Currently my code is pretty straightforward and contains 3 action types for AJAX request (ADD_PRODUCT_REQUEST, ADD_PRODUCT_SUCCESS, ADD_PRODUCT_FAILURE).
My component containing form connected to redux via mapDispatchToProps:
import {addProduct} from '../actions/product';
const mapDispatchToProps = dispatch => ({
onSubmit(productName) {
dispatch(addProduct(productName))
}
});
class AddProduct extends React.Component {
addProduct() {
const {onSubmit} = this.props;
onSubmit(this.productNameInput.val);
}
render() {
return (
<form onSubmit={::this.addProduct}>...</form>
)
}
}
And my action creator is also pretty straightforward (pseudocode):
export const addProduct = (name) => dispatch => {
dispatch(addProductRequest())
fetch(...).then(addProductSuccess()).catch(e => addProductFailure(error))
}
How I can using this "standard" react-redux architecture know that AJAX request executed successfully on component side?
I have only 1 idea - add some value to state informing that product was added, like added:true, but I think it's bad idea.
You must return fetch result in actions, then you need to wrap up then and catch statements to catch result of action, something like this:
addProduct() {
this.setState({ error: {}, isLoading: true });
this.props.onSubmit(this.productNameInput.val)
.then(res => this.setState({ isLoading: false, success: { title: "Success!", msg: "Product added!" } }))
.catch(err => this.setState({ isLoading: false, error: err.response.data.error} }));
}
You can bind in future this example handling to your form validation or to your frontend notification system.
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;
}
}