I am trying to use propTypes to help typeCheck/validate data in my app. The data comes through fine but I get a warning; Warning: Failed prop type: The propallSubjectSubcategoriesis marked as required inOddMainPage, but its value isundefined`.
I've read through some of the other responses but I haven't yet fpound a solution to fix my issues.
COMPONENT
class OddMainPage extends React.Component {
constructor(props) {
super(props);
this.state = {
categoryTables: [],
tableColumns: [],
gettingColumns: false,
selectedCategory: "",
selectedTable: ""
}
}
componentDidMount() {
this.props.dispatch(getAllSubjectSubCategories());
}
//more code to render some data
}}
OddMainPage.propTypes = {
allSubjectSubcategories: PropTypes.array.isRequired,
subCategoryTables: PropTypes.array,
tableColumns: PropTypes.array
}
const mapStateToProps = state => ({
allSubjectSubcategories: state.allSubjectSubcategories.allSubjectSubcategories,
subCategoryTables: state.subCategoryTables.subCategoryTables,
tableColumns: state.tableColumns.tableColumns
})
export default connect(mapStateToProps)(OddMainPage);
REDUCER
const initialState = {
subCategoryTables: [],
allSubjectCategories: [],
allSubjectSubcategories: []
}
export const getAllSubjectSubcategoriesReducer = (state = initialState.allSubjectSubcategories, action) => {
switch (action.type) {
case "GET_ALL_SUBJECT_SUBCATEGORIES":
return {
...state,
allSubjectSubcategories: action.allSubCats
}
default:
return initialState.allSubjectSubcategories
}
}
I also tried setting default state to default: return state but get the same results.
STORE
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
const initialState = {};
const middleware = [thunk];
let store;
if (window.navigator.userAgent.includes("Chrome")) {
store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
} else {
store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware))
);
}
export default store;
It looks like you're changing the return type of your reducer form an array to an object on the GET_ALL_SUBJECT_SUBCATEGORIES action.
Looking at initialState for getAllSubcategoriesReducer, you can see the value is an array. However the return value for the GET_ALL_SUBJECT_SUBCATEGORIES branch is an object. You'll need to standardize on one or the other.
Since the initial state of the reducer is just an empty array, the value of state.allSubjectSubcategories in mapStateToProps will be that empty array. So when you call allSubjectSubcategories: state.allSubjectSubcategories.allSubjectSubcategories, you get undefined.
If you want to keep the nested version you will need to change initialState (and fix the default case of the reducer):
// NOTE: I've nested all of your sub-reducer keys to match your `mapStateToProps`.
const initialState = {
subCategoryTables: {
subCategoryTables: [],
},
allSubjectCategories: {
allSubjectCategories: [],
},
allSubjectSubcategories: {
allSubjectSubcategories: [],
}
}
export const getAllSubjectSubcategoriesReducer = (state = initialState.allSubjectSubcategories, action) => {
switch (action.type) {
case "GET_ALL_SUBJECT_SUBCATEGORIES":
return {
...state,
allSubjectSubcategories: action.allSubCats
}
// return `state` here or you will keep reseting your reducer state
default:
return state
}
}
If you want to keep the reducer as an array like the initial state you will need to update your reducer and your mapStateToProps:
export const getAllSubjectSubcategoriesReducer = (state = initialState.allSubjectSubcategories, action) => {
switch (action.type) {
case "GET_ALL_SUBJECT_SUBCATEGORIES":
// assuming action.allSubCats is an array, we want to replace this entire reducer state with the new array
return action.allSubCats
// return `state` here or you will keep reseting your reducer state
default:
return state
}
}
Now that the reducer above always returns an array you can update your mapStateToProps to remove the extra key being introduced before:
const mapStateToProps = state => ({
allSubjectSubcategories: state.allSubjectSubcategories, // note the change here
// you probably want these to be updated as well with similar changes to your other reducers
subCategoryTables: state.subCategoryTables,
tableColumns: state.tableColumns
})
Related
My reducer removeMovie is not working. Every time I get all initial data from state without one element. I need to update state on every call.
import { createSlice } from '#reduxjs/toolkit';
import movies from '../../services/movies.json';
export const movieSlice = createSlice({
name: 'movie',
initialState: movies,
reducers: {
addMovie: (state, action) => {
state.push(action.payload);
},
removeMovie: (state, action) => {
state = state.filter(movie => movie.title !== action.payload);
console.log(JSON.stringify(state));
},
},
});
// Action creators are generated for each case reducer function
export const { addMovie, removeMovie } = movieSlice.actions;
export default movieSlice.reducer;
What you are doing in removeMovie reducer is not tracked by Immer library. With this line state = state.filter(movie => movie.title !== action.payload); you are assigning local state variable, and that is not actually change of state. You can try this:
removeMovie: (state, action) => {
const newState = state.filter(movie => movie.title !== action.payload);
console.log(JSON.stringify(newState));
return newState;
},
You can find more about this on the following link: immer usage patterns
I was trying to make a store but for some reason the initial state doesnt change when i alter the inicial state const:
this is the reducer with the inicial state:
import { GET_PRODUCT_BY_NAME, ADD_PRODUCT_TO_CART, CLEAR_CART, DELETE_ITEM, CURRENT_PRODUCT,INCREASE_AMOUNT } from '../../actionTypes'
const INITIALSTATE = {
currentProduct:{},
product: {},
list:[]
}
export function ProductReducer(state = INITIALSTATE, action) {
const { type, payload } = action
switch (type) {
case GET_PRODUCT_BY_NAME:
return {
...state,
user: payload.user,
}
case ADD_PRODUCT_TO_CART:
return {
...state,
list: [...state.list,payload]
}
case CLEAR_CART:
return {
list: []
}
case DELETE_ITEM:
console.log()
return {
list:[...state.list.slice(0, action.payload),
...state.list.slice(action.payload + 1)]
}
case CURRENT_PRODUCT:
return {
...state,
currentProduct: payload
}
case INCREASE_AMOUNT:
return {
...state,
}
default:
return state
}
}
this is the file with the store config details:
import { createStore, applyMiddleware } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import ReduxThunk from 'redux-thunk'
import storage from 'redux-persist/lib/storage'
import rootReducer from './rootReducers'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
let store = createStore(persistedReducer, applyMiddleware(ReduxThunk))
let persistor = persistStore(store)
export { store, persistor }
and this is what i get when i console.log the state:
console.log
that result is from an older version
you encountered that issue because you had applied persistence before you settled on how your Product reducer initial state should look like. to solve this you need to either temporarily include your reducer into the persistConfig blacklist or setting the stateReconciler to autoMerge.
Redux automatically adds an entry called state. Practice creating counter function. However, the state item that I did not set is automatically added so that it does not appear in the desired direction when dispacth is executed.
I guess it's caused by combineReducers, but I don't know exactly.
Clearly, I didn't make State in counter.
//action name
const INCRE = 'counter/INCRE';
const DECRE = 'counter/DECRE';
export const incre = () => ({type: INCRE});
export const decre = () => ({type: DECRE});
//default
const initState = {
number: 0
};
//reducer
function counter(state = initState, action) {
console.log("frist value");
console.log(state.number);
console.log(initState);
if(action.type === INCRE){
console.log("add");
console.log(state.number)
return {number: state.number +1 }
}else if (action.type === DECRE) {
console.log("minus");
return {number: state.number -1 }
}
else {
return{state};
};
};
export default counter;
// C is containers & Counter is name
import React from 'react';
import Counter from '../components/Counter';
import { connect } from 'react-redux';
import {incre, decre} from '../modules/counter';
const CCounter = ({number, incre, decre}) => {
return (
<Counter number={number} onIncre={incre} onDecre={decre}/>
);
};
const mapStateToProps = state => ({
number: state.counter.number
});
const mapDispatchToProps = dispatch => ({
incre: () => {
dispatch(incre());
},
decre: () => {
dispatch(decre());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(CCounter);
//reducer
//...
else {
return{state};
};
The above line in your reducer is likely the problem because what it can return is what you're seeing:
{
state: {
number: 0,
}
}
You probably wanted to simply return the state.
else {
return state;
};
I want to know what the best way to clean an array would be, write a new element.
My array, selectedUser, will be nurtured with the information I send as payload, after clicking on a row in the table.
You should always have only one element in the array, so I must delete the information it contains when selecting another element. I am currently doing it as follows, but I don't think it's the right one.
import * as actionTypes from "../actions/userModuleActions";
const initialState = {
selectedUser: []
};
const reducer = (state = initialState, action) => {
state.selectedUser = [];
switch (action.type) {
case actionTypes.UPDATE_USER_MODULE_SELECTED_USER:
return {
...state,
selectedUser: state.selectedUser.concat(action.payload)
};
default:
return state;
}
};
export default reducer;
In this case since you are completely replacing the old value with the new you can replace the selectedUser key in state entirely:
import * as actionTypes from "../actions/userModuleActions";
const initialState = {
selectedUser: []
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_USER_MODULE_SELECTED_USER:
return {
...state,
selectedUser: [action.payload], // note that action.payload is wrapped in []
};
default:
return state;
}
};
export default reducer;
No need to "clear" the selectedUser first.
Have you thought about using another data type for storing the selected user? Maybe you should create a SelectedUser model with attributes and set its initial state to undefined.
I have list of items on single screen. The list is fetched from redux store and rendered as set of form components. At the bottom of list I have "Add new" button.
In my initial implementation I have used container component with setState({newItem: {...}). This has side effect of view flickering when newItem is cleared and new item is added to redux items.
{items.map((item, index) =>
<ItemForm
key={index}
formKey={index.toString()}
initialValues={item}
submitItem={this.handleUpdateItem.bind(this)}
removeItem={() => this.handleRemoveItem(item.id)}
/>
)}
{newItem &&
<ItemForm
key="new"
formKey="new"
initialValues={newItem}
submitItem={this.handleCreateItem.bind(this)}
removeItem={() => this.handleRemoveNewItem()}
/>
}
I think better way to implement this would be not to use newItem and instead handle the new item inside redux. But in that case I have to change the actions/reducers API, in particular the CREATE_ITEM would no longer append item but rather updates the last item in store.
Is there some way I can reduce the flickering or do you have some suggestions how to do it with redux? In particular - what set of actions / reducers should I use to model better solution? Should I rather make API calls in container and simplify the redux actions?
The complexity of the actions/reducers API for me comes from mixing persisted (requires id to make request) and not persisted objects (requires index to find) in redux.
Here is my redux module:
import axios from 'axios';
import * as _ from 'lodash';
export const FETCH_ITEMS = 'items/FETCH_ITEMS';
export const CREATE_ITEM = 'items/CREATE_ITEM';
export const REMOVE_ITEM = 'items/REMOVE_ITEM';
export const UPDATE_ITEM = 'items/UPDATE_ITEM';
const initialState = {
items: []
};
export default function reducer(state = initialState, action) {
let index;
switch (action.type) {
case FETCH_ITEMS:
return {
...state,
items: action.items
};
case CREATE_ITEM: // would become "last item UPDATE_ITEM"
return {
...state,
items: [
...state.items,
action.item
]
};
case REMOVE_ITEM:
index = _.findIndex(state.items, {id: action.id});
return {
...state,
items: [
...state.items.slice(0, index),
action.item,
...state.items.slice(index + 1)
]
};
case UPDATE_ITEM:
index = _.findIndex(state.items, {id: action.item.id});
return {
...state,
items: [
...state.items.slice(0, index),
action.item,
...state.items.slice(index + 1)
]
};
default:
return state;
}
}
export function fetchItems() {
return function (dispatch) {
return axios.get('/api/items')
.then(res => {
const items = res.data;
dispatch({type: FETCH_ITEMS, items});
})
}
}
export function createItem(item) {
return function (dispatch) {
return axios.post('/api/items', item)
.then(res => {
const item = res.data.item;
dispatch({type: CREATE_ITEM, item});
})
}
}
export function removeItem(id) {
return function (dispatch) {
return axios.delete(`/api/items/${id}`)
.then(() => {
dispatch({type: REMOVE_ITEM, id});
});
}
}
export function updateItem(item) {
const {id, ...rest} = item;
return function (dispatch) {
return axios.put(`/api/items/${id}`, rest)
.then(res => {
const item = deserializeItem(res.data.item);
dispatch({type: UPDATE_ITEM, item});
})
}
}