I just starting on React, and starting to do a todo list. It has functionalities like add, modify(done/pending) and remove task.
Below is my action
export const ADD_TASK = 'ADD_TASK';
export const TOGGLE_TASK = 'TOGGLE_TASK';
export const REMOVE_TASK = 'REMOVE_TASK';
export const FILTER_TASK = 'FILTER_TASK';
let todoId = 1;
export function addTask(task) {
let todo = {
id: todoId++,
name: task,
status: 0,
visible: true
};
return {
type: ADD_TASK,
payload: todo
};
}
export function toggleTask(id) {
return {
type: TOGGLE_TASK,
payload: id
};
}
export function removeTask(id) {
return {
type: REMOVE_TASK,
payload: id
};
}
export function filterTask(id) {
return {
type: FILTER_TASK,
payload: id
};
}
and my reducer :
import { ADD_TASK, TOGGLE_TASK, REMOVE_TASK, FILTER_TASK } from '../actions/index';
let filterStatus = -1;
//initial state is array because we want list of city weather data
export default function(state = [], action) {
// console.log('Action received', action);
const toggling = function (t, action) {
if(t.id !== action)
return t;
return Object.assign({}, t, {
status: !t.status
})
};
const visibility = function(t, action) {
return Object.assign({}, t, {
visible: action === -1 ? true : t.status == action
})
};
switch(action.type) {
case ADD_TASK :
//return state.concat([ action.payload.data ]); //in redux reducer dont modify the state, instead create a new one baesd on old one. Here concat is create a new of old one and add a new data
return [ action.payload, ...state];
case TOGGLE_TASK :
return state.map(s => toggling(s, action.payload)).map(t => visibility(t, filterStatus));
case REMOVE_TASK :
return state.filter(s => { return (s.id != action.payload) } );
case FILTER_TASK :
filterStatus = action.payload;
return state.map(t => visibility(t, action.payload));
}
return state;
}
I read somewhere that modifying state is reducer is a bad practice, yet I feel that I'm doing it in my reducer.
Could anyone suggest the correct way of handling add,remove, update value state in the reducer ?
Thank you
i think you need two reducers: one for managing visibility stuff, one for adding, toggling and removing tasks.
so for the second part i would like do this.
export const ADD_TASK = 'ADD_TASK';
export const TOGGLE_TASK = 'TOGGLE_TASK';
export const REMOVE_TASK = 'REMOVE_TASK';
let todoId = 1;
export addTask = (text) => ({
type: ADD_TASK,
id: todoId++,
text
});
export toggleTask = (id) => ({
type: TOGGLE_TASK,
id
});
export removeTask = (id) => ({
type: REMOVE_TASK,
id
});
export function todosReducer(state = [], action) {
switch(action.type) {
case ADD_TASK :
return [...state, {id: action.id, text: action.text, completed: false}];
case TOGGLE_TASK :
return state.map(task => task.id !== action.id ? task : {...task, completed: !task.completed});
case REMOVE_TASK :
return state.filter(task => task.id !== action.id);
}
return state;
}
Related
My issue is that I want to fetch all products from the database and set them into the Redux initial state, to do this I did an action SET_PRODUCTS_LIST and in the action. payload I simply passed the products fetched in the component (I am using next js), all works fine but when I try to fire another action like ADD_PRODUCT_TO_CART the products in the initial state are gone which it results impossible to add more than 1 product to the cart.
Inside my component:
function Header({ cartProps, setProducts }) {
useEffect(async () => {
const products = await getProducts();
setProducts(products);
}, []);
}
const mapStateToProps = (state) => {
return {
cartProps: state.cartState,
};
};
export default connect(mapStateToProps, {
setProducts,
})(Header);
the action to set products:
import { SET_PRODUCTS_LIST } from "./types";
export const setProducts = (products) => {
return (dispatch) => {
dispatch({
type: SET_PRODUCTS_LIST,
payload: products,
});
};
};
My cart reducer:
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
maybe I am completely doing wrong the logic about fetching the products in order to have them in the initial state.
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
...state
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
You should always return state but in ADD_PRODUCT_TO_CART case you return only
{
itemNumbers: state.itemNumbers + 1,
}
so you need to add ...state before itemNumbers: state.itemNumbers + 1,
Once again: I have items in redux store -> array which contains objects {id: str, val: bool}
I want to change obj value by id, I did it this way, it works but I think I over complicated:
export const setItems = items => {
return {
type: SET_DATA,
payload: items
}
}
export const changeItem = id => {
return (dispatch, getState) => {
let cpItems = getState().items.items.map( el => // creating copy
id === el._id ? {...el, val: !el.val} : el
)
dispatch(setItems(cpItems)) //sending copy
}
}
and reducer:
const itemsReducer = (state = initialState, action) => {
switch (action.type){
case SET_DATA:
return {
...state,
items: action.payload
}
default: return state
}
}
is this how is it done in redux ?
The logic is fine, and you can create actions that contain logic.
However, I would not use a thunk for a sync action. I would move the logic to the reducer, and create a specific action type for changeItem:
export const changeItem = id => ({
type: CHANGE_ITEM,
payload: id
})
const itemsReducer = (state = initialState, action) => {
switch (action.type) {
case SET_DATA:
return {
...state,
items: action.payload
}
case CHANGE_ITEM:
return {
...state,
items: state.items.map(el => // creating copy
id === el._id ? {...el, val: !el.val} : el
)
}
default:
return state
}
}
I'm trying to store a dictionary in react-redux in react-native.
So my action looks like this :
let data = {};
export const setData = (pData) => ({
type: 'SET',
data: pData,//I don't know how to store the data in data declared in parent
});
export const getData = () => ({
type: 'GET',
data: data,
});
And my reducer looks like this :
const items = (state = [], action) => {
switch (action.type) {
case 'SET':
return [
//I don't know how to set the data here
];
case 'GET':
return state;
default:
return null;
}
};
export default items;
I looked in many tutorial on YouTube, they just you need to paste this, and boom.
If I get cleared with one dictionary, I think I can work with others.
This part almost right. You don't need "GET" to get data and this part let data = {} should be in reducer;
export const setData = (pData) => ({
type: 'SET',
data: pData,
});
/*
export const getData = () => ({
type: 'GET',
data: data,
});
*/
Reducer
const initState = {
data:[],
anotherSate:[]
}
const rootReducer = (state = initState, action) => {
switch(action.type){
case 'SET': {
return {
...state, // if you have more states
data: [action.data, ...state.data]
}
}
default:
return state;
}
}
export default rootReducer;
You can get your "Data". "New" component
//Your component code
//...
this.props.data // here is your "data"
//...
const mapStateToProps = (state) => {
return {
data: state.data,
}
}
export default connect(mapStateToProps)(NewComponent);
In order to check if your Reducer works, try to add something in your initState and extract the data in NewComponent
const items = (state = [], action) => {
switch (action.type) {
case 'SET':
return {
...state, // adding the previous state first
data: action.data // here data can be any keyword you want to save your dictionary in
}
case 'GET':
return state;
default:
return null;
}
};
Summary
In order to learn Redux, I am incorporating some state, actions, reducers, and trying to see how they are used in React Components.
I have set up a test object...
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
...and aim to:
increment the navigationCount by 1 when visiting pages
add or subtract from someNumber
push() & pop() elements from someList.
Versions
Currently using gatsby ^2.5.0, react ^16.8.6, and react-redux ^6.0.1.
Code
actions & reducers
import { combineReducers } from 'redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from './actionTypes.js';
// state
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
// action creators returning actions
export const pageIncrementer = navigationCount => {
return {
type: PAGE_INCREMENT,
navigationCount,
};
};
export const numberAdder = numberToAdd => {
return {
type: NUMBER_INCREASE,
numberToAdd,
};
};
export const numberMinuser = numberToMinus => {
return {
type: NUMBER_DECREASE,
numberToMinus,
};
};
export const listPusher = itemToAdd => {
return {
type: LIST_PUSH,
itemToAdd,
}
};
export const listPopper = () => {
return {
type: LIST_POP,
}
};
// reducers
const pageIncrementReducer = (state = initialState, action) => {
switch (action.type) {
case PAGE_INCREMENT:
return Object.assign({}, ...state, {
navigationCount: action.navigationCount+1
});
default:
return state.navigationCount;
}
};
const numberChanger = (state = initialState, action) => {
switch (action.type) {
case NUMBER_INCREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber+action.numberToAdd,
});
case NUMBER_DECREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber-action.numberToMinus,
});
default:
return state.someNumber;
};
};
const listChanger = (state = initialState, action) => {
switch (action.type) {
case LIST_POP:
return Object.assign({}, ...state, {
someList: state.someList.pop(),
});
case LIST_PUSH:
return Object.assign({}, ...state, {
someList: state.someList.push(action.itemToAdd),
});
default:
return state.someList;
}
}
// store
const rootReducer = combineReducers({
pageIncrementReducer,
numberChanger,
listChanger,
});
export default rootReducer;
React Component
import React from 'react';
import Layout from '../components/common/Layout.jsx';
import LandingBanner from '../components/landing/LandingBanner.jsx';
import LandingNavgrid from '../components/landing/LandingNavgrid.jsx';
import LandingApp from '../components/landing/LandingApp.jsx';
import { connect } from 'react-redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from '../state/actionTypes';
class LandingPage extends React.Component {
constructor(props){
super(props);
this.state = {
appliedNum: 2000,
};
}
componentDidMount(){
// this.props.pageIncrement(); // => numberChanger returned undefined
// this.props.numberIncrease(4444); // => pageIncrementReducer returned undefined
// this.props.numberDecrease(4444); // => pageIncrementReducer returned undefined
// this.props.listPush(4444); // => pageIncrementReducer returned undefined
this.props.listPop();
}
render(){
return (
<Layout>
<LandingBanner/>
<LandingNavgrid/>
<LandingApp/>
</Layout>
)
}
}
const filterNumbers = (list=[]) => {
console.log('filterNumbers list: ', list);
return list.filter(listElement => !!Number(listElement));
};
const mapStateToProps = (state, ownProps) => {
return {
someNumber: state.someNumber,
someList: filterNumbers(state.someList),
navigationCount: state.navigationCount,
};
};
const mapDispatchToProps = (dispatch) => {
return {
pageIncrement: () => dispatch({ type: PAGE_INCREMENT }),
numberIncrease: () => dispatch({ type: NUMBER_INCREASE }),
numberDecrease: () => dispatch({ type: NUMBER_DECREASE }),
listPush: () => dispatch({ type: LIST_PUSH }),
listPop: () => dispatch({ type: LIST_POP }),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LandingPage);
Errors
redux.js:449 Uncaught Error: Given action "LIST_POP", reducer
"pageIncrementReducer" returned undefined. To ignore an action, you
must explicitly return the previous state. If you want this reducer to
hold no value, you can return null instead of undefined.
first of all, you always need to return state on the default switch case.
default:
return state;
I am using multiple reducers in my project and then combining them with combineReducers() function and have all actions in single file. when i dispatch the action, it is returning me state values to undefined. I think It can't find out because of multiple reducerse. But when i use single reducer file. It is working fine. Can anyone please tell me what the issue.It is how i am combining the reducers.
const rootReducer = combineReducers({
isMobileReducer,
imageSliderReducer
})
and now passing to store, like below:
let store = createStore(rootReducer,applyMiddleware(thunk))
and in frontend how i am accessing state
const mapStateToProps = (state) => ({
images: state.images,
isMobile: state && state.isMobile
})
imageSliderReducer.js
import {
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from '../actions/actionTypes'
const initialState = {
images:[],
error:null
}
const imageSliderReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_BEGIN:
return {...state,error:null}
case FETCH_IMAGES_SUCCESS:
return {...state,images:action.payload.images}
case FETCH_IMAGES_FAILURE:
return {...state,error:action.payload.error,images:[]}
default:
return state
}
}
export default imageSliderReducer;
isMobileReducer.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
} from '../actions/actionTypes'
const initialState = {
isMenuOpen: null,
isMobile: false
}
const isMobileReducer = (state = initialState, action) => {
switch (action.type) {
case OPEN_MENU:
return {...state, isMenuOpen: true}
case CLOSE_MENU:
return {...state, isMenuOpen: false}
case SET_DEVICE_TYPE:
return {...state, isMobile: action.isMobile}
default:
return state
}
}
export default isMobileReducer;
actionCreator.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from './actionTypes'
export function openMenu(isMobile) {
return {
type: OPEN_MENU
}
}
export function closeMenu(isMobile) {
return {
type: CLOSE_MENU
}
}
export function setDeviceType (isMobile) {
return {
type: SET_DEVICE_TYPE,
isMobile: isMobile
}
}
export function fetchImages() {
return dispatch => {
dispatch(fetchImagesBegin());
return fetch("https://7344.rio.com/wp-json/customapi/homeslider")
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchImagesSuccess(json.posts));
return json.posts;
})
.catch(error => dispatch(fetchImagesFailure(error)));
};
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const fetchImagesBegin = () => ({
type: FETCH_IMAGES_BEGIN
});
export const fetchImagesSuccess = images => ({
type: FETCH_IMAGES_SUCCESS,
payload: { images }
});
export const fetchImagesFailure = error => ({
type: FETCH_IMAGES_FAILURE,
payload: { error }
});
Try using this:
const mapStateToProps = (state) => ({
images: state.imageSliderReducer.images,
isMobile: state.isMobileReducer.isMobile
})