The scenario is :
1.press a button(Class A-Component) to add a item to a list (Class B-Component) then re-render the Table.
I have checked the list in the store by store.getState() And i have updated the list in the store perfectly after pressing button.
But it didnt re render the Table which showing the list.
Anyone can help ?
initialState3
var initialState3 = {
products1: [
{
id: "123",
abbreviation: "123",
case_no: "123",
created_dt: "31/01/2018",
last_updated: "11:43:45"
}
]
};
reducers
function ReducersForSeach(state = initialState3, action) {
switch (action.type) {
case "CLICK_SEARCH": {
products1.push({
id: "456",
abbreviation: "456",
case_no: "456",
created_dt: "31/01/2018",
last_updated: "11:43:45"
});
return {
...state,
products1
};
}
}
}
Component
var Table = React.createClass({
render() {
const { products1 } = this.props;
const tableHeaderColumns = columnData.map(column => (
<TableHeaderColumn dataField={column.action} isKey={column.isKey} dataSort={column.dataSort}>
{column.description}
</TableHeaderColumn>
));
return (
<BootstrapTable height="600" style={{ overflowY: "scroll" }} data={products1} options={options}>
{tableHeaderColumns}
</BootstrapTable>
);
}
});
Connection
function mapStateToPropsFortable(state) {
return {
products1: state.reducreForSeach.products1
};
}
const Table1 = connect(
mapStateToPropsFortable,
null
)(Table);
ReactDOM.render(
<Provider store={store}>
<Table1 />
</Provider>,
document.getElementById("divReqListTable")
);
Store
var rootReducer = Redux.combineReducers({
reducreForAge,
reducreForButtonGroup2,
reducreForSeach
});
var store = Redux.createStore(rootReducer);
Chang your reducer to this
function ReducersForSeach(state = initialState3, action) {
switch (action.type) {
case "CLICK_SEARCH": {
const products1 = {
id: "456",
abbreviation: "456",
case_no: "456",
created_dt: "31/01/2018",
last_updated: "11:43:45"
};
return {
...state,
products1: state.products1.concat(products1)
};
}
default:
return state;
}
}
You click try it in https://codesandbox.io/embed/74108xyln0
This is the problem area.
return {
...state,
products1
};
Change this line function ReducersForSeach(state = initialState3, action) { to
function ReducersForSeach(state = initialState3.products1, action) {
Try this::
products1 = {
id: "456",
abbreviation: "456",
case_no: "456",
created_dt: "31/01/2018",
last_updated: "11:43:45"
});
return {
...state,
products1: [...state.products1, products1]
};
Normally you would put the payload in there and send through that object when you are dispatching the action.
Like this::
return {
...state,
products: [...state.products, action.payload]
};
This is assuming that products is an array and you want to .push() a new item into that array.
I hope this helps. :)
UPDATE::
Here is a full example of the reducer that i use for products, that adds a new one in every time it's called.
I do send the object through in the action.
Reducer:
import {
PRODUCTS,
} from '../constants/ActionTypes';
let products = []
if (localStorage.getItem('products') != undefined) {
products = JSON.parse(localStorage.getItem('products'))
}
const initialState = {
products: products
};
export default function (state = initialState, action) {
switch (action.type) {
case PRODUCTS:
localStorage.setItem('products', JSON.stringify([...state.items, action.payload]))
return {
...state,
products: [...state.items, action.payload]
}
break;
default:
// console.log('Products reducer called: default');
// console.log(action.type);
break;
}
return state
}
action::
export const addProduct = (obj) => {
return {
type: PRODUCTS,
payload: obj
};
};
This is the action call in the index.js file.
this.props.dispatch(addProduct(newItemObj));
I hope this makes sense.
Related
I have a e-commerce store when user click on add item to cart, it add to redux store.
I have a checkout page where it lists all added item, if user click on remove item, it should be removed from store. Each item in checkout page have key as uuid.
I am using uuid to keep unique id for items when added to cart.
Suppose I have 2 items in cart , if user click delete in out item , it dispatches uuid of the item to store,
const initialState = {
items: []
};
const cart = (state = initialState, action) => {
switch (action.type) {
...
case "DELETE": {
return {
items: [
state.items.filter((item) => {
item.uuid !== action.payload;
}),
],
};
...
};
export default cart;
But after this all my cart items gets deleted and get warning in console that all items should have unique id.
Each item in state consists of below kind objects,
[{uuid: "uniqueid_1", name:'first'},{uuid: "uniqueid_2", name:'first'}]
I dispatches an remove action consists of payload as uuid (ie,"uniqueid_1")
The complete code reference below,
Action
import { v4 as uuidv4 } from "uuid";
const add = (item, quantity) => {
return {
type: "ADDTOCART",
payload: {
uuid: uuidv4(),
id: item.id,
image: item.photo,
name: item.name,
price: item.price,
quantity: quantity,
},
};
};
const remove = (item) => {
return {
type: "DELETE",
payload: item,
};
};
const removeall = () => {
return {
type: "REMOVEALL",
};
};
export default { add, remove, removeall };
REDUCER
const initialState = {
items: [],
};
const cart = (state = initialState, action) => {
switch (action.type) {
case "ADDTOCART": {
return { items: [...state.items, action.payload] };
}
case "DELETE": {
return {
items: [
state.items.filter((item) => {
item.uuid !== action.payload;
}),
],
};
}
case "REMOVEALL": {
return { items: [] };
}
default:
return state;
}
};
export default cart;
REMOVE ACTION
onClick={() => {dispatch(cartAction.remove(product.uuid));}}
Your mistake could be that you now have an array too much. You can change it to:
const initialState = {
items: []
};
const cart = (state = initialState, action) => {
switch (action.type) {
...
case "DELETE": {
return {
items:
state.items.filter((item) => {
item.uuid !== action.payload.uuid;
}),
};
...
};
export default cart;
This is because state.items.filter will already return an array of objects, so you don't need to wrap it in []
Also Due to the fact that you are using the item as payload for the delete action you need to use item.uuid !== action.payload.uuid for comparison
Add the Spread Syntax(...) here:
items: [
...state.items.filter((item) => {
item.uuid !== action.payload;
})
],
because this:
[{uuid: "uniqueid_1", name:'first'},{uuid: "uniqueid_2", name:'first'}].filter(x=>x.uuid!=="uniqueid_1")
return an array.
your items array it inside another array at the end, like this:
[[{uuid: "uniqueid_1", name:'first'}]]
And you need this:
[{uuid: "uniqueid_1", name:'first'}]
I have removed curly braces from filtering and it worked
items: [
state.items.filter((item) =>
item.uuid !== action.payload;
),
],
So I basically started learning Redux and wanted to create a simple store app where you can add the phones to the cart. I have created a state object and within it, I have created an array with objects with a list of items in the store. I wanted to update on [+] click the number of items ordered but it doesn't work for now. I have been struggling with that for 1 hour already and still do not see where the problem might be.
Reducer looks like that:
const initialState = {
liked:0,
cart:0,
item: [
{
id:1,
name: 'Iphone 8',
price: 2000,
desc: 'The new Iphone 8 available at our store!',
orderedNum: 0
},
{
id:2,
name: 'Iphone 6',
price: 1500,
desc: 'The new Iphone 6 available at our store!',
orderedNum: 0
},
{
id:3,
name: 'Samsung S8',
price: 2200,
desc: 'The new Samsung S8 available at our store!',
orderedNum: 0
},
{
id:4,
name: 'Xiaomi Mi 6',
price: 1400,
desc: 'The new Xiaomi Mi 6 available at our store!',
orderedNum: 0
},
{
id:5,
name: 'Pocophone P1',
price: 2100,
desc: 'The new Pocophone P1 available at our store!',
orderedNum: 0
},
{
id:6,
name: 'Nokia 3310',
price: 999,
desc: 'The new Nokia 3310 available at our store!',
orderedNum: 0
},
]
}
const reducer = (state = initialState, action) => {
const newState = {...state};
switch(action.type) {
case 'ADD_NUM':
return state.item.map((el, index) => {
if(el.id === action.id ){
return {
...el,
orderedNum: el.orderedNum + 1
}
}
return el;
})
default:
break;
}
return newState;
}
export default reducer;
I have the action:
const mapStateToProps = state => {
return {
item: state.item
}
}
const mapDispatchToProps = dispatch => {
return {
addNum: () => dispatch ({
type: 'ADD_NUM',
id: this.props.id,
value: 1
})
}
I have tried it in a different ways but I believe it could be the problem with nesting in the reducer.
Could someone advise?
Lets start with your reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_NUM":
return {
// destruct and return a new object otherwise react wont update the UI
...state,
item: state.item.map(el =>
el.id === action.id
? { ...el , orderedNum: el.orderedNum + action.value }
: el
)
};
default:
return state;
}
};
mapDispatchToProps
const mapDispatchToProps = dispatch => {
return {
// add id to addNum
addNum: id =>
dispatch({
type: "ADD_NUM",
id,
value: 1
})
};
};
Items component
const Items = ({ item, addNum }) => (
item.map(el => (
<div key={el.id}>
<h1>{el.name}</h1>
<h3>{el.price}</h3>
<h3>{`orderedNum: ${el.orderedNum}`}</h3>
// add the id to addNum
<button onClick={() => addNum(el.id)}>+</button>
</div>
))
);
CodeSandBox
Your reducer should look something like this:
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.item];
case 'REMOVE_ITEM':
return state.filter(({ id }) => id !== action.id);
default:
return state;
}
};
As pointed out in the comments the above will work for an initial state that is an array but not an object... Here is how to handle it if it's an object (with two helper methods from lodash mapKeys and omit:
export default (state = {}, action) => {
switch (action.type) {
case FETCH_ITEMS:
return { ...state, ...mapKeys(action.payload, 'id') };
case FETCH_ITEM:
return { ...state, [action.payload.id]: action.payload };
case CREATE_ITEM:
return { ...state, [action.payload.id]: action.payload };
case EDIT_ITEM:
return { ...state, [action.payload.id]: action.payload };
case DELETE_ITEM:
return omit(state, action.payload);
default:
return state;
}
};
I am trying to do a setting screen with two separate action functions. one for lb\kg and another for weight and calories, however if I change one of the functions the other gets hidden? or removed from screen.
Where I should be getting 150 lb, I can get either 150, or lb which are both created from separate actions. What am I doing wrong?
Where the reducer props get displayed.
<Text style={globalStyles.defaultText}>
Current Weight:{" "}
<Text>
{personalWeight} <--- be like 150
{weightProp} <---- be like lb
</Text>
{"\n"}
</Text>
actions page:
export const DISTANCE_SETTINGS = "DISTANCE_SETTINGS";
export const WEIGHT_SETTINGS = "WEIGHT_SETTINGS";
export const ALLINPUT_SETTINGS = "ALLINPUT_SETTINGS";
// settings button lists
export const settingsAction = (buttonGroupName, actionId) => dispatch => {
switch (buttonGroupName) {
case "distance":
dispatch({
type: DISTANCE_SETTINGS,
payload: actionId
});
break;
case "weight":
dispatch({
type: WEIGHT_SETTINGS,
payload: actionId
});
break;
default:
alert("There was an error somewhere");
}
};
// settings input options weight/calories
export const settingsInputs = data => dispatch => {
dispatch({
type: ALLINPUT_SETTINGS,
payload: data
});
};
reducers page:
import {
DISTANCE_SETTINGS,
WEIGHT_SETTINGS,
ALLINPUT_SETTINGS
} from "../actions/settingsAction";
export const inititalState = {
profile: {
weight: 150,
caloriesBurned: 100,
distanceSettings: "",
weightSettings: ""
}
};
export default function(state = inititalState, action) {
switch (action.type) {
case DISTANCE_SETTINGS:
return {
...state,
profile: {
distanceSettings: action.payload
}
};
case WEIGHT_SETTINGS:
let conversion = `${action.payload === "Pounds" ? "lb" : "kg"}`;
return {
...state,
profile: {
weightSettings: conversion
}
};
case ALLINPUT_SETTINGS:
return {
...state,
profile: {
weight: action.payload.weight,
caloriesBurned: action.payload.calories
}
};
default:
return state;
}
}
Your reducers should be:
export default function(state = inititalState, action) {
switch (action.type) {
case DISTANCE_SETTINGS:
return {
...state,
profile: {
...state.profile, // You don't have it
distanceSettings: action.payload
}
};
case WEIGHT_SETTINGS:
let conversion = `${action.payload === "Pounds" ? "lb" : "kg"}`;
return {
...state,
profile: {
...state.profile, // You don't have it
weightSettings: conversion
}
};
case ALLINPUT_SETTINGS:
return {
...state,
profile: {
...state.profile, // You don't have it
weight: action.payload.weight,
caloriesBurned: action.payload.calories
}
};
default:
return state;
}
}
How do I target which element/object in array in a reducer I have to pass the action to? I am trying to figure out what action I take on the reducer. Or how do I change affect the number value in the object. So I am rendering the array in a View with TouchableOpacity which onPress Im calling the action dispatch as following:
import React, { Component } from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
class NewData extends Component {
render(){
const renData = this.props.newData.map((data, idx) => {
return (
<View key={idx}>
<TouchableOpacity
onPress={() => this.props.incNum(data.id)}
//not sure how to toggel dispatch call above, for onPress
>
<Text style={styles.welcome}>{data.id} - {data.name} - {data.number}</Text>
</TouchableOpacity>
</View>
)
});
return(
<View>
{renData}
</View>
);
}
}
function mapStateToProps (state) {
return {
newData: state.newData
};
};
//I think I need to create a toggle here:
//Something like: this.props.newData.id ? incNum : decNum
function mapDispatchToProps (dispatch) {
return {
incNum: (id) => {
dispatch({
type: "INC_NUM",
payload: id
})
},
decNum: (id) => {
dispatch({
type: "DEC_NUM",
payload: id
})
}
};
};
export default connect( mapStateToProps, mapDispatchToProps )( NewData )
My Reducer:
const initialState = [
{
id: '01',
name: 'Name One',
number: 11
},
{
id: '02',
name: 'Name Two',
number: 22
},
{
id: '03',
name: 'Name Three',
number: 33
}
]
export default function newData (state = initialState, action) {
switch (action.type) {
case "INC_NUM":
return {
...state,
number: this.state.number ++ // <== need help here.
}
case "DEC_NUM":
return {
...state,
number: this.state.number --
}
default:
return state
}
}
I think <TouchableOpacity onPress={() => this.props.incNum(data.id)} will bind the id to be changed. And passing the payload: id will pass the id to the reducer. But how do I update the value in the reducer? I can do without the toggle for now. I want to learn about passing the value and updating accordingly in the reduder.
Thank you.
EDIT1 :
So my reducer now is:
const initialState = {
1: {
id: '01',
name: 'Name One',
number: 11
},
2: {
id: '02',
name: 'Name Two',
number: 22
}
}
export default function newData (state = initialState, action) {
switch (action.type) {
case "INC_NUM":
return {
...state,
[action.payload]: {
...state[action.payload],
number: state[action.payload].number++ <== Line 58 in ERROR screenshot
}
case "DEC_NUM":
return {
...state,
[action.payload]: {
...state[action.payload],
number: state[action.payload].number--
}
default:
return state
}
}
And the way I'm rendering it is:
class NewData extends Component {
render(){
const renData = Object.keys(this.props.newData).map((key, idx) => {
let data = this.props.newData[key]
return (
<View key={idx}>
<TouchableOpacity
onPress={() => this.props.updateNum(data.id)}
>
<Text style={styles.welcome}>{data.id} - {data.name} - {data.number}</Text>
</TouchableOpacity>
</View>
)
});
return(
<View>
{renData}
</View>
)
}
}
function mapDispatchToProps (dispatch) {
return {
updateNum: (id) => {
INC_NUM({
type: "INC_NUM",
payload: id
})
}
};
};
Everything in the reducer is appearing as expected. But when I click and the action is called, I get an error:
your reducer seems to be lot more complex that it needs to be, below is the simpler approach, hope this helps.
const initialState = {
1: {
id: '01',
name: 'Name One',
number: 11
},
2: {
id: '02',
name: 'Name Two',
number: 22
}
}
export default function newData (state = initialState, action) {
switch (action.type) {
case "INC_NUM":
const newState = {...state};
newState[action.payload].nubmer++;
return newState;
case "DEC_NUM":
const newState = {...state};
newState[action.payload].nubmer--;
return newState;
default:
return state
}
}
Your problem appears to be in your reducer and the structure of your state.. you're not using the ID to identify which keyval pair to update.
First I'd modify your initialState to be an object and use the id as your keys:
const initialState = {
1: {name: 'Name One', number: 11},
2: {name: 'Name Two', number: 22},
3: {name: 'Name Three', number: 33}
}
Then in your reducer:
case "INC_NUM":
return {
...state,
[action.id]: {
...state[action.id],
number: state[action.id].number++
}
}
I know the syntax is convoluted, but the spread operator is nice shorthand for what otherwise would be even harder to read.
I'm doing a todo list using redux and I want to add sub todo list for each todo, but I can't understand why my line for my reducer is not working (see above)
Can you help me please ?
import { combineReducers } from 'redux'
import { ADD_TODO, TOGGLE_TODO, ADD_SUB_TODO} from '../constants/ActionTypes'
import _ from 'lodash'
const initialState = {
todos : []
}
export function todo(state, action) {
switch (action.type) {
case ADD_TODO:
return {
id: action.id,
text: action.text,
completed: false
};
default:
return state;
}
}
export function allTodo (state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [
...state.todos,
{
id: action.id,
text: action.text,
completed: false,
subtodo:[]
}
]
};
case ADD_SUB_TODO:
console.log("REDUCER")
console.log(...state.todos)
return {
...state,
// THIS LINE DOES'NT WORK :
...state.todos[0].subtodo: [ ...state.todos[0].subtodo, {
id: action.id,
text: action.text
}]
};
default:
return state;
}
};
export default combineReducers({
allTodo
})
this line is not working :
...state.todos[0].subtodo: [ ...state.todos[0].subtodo, {
id: action.id,
text: action.text
}]
this is my sub todo object :
{
id: action.id,
text: action.text
}
Assuming that action.parent contains the index of parent todo, try this.
case ADD_SUB_TODO:
let subtodo = {id: action.id, text: action.text}
let subtodos = [...state.todos[action.parent].subtodo, subtodo]
let todo = _.assign({}, state.todos[action.parent], {subtodo: subtodos})
return _.assign({}, state, {todos: [...state.todos, todo]})
if you want to try this with one todo the way you have in your question,
case ADD_SUB_TODO:
let subtodo = {id: action.id, text: action.text}
let todo = _.assign({}. state.todos[0], {subtodo: [subtodo]})
return _.assign({}, state, {todos: [...state.todos, todo]})
Thanks to Mad Wombat, this is the final code :
case ADD_SUB_TODO:
let subtodo = {id: action.id, text: action.text}
let subtodoList = _.concat(...state.todos[action.parentId].subtodo, subtodo)
let todo = _.assign({}, state.todos[action.parentId], {subtodo: subtodoList})
return {
...state,
...state.todos[action.parentId] = todo,
todos: [
...state.todos
]
};