I am working on a React application and I am using Redux to store the state. I have the following the code:
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { ADD_CATEGORY, GET_MENU } from './menu.types';
export const getMenu = () => async dispatch => {
const response = await fetch(`${apiUrl}/menu`);
if (response.ok) {
const menuData = await response.json();
dispatch({ type: GET_MENU, payload: menuData })
}
}
export const addCategory = category => async dispatch => {
const options = {
...apiConfig(),
method: 'POST',
body: JSON.stringify(category)
};
const response = await fetch(apiUrl + '/category/', options)
let data = await response.json()
if (response.ok) {
dispatch({ type: ADD_CATEGORY, payload: { ...data } })
} else {
alert(data.error)
}
}
menu.reducer.js:
import { ADD_CATEGORY, GET_MENU } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return [...action.payload];
case ADD_CATEGORY:
return [ ...state, { ...action.payload, menuItem: [] } ]
default:
return state;
}
}
In the above Reducer, the initial state is an empty array. Dispatching the GET_MENU action, changes the initial state so that it contains an array of menu items instead.
The array that is fetched in the GET_MENU action is of the following:
I have modified the code in the Reducer function so that the initial state is now the following:
menu.reducernew.js:
import { ADD_CATEGORY, GET_MENU } from './menu.types';
const INITIAL_STATE = {
menuArray: [],
isSending: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return {
...state,
menuArray: action.payload
};
case ADD_CATEGORY:
return {
...state,
menuArray: [ ...menuArray, { ...action.payload, menuItem: [] } ]
};
default:
return state;
}
}
For the case ADD_CATEGORY in the Reducer, I am not sure what the correct syntax is for reassigning the menuArray property in the state to the modified array. I want the array to have the new object that is fetched within the addCategory action creator added to it.
When I run my application, I am getting the following error:
I am not sure why I am getting this error or how to resolve it. Any insights are appreciated.
Here menuArray: [ ...menuArray, { ...action.payload, menuItem: [] } menuArray variable is not defined which you are trying to spread. Use state.menuArray to access current menuArray.
Change:
menuArray: [ ...menuArray, { ...action.payload, menuItem: [] }
To:
menuArray: [ ...state.menuArray, { ...action.payload, menuItem: [] }
case ADD_CATEGORY:
return {
...state,
menuArray: [ ...state.menuArray, { ...action.payload, menuItem: [] } ]
};
Related
I'm trying to implement sorting functionality in my notes app but have some problems with it. My sort function must just reverse an array of notes but it does not work.
I have a state with notes:
export const initialState = {
notes: [
{
id: nanoid(),
text: '',
date: '',
},
],
}
Action:
export const sortNoteAction = ([notes]) => ({
type: SORT_NOTE,
payload: [notes],
})
Reducer:
export default function notes(state = initialState, { type, payload }) {
switch (type) {
case SORT_NOTE: {
return {
...state,
notes: [payload].reverse(),
}
}
default:
return state
}
}
Action:
export const sortNoteAction = (notes=[]) => ({
type: SORT_NOTE,
payload: notes,
})
Reducer:
export default function notes(state = initialState, action) {
switch (action.type) {
case SORT_NOTE: {
return {
...state,
notes: action.payload.reverse(),
}
}
default:
return state
}
}
I think it doesn't work because you are essentially sorting a list with just one item. If you change [notes] to notes (2 occurrences) and [payload] to payload (1 occurrence) it will probably work
I am working on a React application and I am using Redux to store the state. I have the following code:
menu.reducer.js:
import { GET_MENU } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return [ ...action.payload ];
default:
return state;
}
}
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { GET_MENU } from './menu.types';
export const getMenu = () => async dispatch => {
const response = await fetch(`${apiUrl}/menu`);
if (response.ok) {
const menuData = await response.json();
dispatch({ type: GET_MENU, payload: menuData })
}
}
In the above Reducer, the initial state is an empty array. Dispatching the GET_MENU action, changes the initial state so that it contains an array of menu items instead.
The array that is fetched in the GET_MENU action is of the following:
However I want my initial state to be like the following:
const INITIAL_STATE = {
menuArray = [],
isSending = false
}
In the GET_MENU case in the reducer code, I am not sure what the correct syntax is to use in order to assign the menuArray property in the state to the array that is returned from the GET_MENU action.
Any insights are appreciated.
The state is simply a JavaScript value. If you want it to be an object with two properties, this isn't the right syntax:
const INITIAL_STATE = {
menuArray = [],
isSending = false
}
This is:
const INITIAL_STATE = {
menuArray: [],
isSending: false
}
Your reducer will now need to also return objects. You'll want to return a new object each time. Here's how you can do your reducer, specifically:
import { GET_MENU } from './menu.types';
const INITIAL_STATE = {
menuArray: [],
isSending: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return { ...state, menuArray: [...action.payload] };
default:
return state;
}
}
This says "create an object comprised of all the properties of the previous state but with the menuArray property set to the payload."
import { GET_MENU } from './menu.types';
const initialState= {
menuArray: [],
isSending: false
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_MENU:
return {...state, menuArray: action.payload};
default:
return state;
}
}
import { GET_MENU } from './menu.types';
const INITIAL_STATE = {
menuArray: [],
isSending: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return {
...state,
menuArray: action.payload
};
default:
return state;
}
}
I have the following state object in redux:
console.log({
jobOffers: {
filters: {
employments: [],
careerLevels: [],
jobTypeProfiles: [],
cities: [],
countries: [],
searchTerm: '',
currentPage: 1,
pageSize: 5
}
}
});
I want to set the array employments new.
That's my redux reducer:
export const reducer = (state = initialStateData, action) => {
switch (action.type) {
case Action.SET_ARR_FILTER:
{
const newNestedState = {
...state[action.key],
[action.key]: action.value,
};
return { ...state,
[action.key]: newNestedState
};
}
default:
return state;
}
};
The action:
export const SET_ARR_FILTER = 'SET_ARR_FILTER';
export const setEmployment = employment => ({
type: SET_ARR_FILTER,
key: 'employments',
value: employment,
});
But my object looks like this after the reducer has been called:
console.log({
employments: {
employments: ['HelloWorld']
},
})
What is wrong here ?
You're a level too deep (or not deep enough, depending on how you see it).
You need something like:
case Action.SET_ARR_FILTER:
{
const { filters } = state
return { ...state,
filters: {
...filters,
[action.key]: action.value
}
};
}
Similar to Mark's answer, all one line if you like.
export const reducer = (state = initialStateData, action) => {
switch (action.type) {
case Action.SET_ARR_FILTER:
return {
...state,
filter: {
...state.filter,
[action.key]: action.value
}
}
default:
return state;
}
};
Finally got it myself. Answer is:
case Action.SET_ARR_FILTER:
{
return {
...state,
jobOffers: {
...state.jobOffers,
filters: { ...state.jobOffers.filters,
[action.key]: action.value
},
},
};
}
I feel little confused, the problem is defineAvailableTouch action and state update connected to it.
Here is my code:
Actions/index.js
import {
ANIMATE_HELLO,
HANDLE_SCROLL,
IS_TOUCH_DEVICE,
SET_ABOUT_TOP,
SET_CONTACT_TOP,
SET_PORTFOLIO_TOP
} from "../Constants/ActionTypes";
export const animateHello = hello => ({
type: ANIMATE_HELLO,
payload: hello
});
export const handleScroll = scrollDelta => ({
type: HANDLE_SCROLL,
payload: scrollDelta
});
export const defineTouchAvailable = isTouchDevice => ({
type: IS_TOUCH_DEVICE,
payload: isTouchDevice
});
export const setAboutTop = aboutTop => ({
type: SET_ABOUT_TOP,
payload: aboutTop
});
export const setContactTop = contactTop => ({
type: SET_CONTACT_TOP,
payload: contactTop
});
export const setPortfolioTop = portfolioTop => ({
type: SET_PORTFOLIO_TOP,
payload: portfolioTop
});
Reducers/index.js
import {
IS_TOUCH_DEVICE,
} from "../Constants/ActionTypes";
import { initialState } from "../Constants/InitialState/InitialState";
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case ANIMATE_HELLO:
return {
...state,
hello: action.payload
};
case HANDLE_SCROLL:
return {
...state,
scrollState: action.payload
};
case IS_TOUCH_DEVICE:
console.log(action.payload); //!!!!!! THIS PRINTS EXPECTED VALUE !!!!!!!!!!
return {
...state,
isTouchDevice: action.payload
};
case SET_ABOUT_TOP:
return {
...state,
aboutTop: action.payload
};
case SET_CONTACT_TOP:
return {
...state,
contactTop: action.payload
};
case SET_PORTFOLIO_TOP:
return {
...state,
portfolioTop: action.payload
};
default:
return state
}
};
InitialState.js
export const initialState = {
scrollState: 0,
hello: 'H',
aboutTop: 0,
portfolioTop: 0,
contactTop: 0,
isTouchDevice: true
};
App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import About from "./Containers/About";
import Contact from "./Containers/Contact";
import Page from "./Containers/Page";
import Projects from "./Containers/Projects";
import {
defineTouchAvailable,
handleScroll
} from "./Actions";
window.onbeforeunload = () => {
handleScroll(0);
document.documentElement.scrollTop = 0;
};
const mapStateToProps = state => {
return {
isTouchDevice: state.isTouchDevice
}
};
const dispatchStateToProps = dispatch => {
return {
defineTouchAvailable: isTouchDevice =>
dispatch(defineTouchAvailable(isTouchDevice)),
handleScroll: scrollState => dispatch(handleScroll(scrollState))
}
};
class App extends Component {
componentDidMount() {
try {
document.createEvent('touchevent');
this.props.defineTouchAvailable(true);
} catch(e) {
this.props.defineTouchAvailable(false);
}
console.log(this.props.isTouchDevice); //!!!!!!!!!!!!!!! THIS ALWAYS PRINTS VALUE FROM initialState !!!!!!!!!!!!!!
if(this.props.isTouchDevice) {
document.documentElement.scroll(0, 1);
}
document.addEventListener('scroll', () => {
if (document.documentElement.scrollTop === 0) {
this.props.handleScroll(0);
}
});
}
render() {
return (
<div>
<Page/>
<Projects/>
<About/>
<Contact/>
</div>
);
}
}
export default connect(mapStateToProps, dispatchStateToProps)(App);
I really can't figure out whats wrong here.
As I commented
reducer console.log prints correct value that is expected to be assigned to my state (isTouchDevice field), but
after assigning it in dispatch action nothing changes - it is always value from initialState.
Can someone please explain it to me? Do I change my redux state uncorrectly? Then why other actions work as they're expected to?
The updated value of isTouchDevice will be available in componentDidUpdate, render or componentWillReceiveProps, not in componentDidMount.
componentDidMount will only be called one time when your component is mounted.
Note: componentWillReceiveProps is deprecated, better to not use it.
I have action creator to get data from API and have another action creator for loading status and want change loading status when data completely fetched.
Now, I wrote following codes but not working good, Loading status changes to false before data fetched completely.
My ActionCreator:
export const loadingStatus = (bool) => {
return {
type: Constants.LOADING_STATUS,
isLoading: bool
};
}
const allFlashCards = (action) => {
return{
type: Constants.ALL_CARD,
...action
}
};
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and my Reducer:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
export const Loading = (status= false, action) => {
switch (action.type) {
case Constants.LOADING_STATUS:
return action.isLoading;
break;
default:
return status;
}
}
and in my component:
componentDidMount() {
this.props.fetchCards();
}
render() {
return(
<div>
{this.props.loading ?
<Loading/> :
Object.keys(this.props.cards.data).map(this.renderCard)
}
</div>
);
}
const mapStateToProps = (state) => ({
cards: state.main,
loading: state.loading
});
const mapDispatchToProps = (dispatch) => ({
fetchCards: bindActionCreators(fetchAllFlashCards, dispatch)
});
and combineReducer is:
import { combineReducers } from 'redux';
import FlashCard , { Loading } from './FlashCard.js';
import { routerReducer } from "react-router-redux";
export default combineReducers({
main: FlashCard,
loading: Loading,
routing: routerReducer
});
In my page, I have an error in console and it's:
Uncaught TypeError: Cannot read property 'data' of undefined and if put my codes in timeout fixed my bug :/
What should i do?
Your default state is wrong here:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
It should be an empty object {} instead of an empty array [], since you're returning objects.
This code
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
Looks completely fine. loadingStatus(false) should not be called before setting the flash cards. Your reducers and action creators are synchronous (as they should). So, nothing of note there.
I saw that you're using action.data on the Constants.ADD_CARD action case, but in your code you do not dispatch any actions with that type. Do you do it somewhere else? Maybe that's where the error is?
EDIT:
Another place that you're using .data is in your renderer: this.props.cards.data. What's the value of the state.main?
How are you creating your rootReducer? It should be something like this:
const rootReducer = combineReducers({
main: FlashCard,
loading: Loading,
});
Are you using main there? Or maybe cards?
Finally, I fixed my problem:
In my actionCreator change fetchAllFlashCards method to following:
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then(({data})=>{
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and in reducer change FlashCard reducer to following:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action.data};
break;
default:
return state;
}
};