item still adding in React Redux - react-redux

am writing a add button function in my webapp, so when there is no items and if the items does not exist, but the item keeps on adding a item even if it exist, please can someone help me out
here's is code
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
items: [],
};
export const basketSlice = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, action) => {
if ( state.items.length === 0 || state.items !== action.payload ) {
// if item does not exists then add that item
state.items = [...state.items, action.payload]
} else {
// else return actual array
state.items;
}
},
},
});
export const { addToBasket, removeFromBasket } = basketSlice.actions;
export default basketSlice.reducer;

Related

Why is a state created automatically? (redux)

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;
};

React-Redux ailed propType - value undefined

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
})

Re-rendering items after sorting in React Redux

This is the smart component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Filter from '../Filter';
import Sort from '../Sort';
import { getItems, selectItem, reverseItems, findItems } from '../../actions/items';
import './app.css';
const App = ({filterList, sortList, onGetItems, onFindItems, reverseItems, onSelectItem}) => {
onGetItems();
return (
<div>
<Filter items={filterList} findText={onFindItems} reverseItems={reverseItems} selectItem={onSelectItem} />
<Sort items={sortList} selectItem={onSelectItem} />
</div>
)}
function mapStateToProps(state) {
return {
filterList: state.items.filter(item => item.name.includes(state.filter.toLowerCase())),
sortList: state.items,
}
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
onGetItems: getItems,
onSelectItem: selectItem,
onFindItems: findItems,
reverseItems: reverseItems
}, dispatch)}
export default connect(mapStateToProps, matchDispatchToProps)(App);
and actions:
let items = [];
(function onGetItems() {
let xhr = new XMLHttpRequest();
xhr.open('GET', '/items.json', false);
xhr.send();
if (xhr.status !== 200) {
console.log(xhr.status + ': ' + xhr.statusText);
} else {
items = JSON.parse(xhr.responseText.toLowerCase());
items.sort(function(a, b) {
if (a.name > b.name) return 1;
if (a.name < b.name) return -1;
return 0;
});
}
})();
export const getItems = () => dispatch => {
dispatch({ type: 'ADD_ITEMS', payload: items });
}
export const selectItem = (item) => {
console.log(item);
return {
type: "ITEM_SELECTED",
payload: item
}
};
export const reverseItems = (items) => {
console.log(items)
return {
type: "REVERSE_ITEMS",
payload: items.reverse()
}
};
export const findItems = (items) => {
return {
type: "FIND_ITEMS",
payload: items
}
};
and 2 reducers:
const initialState = '';
export default function filter(state = initialState, action) {
switch (action.type) {
case 'FIND_ITEMS': return action.payload;
default: return state
}
}
const initialState = [];
export default function items(state = initialState, action) {
switch (action.type) {
case 'ADD_ITEMS': return action.payload;
default: return state
}
}
The action reverseItems reverses the array, but the problem is that it doesn't rewrite state because it's formed by another action.
I realize that it's a basic issue, but I can't get how to do that.
Replace your
items.sort() statement with [...items].sort() which creates a new reference to the array and allows the re-rendering of the component. The sort function sorts the array using the same reference and does not cause a re-render.
Try to use Redux Thunk for your async calls.
You can dispatch an action, for example RECEIVED_ITEMS after your http request.

Redux reducer correct way to check if item already exists in state

I'm a little uncertain of this approach when updating an existing or adding a new object to a redux store but am having trouble getting this to work using the accepted methods i.e. Object.assign, update() or spread operators. I can get it working as follows:
const initialState = {
cart: []
}
export default function cartReducer(state = initialState, action) {
switch (action.type) {
case ADD_TO_CART:
let copy = _.clone(state.cart);
let cartitem = _.find(copy, function (item) {
return item.productId === action.payload.productId;
});
if (cartitem) {
cartitem.qty = action.payload.qty;
} else {
copy.push(action.payload);
}
return {
...state,
cart: copy
}
default:
return state
}
}
Although this works, I'm using Underscore to copy the state and check whether the item already exists in state which seems unnecessary and overkill?
This is the code for the redux. Use the .find function to find if an element is already in the array.
Example Code:
const inCart = state.cart.find((item) =>
item.id === action.payload.id ? true : false
);
return {
...state,
cart: inCart
? state.cart.map((item) =>
item.id === action.payload.id
? { ...item, qty: item.qty + 1 }
: item
)
: [...state.cart, { ...item, qty: 1 }],
};
With Redux-Toolkit you can mutate the state objects so you can likely simplify this a bit.
const basket = createSlice({
name: "cart",
initialState,
reducers: {
addToCart: (state, { payload }) => {
const inCart= state.cart.find((item) => item.id === payload.id);
if (inCart) {
item.qty += payload.qty;
} else {
state.items.push(payload);
}
},
},
});

Redux architecture: multiple items editable on same page with new item form

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});
})
}
}

Resources