How to write a function in reducer in React-Redux - reactjs

I am working on a React project, In that I have to perform Increment operation and I am writing
That's logic in a store, but I don't now how to stop it's Increment after 6.
So someone please help me how to write logic to stop Incrementing after 6, its initial value
Is 1. When I am clicking the plus icon it has to Increment but it has to stop Incrementing
after number 6. For this project I am using React-Redux.
This is Buttontypes.js
export const additionVariable = 'ADDITION'
This is Buttonactions.js
import { additionVariable } from './Buttontypes';
export const addition = () => {
return {
type: additionVariable
}
}
This is Buttonreducer.js
import { additionVariable } from './Buttontypes';
const initialState = {
value: 1
}
const additionReducer = (state = initialState, action) => {
switch(action.type) {
case additionVariable: return {
...state,
value: state.value + 1
}
default: return state
}
}
export default additionReducer
This is store.js
import { createStore } from 'redux';
import mainReducer from './Button/Buttonreducer';
const store = createStore(mainReducer);
export default store
This is App.js
import React from 'react';
import './App.css';
import { connect } from 'react-redux';
import { addition } from './Redux/Button/Buttonactions';
function App(props) {
return (
<div className="container">
<div className='row'>
<div className='adultbox pr-2'>
<div className='onebox'>
<span className='adultminus'>-</span>
</div>
<div className='twobox'>
<span className='adultvalue'>{props.value}</span>
</div>
<div className='threebox'>
<span onClick={props.Fun} className='adultplus'>+</span>
</div>
</div>
</div>
</div>
);
}
const mapStateToProps = state => {
return {
value: state.value
}
}
const mapDispatchToProps = dispatch => {
return {
Fun: () => dispatch(addition())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(App)

try this for your reducer :
import { additionVariable } from './Buttontypes';
const initialState = {
value: 1
}
const additionReducer = (state = initialState, action) => {
switch(action.type) {
case additionVariable: return {
...state,
value: state.value <= 6 ? state.value + 1 : state.value
}
default: return state
}
}
export default additionReducer

In addition to what already suggested about the reducer, I would advice you to add a check in mapDispatchToProps and (optional) disable the + if the value is equal to 6:
const mapDispatchToProps = dispatch => {
return {
Fun: (value) => {
if (value < 6 {
dispatch(addition())
}
}
}
}
Then, in App.js:
<div className='threebox'>
<span onClick={() => props.Fun(props.value)} className='adultplus'>+</span>
</div>

Related

value of state increment twice in reducer.js file

Order is an array of Objects and there is a key 'count' inside each variable. When ADD_ITEM_IN_ORDER case is executed than the count of particular object should be increment by 1. But, in this case, when the particular item is already present in the array than the value of count of that item incrementing by 2, which should not happen(it should increment by 1).
reducer.js
export const initialState = {
Order: [],
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { ...state, Order: tempOrder1 };
}
else {
console.log("New item added");
return {
...state,
Order: [...state.Order, action.item]
};
}
default:
return state;
}
};
export default reducer;
action file
import React from 'react';
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { useStateValue } from '../StateProvider';
import { db } from '../firebase';
const CartMenu = (props) => {
const [{ Order }, dispatch] = useStateValue();
const add = () => {
dispatch({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
dispatch({
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
StateProvider.js
//setup data layer
// we need this to track the basket data
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext();
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
(
<StateContext.Provider value = {useReducer(reducer,initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
The context API broadcasts updates when it notices a change in the value. Since you are invoking useReducer within the value props, that returns an array (state value and dispatch function), it is this, that is likely causing double dispatch. I recommend you re-write your Provider logic.
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext({ //Make sure to export this
Order:[], //Will be consuimg the state value from here
addItem:(arg)=>{} //This function will be hooked to a dispatch function below
});
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
{
const [state,dispatchFn] = useReducer(reducer,initialState)
const addItemHandler = (item) => {
dispatchFn(item)
}
return(<StateContext.Provider value = {{Order:state.Order,addItem:addItemHandler}}>
{children}
</StateContext.Provider>)
};
You can then wrap your root component with the context provider component StateProvider so that all the components can access the state values.
In your index.js wrap the component like this:
import {StateProvider} from './path/to/provider'
ReactDOM.render(<StateProvider><App/></StateProvider>,doucment.getElementById("root"))
You can then use the context state and dispatch function from useContext hook by, passing the context variable.
In your action file:
import {useContext},React from 'react'; //Import use context
import {StateContext} from './path/to/state/context'
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { db } from '../firebase';
const CartMenu = (props) => {
const order_ctx = useContext(StateContext);
const add = () => {
order_ctx.addItem({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
order_ctx.rmItem({ //Note: rmItem method is not added to createContext, but this is just to demonstrate how state update methods can be invoked by using the context instance.
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
And also since, your state contains only an array of orders, you can just return the updated array without having to override the previous state.
In your reducer:
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { Order: [...tempOrder1] }; //Return updated array only
}
else {
console.log("New item added");
return {
Order: [...state.Order, action.item] //Return updated array only
};
}
default:
return state;
}

React-redux no result apears

I'm using React redux to build a div of a number that you can raise or to subtract.
enter image description here
the problem is that everything seems to look good
but there is no result on the browser
reducer-> index.js:
import { combineReducers } from 'redux'
const sumReducer = (sum = 0, action) => {
if (action.type === 'ADD' || action.type === 'SUB') {
let x = action.payload;
return x;
}
return sum;
}
export default combineReducers({
sum: sumReducer
});
**action-> index.js**
export const add = (sum) => {
return {
type: 'ADD',
payload: sum + 1
}
}
export const sub = (sum) => {
return {
type: 'SUB',
payload: sum - 1
}
}
my component is: upDownSum.js
import React from 'react'
import { connect } from 'react-redux'
import { add, sub } from '../action'
class UpDownSum extends React.Component {
render() {
console.log(this.props);
console.log("ghj");
return (
<div key="1">
<button onClick={() => sub(this.props.sum)}>-</button>
<div>{this.props.sum}</div>
<button onClick={() => add(this.props.sum)}>+</button>
</div>
)
}
}
const MapStateToProps = (state) => {
return {
sum: state.sum
}
}
export default connect(MapStateToProps, {add: add ,sub: sub})(UpDownSum);
app.js
import './App.css';
import UpDownSum from './upDownSum'
function App() {
return (
<UpDownSum/>
);
}
export default App;
Thank you!
You are mapping dispatch to props, meaning to dispatch actions you will need to make sure to use props and not just the imported actions:
return (
<div>
<button onClick={() => this.props.sub(this.props.sum)}>-</button>
<div>{this.props.sum}</div>
<button onClick={() => this.props.add(this.props.sum)}>+</button>
</div>
)

TypeError: Redux Action is not a function

I was trying to dispatch a redux action(setMarker) into a component with mapDispatchToProps, but whenever I invoke that action I get this
TypeError: setMarker is not a function
import React from 'react';
import { connect } from 'react-redux';
import { setMarker } from '../../redux/map/map.actions';
import './suggestion.styles.scss';
const Suggestion = (location, { setMarker }) => {
const { area, city, address } = location;
return (
<div className="suggestion" onClick={() => console.log(setMarker)}>
<i className="fas fa-map-marker-alt fa-2x"></i>
<div className="address">
<h3 className="location-title">{`${area}, ${city}`}</h3>
<p className="location-desc">{address}</p>
</div>
</div>
);
};
const mapDispatchToProps = {
setMarker,
};
export default connect(null, mapDispatchToProps)(Suggestion);
My actions file contains the following code
import mapActionTypes from './map.types';
export const setMarker = (location) => ({
type: mapActionTypes.SET_MARKER,
payload: location,
});
And my reducer file contains the following code
import mapActionTypes from './map.types';
const INITIAL_STATE = {
location: {
lat: 23.810331,
lng: 90.412521,
},
};
const mapReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case mapActionTypes.SET_MARKER:
return {
...state,
location: action.payload,
};
default:
return state;
}
};
export default mapReducer;
you need to import it like this: { setMarker } so that it becomes a destructured prop
or you could do
const Suggestion = (props) => {
then call
props.setMarker()
also if you want another tip, you can do this for mapDispatch now
const mapDispatchToProps = {
setMarker
}
much easier to read :)
If Suggestion is a react component in the functional form then you would get props as an argument. You should use object destructuring syntax if you want to access them directly or you can use props.setMarker.
const Suggestion = ({location, setMarker}) => {
const { area, city, address } = location;
return (
<div className="suggestion" onClick={() => setMarker(location)}>
<i className="fas fa-map-marker-alt fa-2x"></i>
<div className="address">
<h3 className="location-title">{`${area}, ${city}`}</h3>
<p className="location-desc">{address}</p>
</div>
</div>
);
};

State doesnt get reset on route change - React-redux-router

Hi I am creating a Server side react app. I have multiple routes using the same components and reducers. One specific reducer is an ItemsPerPage dropdown. I am getting the value from the reducer and passing it as a payload to a post request to a database to fetch that many results.
In one page, I get 50 results, and when I navigate to the other page using the same reducer, the state value should be 10 but rather its 50. How do I reset the state when going to another page?
I am using { LOCATION_CHANGE } from 'react-router-redux'
routerReducer.js:
import { LOCATION_CHANGE } from 'react-router-redux';
const initialState = {
location: '',
};
export const routeReducer = (state = initialState, action) => {
switch (action.type) {
case LOCATION_CHANGE:
return {
...state,
...action.payload,
};
default:
return state;
}
};
ItemsPerPageDropdown:
import React, {PropTypes, Component} from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import { changeItemsPerPage } from '../../actions/index';
class ItemsPerPage extends Component {
handleChange = (event) => {
this.props.changeItemsPerPage(event.target.value)
};
render() {
const itemsPerPage = [10, 20, 50];
return (
<div className={'table-item-count-container'}>
<label className={'filter-label items-by-page-label'}>Items Per Page:</label>
<select id="items-per-paghe"
className="form-control items-by-page-select"
onChange={this.handleChange}
>
{_.map(itemsPerPage, (item, index) => <option key={index}>{item}</option>)}
</select>
</div>
)
}
}
export default connect(null, {changeItemsPerPage})(ItemsPerPage);
ItemsPerPageReducer:
import * as ACTION_TYPES from '../consts/action_types';
const initialState = {
items: 10,
};
export const itemsPerPageReducer = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPES.CHANGE_ITEMS_PER_PAGE:
return {
...state,
items: action.data,
};
default:
return state;
}
};
Main page using this component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom'
import { withRouter } from 'react-router'
import { bindActionCreators } from 'redux';
import _ from 'lodash';
import Moment from 'react-moment';
import Header from '../../components/Header/Header';
import DataTable from '../../components/DataTable/DataTable';
import InstructionList from '../../components/InstructionList/InstructionList';
import { getData, postData } from '../../actions';
import { columns } from './Report1Columns';
import * as instructions from '../../consts/instructionText';
class Report1 extends Component {
params = {
userId: this.props.corpId,
filteredAppID: '',
envClassification: '',
Status: '',
startRow: 0 + this.props.activePage, //1
numRows: this.props.itemsPerPage,
sortCol: 'application_name',
sortDir: 'asc',
};
loadPage = () => {
if(this.props.postData) {
this.props.postData('https://localhost:3001/reports/report1/', this.params);
}
};
componentDidMount = () => {
this.props.postData('https://localhost:3001/reports/report1/', this.params);
};
componentWillReceiveProps = (nextProps) => {
if (nextProps.itemsPerPage !== this.props.itemsPerPage) {
this.params.numRows = nextProps.itemsPerPage;
this.loadPage();
}
if(nextProps.activePage !== this.props.activePage) {
this.params.startRow = ((nextProps.activePage - 1) * this.props.itemsPerPage) +1;
this.loadPage();
}
if(nextProps.searchTerm !== this.props.searchTerm) {
this.params.filteredAppID = nextProps.searchTerm;
this.loadPage();
}
if(nextProps.envClassification !== this.props.envClassification) {
this.params.envClassification = nextProps.envClassification === 'All' ? '' : nextProps.envClassification;
this.loadPage();
}
if(nextProps.watchtowerStatus !== this.props.Status) {
this.params.watchtowerStatus= nextProps.watchtowerStatus=== 'Manage & Analyze' ? '' : nextProps.watchtowerStatus;
this.loadPage();
}
};
render() {
return (
<div>
<Header title={ 'Report 1' } />
<InstructionList instructions={ instructions.Report1 } />
{this.props.data &&
<DataTable
keyField={ 'Report1' }
columns={ columns }
paginatedData={ this.props.data }
totalRows={ this.props.totalRows }
placeholder={ 'ID/NAME' }
showStatus={true}
/>}
</div>
);
}
}
const mapStateToProps = state => ({
/**
* The date to be passed to the table
*/
data: _.get(state.isFetchingPost, 'data.rows'),
/**
* Total Rows count of data
*/
totalRows: state.isFetchingPost.data.total_rows,
/**
* Items Per Page
*/
itemsPerPage: state.itemsPerPageReducer.items,
/**
* Item which is searched
*/
searchTerm: state.searchReducer.searchTerm,
/**
* The current active page for pagination
*/
activePage: state.changeActivePageReducer.activePage,
/**
* The value of the dropdown selected in Environment Classification
*/
envClassification: state.envClassificationReducer.envClassification,
/**
* The value of the dropdown selected in Status
*/
watchtowerStatus: state.watchTowerStatusReducer.watchTowerStatus
});
const mapDispatchToProps = dispatch => bindActionCreators({ getData, postData }, dispatch);
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Report1));
If you can see in the images below, I navigate between the routes and state still remains same instead of taking initial state.
Add a case for LOCATION_CHANGE to your ItemsPerPageReducer to reset the count when the location changes:
import * as ACTION_TYPES from '../consts/action_types';
import { LOCATION_CHANGE } from 'react-router-redux';
const initialState = {
items: 10,
};
export const itemsPerPageReducer = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPES.CHANGE_ITEMS_PER_PAGE:
return {
...state,
items: action.data,
};
case LOCATION_CHANGE:
return {
...state,
items: 10,
};
default:
return state;
}
}
};
If you only want it to reset on certain location changes, you can check action.payload to test if it is a route you actually want to reset on.

How to reuse reducer with same action using redux-subspace

I'm building a small app using React, semantic-ui-react, redux-subspace.
I have many different tables and when the user clicks on one of the cells, the value supposed to come out on the console but the result is undefined when it clicked. I'm trying to reuse reducer. Same action with different instances.
I appreciate any comments that guide me to right direction.
PartA.js
This component renders Tables and wrapped with <SubspaceProvider>.
<Segment inverted color='black'>
<h1>Age </h1>
{ this.state.toggle ?
<SubspaceProvider mapState={state => state.withSpouseAge} namespace="withSpouseAge">
<TableForm
headers={spouse_ageHeaders}
rows={spouse_ageData}
namespace={'withSpouseAge'}
/>
</SubspaceProvider> :
<SubspaceProvider mapState={state => state.withoutSpouseAge} namespace="withoutSpouseAge">
<TableForm
headers={withoutSpouse_ageHeader}
rows={withoutSpouse_ageData}
namespace={'withoutSpouseAge'}
/>
</SubspaceProvider> }
TableForm.js
This component return Table with the Data and this is where I want to implement onClick method.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Table } from 'semantic-ui-react';
import { select } from '../actions';
const shortid = require('shortid');
class TableForm extends Component {
constructor(props){
super(props);
this.state = {
activeIndex: 0,
}
this.handleOnClick = this.handleOnClick.bind(this);
this.isCellActive = this.isCellActive.bind(this);
};
isCellActive(index) {
this.setState({ activeIndex: index });
}
handleOnClick(index, point) {
this.isCellActive(index);
this.props.onSelect(point);
};
tableForm = ({ headers, rows }) => {
const customRenderRow = ({ factor, point, point2 }, index ) => ({
key: shortid.generate(),
cells: [
<Table.Cell content={factor || 'N/A'} />,
<Table.Cell
content={point}
active={index === this.state.activeIndex}
textAlign={'center'}
selectable
onClick={() => this.handleOnClick(index, point)}
/>,
<Table.Cell
content={point2}
textAlign={'center'}
selectable
/>
],
});
return (
<Table
size='large'
padded
striped
celled
verticalAlign={'middle'}
headerRow={this.props.headers}
renderBodyRow={customRenderRow}
tableData={this.props.rows}
/>
)
};
render() {
console.log(this.props.withSpouseAgePoint);
const { headers, rows } = this.props;
return (
<div>
{this.tableForm(headers, rows)}
</div>
);
}
};
const mapDispatchToProps = (dispatch) => {
return {
onSelect: (point) => {dispatch(select(point))},
}
}
const mapStateToProps = state => {
return {
withSpouseAgePoint: state.withSpouseAge,
withSpouseLoePoint: state.withSpouseLoe,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TableForm);
Action
import {
SELECT,
} from './types';
export const select = (points) => ({
type: 'SELECT',
points,
});
Reducer.js
import { SELECT } from '../actions/types';
const INITIAL_STATE = {
point: 0,
};
const selectionReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SELECT':
return { ...state, point: state.point + action.points };
default:
return state;
}
};
export default selectionReducer;
Reducer index.js
import { createStore, combineReducers } from 'redux';
import { subspace, namespaced } from 'redux-subspace';
import selectionReducer from './selectionReducer';
import toggleReducer from './toggleReducer';
const reducers = combineReducers({
withSpouseAge: namespaced('withSpouseAge')(selectionReducer),
withSpouseLoe: namespaced('withSpouseLoe')(selectionReducer),
withSpouseOlp: namespaced('withSpouseOlp')(selectionReducer),
withSpouseOlp2: namespaced('withSpouseOlp2')(selectionReducer),
withSpouseExp: namespaced('withSpouseExp')(selectionReducer),
withoutSpouseAge: namespaced('withoutSpouseAge')(selectionReducer),
withoutSpouseLoe: namespaced('withoutSpouseLoe')(selectionReducer),
withoutSpouseOlp: namespaced('withoutSpouseOlp')(selectionReducer),
withoutSpouseOlp2: namespaced('withoutSpouseOlp2')(selectionReducer),
withoutSpouseExp: namespaced('withoutSpouseExp')(selectionReducer),
toggle: toggleReducer,
});
Update
I added below TableForm component
const mapDispatchToProps = (dispatch) => {
return {
onSelect: (point) => {dispatch(select(point))},
}
}
const mapStateToProps = state => {
return {
withSpouseAgePoint: state.withSpouseAge,
withSpouseLoePoint: state.withSpouseLoe,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TableForm);
implement this.props.onSelect(point) on handleOnClick. It still shows me the same result undefined. I checked store states by getState(). consloe.log. I think my implementation of redux-subspace is wrong. I uploaded whole TableForm component and also updated reducer. Please help me out!
update 2
I replaced mapStateToProps and it worked like a magic. Thank you again #JustinTRoss.
but there is another problem, all the states are coming out with the same value when I clicked on the cell.
. my plan is each state has their own value stored.
const mapStateToProps = state => {
return {
withSpouseAgePoint: state,
withoutSpouseAge: state,
}
}
You have already namespaced your component to withSpouseAge and mapped state to state.withSpouseAge in your SubspaceProvider. Thus, you're calling the equivalent of state.withSpouseAge.withSpouseAge (undefined).
Another potential issue is the signature with which you are calling connect. From the snippet you provided, there's no way to be sure of the value of 'select'. Typically, connect is called with 2 functions, often named mapStateToProps and mapDispatchToProps. You are calling connect with a function and an object. Here's an example from http://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/#connect :
import {connect} from 'react-redux'
const TodoItem = ({todo, destroyTodo}) => {
return (
<div>
{todo.text}
<span onClick={destroyTodo}> x </span>
</div>
)
}
const mapStateToProps = state => {
return {
todo : state.todos[0]
}
}
const mapDispatchToProps = dispatch => {
return {
destroyTodo : () => dispatch({
type : 'DESTROY_TODO'
})
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoItem)
Additionally, there's one other issue, although it isn't affecting you yet: You're calling this.tableForm with 2 arguments (headers and rows), while you defined the this.tableForm function to take a single argument and destructure out 'headers' and 'rows' properties.

Resources