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
]
};
Related
If I have the initial state in my reducer like that:
let initialState = {
id: 1,
url: 'http://',
name: 'jjj',
List: []
};
and I want to assign values into this object from different action.types
e.g:
List: [
{
name: 'a1',
id: 1
},
{
name: 'a2',
id: 2
}
]
here is my reducer:
export default (state = initialState, action) => {
switch (action.type) {
case SET_NAME_A:
return { ...state, List: action.payload };
case SET_NAME_B:
return { ...state, List: action.payload };
default:
return state;
}
};
my action.payload giving to me value of input for name a and name b they are different inputs how I can do that?
You mean adding element to array in reducer?
return {
...state,
List: [...state.List, action.payload]
}
You need to spread reducer so that you just need to update the List :
export default (state = initialState, action) => {
switch (action.type) {
case SET_NAME_A:
return { ...state, List: [...state.List,action.payload ]}; //You can add more conditions base on action
case SET_NAME_B:
return { ...state, List: [...state.List,action.payload ] };
default:
return state;
}
};
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;
}
};
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.
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 followed Dan Abramov's code at https://github.com/tayiorbeii/egghead.io_redux_course_notes/blob/master/08-Reducer_Composition_with_Arrays.md
I am getting error message "Unexpected token at line 22" referring to the ...todo
Didn't think it's to do with Babel presets as ...state is working just fine. When I substitute ...todo with ...state inside the map function, it returns the same error.
///Reducer//
export default (state=[], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state,
{
id:action.id,
text: action.text,
completed:false
}
];
case 'TOGGLE_TODO':
return state.map(todo => {
if (todo.id !== action.id) {
return todo;
}
return {
...todo, //returning error
completed: !todo.completed
};
});
default:
return state;
}
}
My calling code:
it('handles TOGGLE_TODO', () => {
const initialState = [
{
id:0,
text: 'Learn Redux',
completed: false
},
{
id:1,
text: 'Go Shopping',
completed: false
}
];
const action = {
type: 'TOGGLE_TODO',
id: 1
}
const nextstate = reducer(initialState,action)
expect (nextstate).to.eql([
{
id:0,
text: 'Learn Redux',
completed: false
},
{
id:1,
text: 'Go Shopping',
completed: true
}
])
It is about presets, actually.
Array spread is part of ES2015 standard and you use it here
return [...state,
{
id:action.id,
text: action.text,
completed:false
}
];
However, here
return {
...todo, //returning error
completed: !todo.completed
};
you use object spread which is not part of the standard, but a stage 2 proposal.
You need to enable support of this proposal in Babel: https://babeljs.io/docs/plugins/transform-object-rest-spread/ or desugar it into Object.assign calls (see this part of the proposal)