As an initial state I use array of objects:
export default{
items: [
{
Date: 1,
Operation: 'revenue',
}
]
}
In component I dispatch the action, which must update one element in object of array: "Operation"
const mapDispatchToProps = (dispatch) => {
return {
selectOperation: (input) => dispatch({type: app.SELECT, payload: input})
}
};
class OperationSelect extends React.Component {
// constructor
handleChange(event) {
this.props.selectOperation({
key: 'Operation',
value: event.target.value
});
};
// render() logic
}
export default connect(null, mapDispatchToProps)(OperationSelect)
Reducer:
import initialState from '../constants/initialState';
import { app } from '../constants/types';
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
[action.payload.key]: state.items.map(
(item, i)=> i===0 ? {...item, Operation: action.payload.value}
: item
)
};
default:
return state;
}
}
But, when I select new value and application run handleChange, dispatch the action, run reducer, the state in store keeps the old value of "Operation".
What am I doing wrong?
This is I think what you need to do:
first add an id property to your items and then do something like this:
export default {
items: [
{
id: 0,
Date: 1,
Operation: "revenue",
},
],
};
class OperationSelect extends React.Component {
// constructor
handleChange(event) {
this.props.selectOperation({
key: "Operation", // I think you need to check this and try to findout that you need this or not
value: event.target.value,
id: 0 // 0 is just an example you need to decide how you would implement the id
});
}
// render() logic
}
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
items: state.items.map((item, i) =>
i === action.payload.id ? { ...item, Operation: action.payload.value } : item
),
};
default:
return state;
}
}
The problem was in that I did not update in reducer the state of array.
This is a working code:
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
items: state.items.map(
(item, i)=> i===0 ? {...item, [action.payload.key]: action.payload.value}
: item
)
};
default:
return state;
}
}
Earlier in return-block I used [action.payload.key] in place, where "items" should have used. So I updated "Operation" in place, where "items" updated.
Related
this is file list.js
...
class List extends React.Component {
render() {
return (
...
{this.props.todoList.map((todo, index) => <Item {...todo} key = {index}/>)} // err this code
...
);
}
};
const mapStateToProps = (state) => {
return {
todoList: state.todos
};
};
...
this is file rootReducers.js
const initialState = {
todos : [
{ id: 1, name: "Khoa" },
{ id: 2, name: "Khoai" },
{ id: 3, name: "Kha" }
],
currentName : ''
}
const TodoList = (state = initialState, action) => {
switch (action.type) {
case "ADD_TODO":
return [...state.todos, { id: state.todos.length + 1, name: action.text }];
default:
return state;
}
};
...
this is action.js
export const addTodo = (text) =>{
return {
type : 'ADD_TODO',
text
}
}
this is file fromCreate.js
...
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (text) => dispatch(addTodo(text))
};
};
...
i'm loading data success but, when i excute event addtoto it message : Cannot read property 'map' of undefined. Help me
Your reducer code seems wrong. You need to return full new state slice from it not just an array.
const TodoList = (state = initialState, action) => {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [...state.todos, { id: state.todos.length + 1, name: action.text }]
};
default:
return state;
}
};
I have a state object that contains an array inside names rows. This array contains a list of objects:
{_id: "5e88ad4c5f6f7388d50b9480",
stampa: "Confezione",
S: 0,
M: 0,
L: 0,
XL: 0,
coloured: "",
modello: "",
SKU: ""}
Now, in a form I dispatch an action that the payload contains exactly the same object, the only differences are the keys S, M, L, XL that can change.
Then, in my reducer, I want to find in my original state the same object by matching with _id, and then update it with the object that comes with the payload.
This is my attempt, however I am getting the following error:
TypeError: state.rows.map is not a function
case "UPDATE_ROW":
return state.rows.map((row) =>
row._id === action.payload._id
? {
...row,
_id: action.payload,
}
: row
);
How can I tackle this in a better way?
EDIT:
This is my reducer:
export default (state, action) => {
switch (action.type) {
case "UPDATE_STATE":
return {
...state,
rows: action.payload,
};
case "SET_ERROR":
return {
...state,
error: action.payload,
};
case "UPDATE_ROW":
console.log("updating", action.payload);
return state.rows.map((row) =>
row._id === action.payload._id
? {
...row,
_id: action.payload,
}
: row
);
default:
return state;
}
};
And this is my state:
import React, { createContext, useReducer } from "react";
import AppReducer from "./AppReducer";
// Initial State
const initialState = {
rows: [],
};
export const GlobalContext = createContext(initialState);
// Provider component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
console.log(state);
return (
<GlobalContext.Provider value={[state, dispatch]}>
{children}
</GlobalContext.Provider>
);
};
The main issue you are having there is that you should default state.rows to be an array in order for the type error to stop.
Other than that, from what I see there, your logic seems fine.
EDIT:
Okay, looks like your issue was that you weren't returning the full state when you were running the "UPDATE_ROW" case. If you use this, I believe the issue should be fixed.
I also fixed the logic in the reducer case as well. It looked like you were adding the entirety of the payload object in the _id property while keeping the rest of the row the same. Rather than update the row with the payload property.
export default (state, action) => {
switch (action.type) {
case "UPDATE_STATE":
return {
...state,
rows: action.payload,
};
case "SET_ERROR":
return {
...state,
error: action.payload,
};
case "UPDATE_ROW":
console.log("updating", action.payload);
return {...state, rows:state.rows.map((row) =>
row._id === action.payload._id
? {
...action.payload
}
: row
)};
default:
return state;
}
};
I need to toggle somehove adding/removing object from redux store. It is like check/uncheck. I have following code:
const list = ({ id, item }) => {
const isChecked = name => items.some(item => item.name === name);
let itemClass = cx({
item: true,
selected: isChecked(name),
});
return (
<li className={itemClass}
onClick={() => click(fullItem)} key={id}>
<div className={styles.name}>
{isChecked(name) ?
(<span><i className={`fa fa-check`}></i>{name}</span>)
: (<span>{name}</span>)
}
</div>
</li>
);
}
export const click = item => ({
type: ADD_ITEM,
payload: item,
});
import {
ADD_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_ITEM:
return {
...state,
items: [action.payload],
};
default:
return state;
}
};
but for now it only work for adding item to store, when I click on item when it is selected, it should remove it from the store. How can I toggle onclick removing/adding object to redux store?
You could try something like this. Changing the ADD_ITEM instead to a TOGGLE_ITEM where you check for existence of the item using something like Array.prototype.find. Adding if it does not exist, and removing it if it does exist:
export const click = item => ({
type: TOGGLE_ITEM,
payload: item,
});
import {
TOGGLE_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
const currentItem = state.items.find(item => item.id === action.payload.id);
if (!currentItem) {
return {
...state,
items: [...state.items, action.payload],
};
} else {
const newItems = state.items.filter(item => item.id !== action.payload.id];
return {
...state,
items: [...newItems]
};
}
default:
return state;
}
};
You may though want to consider having separate add, update, and delete actions, and dispatch the different actions accordingly from your components.
export const click = item => ({
type: TOGGLE_ITEM,
payload: item,
});
import {
TOGGLE_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
// check to see if the item already in our array
// Array.some return true/false
const itemAlreadyExists = state.items.some(item => item.id === action.payload.id)
return {
...state,
// if the item already in our array filter it
// if not just add it
items: itemAlreadyExists
? state.items.filter(item => item.id !== action.payload.id)
: [...state.items, action.payload],
};
default:
return state;
}
};
I want to add a loader for each action, like the buttons will display loading when dispatch (see demo)
export default class Items extends Component {
render() {
return (
<div>
<div>status: {this.props.item.status}</div>
<button onClick={() => this.props.resetItem()}>
{this.props.loading ? "loading..." : "Reset"}
</button>
<button onClick={() => this.props.approveItem()}>
{this.props.loading ? "loading..." : "Approve"}
</button>
</div>
);
}
}
The problem is all button will show loading because my reducer has a global loading state only
export function items(state = initState, action) {
switch (action.type) {
case "APPROVE":
return {
...state,
loading: true
};
case "APPROVED":
return {
...state,
loading: false,
item: {
status: "approved"
}
};
case "RESET":
return {
...state,
loading: true
};
case "DONE_RESET":
return {
...state,
loading: false,
item: {
status: "pending"
}
};
default:
return state;
}
}
I can hardcode approve_loading, reset_loading and so on but that's redundancy, any technique to do namespacing in reducer?
Neat question - have never run into this myself but I'm wondering if something like this could work. You can use combineReducers() to namespace, so a perhaps not entirely elegant approach could be:
export function itemsReducer(index) {
return function items(state = {}, action) {
switch (action.type) {
case `APPROVE_${index}`:
return {
...state,
loading: true,
};
case `APPROVED_${index}`:
return {
...state,
loading: false,
item: {
status: 'approved',
},
};
default:
return state;
}
};
}
const reducers = {};
//could add more indexes here for more items;
[0, 1].forEach(i => {
reducers[`item${i}`] = itemsReducer(i);
});
export default combineReducers(reducers);
//state = {
// item0: {...},
// item1: {...}
//}
Your actions would then need to include the appropriate index (0 or 1) when dispatching (e.g. APPROVED_1) so the correct item state will get set.
Equivalent syntax:
export default combineReducers({
item0: itemsReducer(0),
item1: itemsReducer(1)
});
This is the reducer state. I need to add, update, remove the object in cartData. At the first time, cartData is empty.
const initialState = {
fetchData: {},
cartData: {}
}
Example:
fetchData: {
"React":{'name': 'React'},
"Node":{'name': 'Node'},
}
If user ADD_ITEM react book, new item is adding in the cart here.
cartData:{
"React":{'name': 'React', 'quantity': 1},
}
If user Edit_ITEM react book, existing item is updating here.
cartData:{
"React":{'name': 'React', 'quantity': 4},
}
If user REMOVE_ITEM react book, removing when its come to zero here.
cartData:{
}
How can we modify redux state for these actions?
Tried this: using lodash. But did't worked out correctly.
case types.ADD_ITEM:
return { ...state, cartData: // add new item }
case types.EDIT_ITEM:
return { ...state, [state.cartData.name]: action.payload }
case types.REMOVE_ITEM:
return _.omit(state, [state.cartData.name]: action.payload)
You can use spread syntax for add and edit items and Object.keys() and reduce() for remove item.
const initialState = {
fetchData: {},
cartData: {}
}
function cartReducer(state = initialState, action) {
switch(action.type) {
case 'ADD_ITEM':
return {...state, cartData: {...state.cartData, ...action.payload}}
case 'EDIT_ITEM':
return {...state, cartData: {...state.cartData, ...action.payload}}
case 'REMOVE_ITEM':
let newState = Object.keys(state.cartData).reduce((r, e) => {
if(!action.payload[e]) r[e] = state.cartData[e];
return r
}, {})
return {...state, cartData: newState}
default:
return state;
}
}
var state = {}
state = cartReducer(undefined, {
type: 'ADD_ITEM',
payload: {"React":{'name': 'React', 'quantity': 1}}
})
console.log(state)
state = cartReducer(state, {
type: 'ADD_ITEM',
payload: {"Node":{'name': 'Node', 'quantity': 2}}
})
console.log(state)
state = cartReducer(state, {
type: 'EDIT_ITEM',
payload: {"React":{'name': 'React', 'quantity': 4}}
})
console.log(state)
state = cartReducer(state, {
type: 'REMOVE_ITEM',
payload: {"React":{'name': 'React', 'quantity': 1}}
})
console.log(state)
It's hard to know exactly what you are trying. Below is an example of a reducer function with an add to cart method. You'll need to add a similar method for each of your scenarios.
export function reducer(state = initialState, action: any): State {
switch(action.type) {
case "ADD_TO_CART": {
return {
fetchData: state.fetchData,
cartData: Object.assign({}, state.cartData, action.payload}
};
}
}
default: {
return state;
}
}
You will then dispatch the action by calling the dispatch function:
dispatch({
type: "ADD_TO_CART",
payload: "React":{'name': 'React', 'quantity': 1}
})
In actions:
const editData = (items) => (dispatch) => {
dispatch({type: 'EDIT_ITEMS', payload: items});
}
In reducer:
const reducer = (state = INITIAL_STATE, action){
case 'EDIT_ITEMS': {
if(_.isEmpty(action.payload)){
return {
...state,
cartData: {},
};
} else {
return {
...state,
cellData: action.payload,
};
}
}
}
This should be the way to do it. payload should be all the items you've in the cart at any point of time.
[EDIT:]
As the question has been edited, You can also do that using deleting a key, using
// Ref: https://github.com/erikras/react-redux-universal-hot-example/issues/962#issuecomment-219354496
export const removeByKey = (object, deleteKey) => {
return Object.keys(object)
.filter(key => key !== deleteKey)
.reduce((result, current) => {
result[current] = object[current];
return result;
}, {});
};
case types.REMOVE_ITEM:
return { ...state, cartData: deleteKey(cartData, action.payload)) }