I'm using react and redux and I'm trying a simple example.
If I clicked the button, then the number should increment by 1.
But when I currently click the button you'll see all element gonna + 1
how can i fix that?...
class Menu extends Component {
componentDidMount() {
this.props.getItems();
}
plus = () =>{
this.props.getplus();
}
render() {
const {item, count} = this.props.item
return (
<div>
{item.map(items => {
<div>{items.example}</div> <buttom onClick={this.plus}> + </button>
<div>{count}
</div> }
</div>
) }
const mapStateToProps = (state) => ({
item: state.item
})
export default connect(mapStateToProps , { getItems, getplus }) (Menu);
itemAction.js
export const getItems = () =>{
return {
type: GET_ITEMS
} }
export const getplus = () => {
return {
type: PLUS_ITEMS
} }
Reducer.js
const initialState = {
item: [
{
example:"example1"
},
{
example:"example2"
},
{
example:"example3"
},
],
count:0
}
export default function (state = initialState, action) {
switch(action.type){
case GET_ITEMS:
return {
...state
}
case PLUS_ITEMS:
return {
...state,
count:state.count + 1
}
default:
return state;
}
}
I see two errors in your code, replace this:
const {item, count} = this.props.item
with this:
const {item, count} = this.props
And map also count to your props:
const mapStateToProps = (state) => ({
item: state.item
count: state.count
})
Related
I'm a little confused on passing an object to the redux store. I have successfully created the store and can add items from the initial state. The function also fires when called
Action:
import { GET_ITEM } from './OrderTypes'
export const getItem = (payload) => {
return {
type: GET_ITEM,
payload: { payload }
}
}
Reducer:
import { GET_ITEM } from './OrderTypes'
const initialState = {
orderList: [],
}
const orderReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ITEM: return {
...state,
orderList: [...state.orderList, action.payload]
}
default: return state
}
}
export default orderReducer
Component:
class TestComponentextends Component {
pushItem = () => {
this.props.getItem({
payload: 'test object'
})
}
render() {
return (
<input type='button' value='test btn' onClick={this.pushItem} />
)
}
}
const mapStateToProps = state => {
return {
orderList: state.orderList
}
}
const mapDispatchToProps = dispatch => {
return {
getItem: () => dispatch(getItem())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TestComponent)
What happens: An empty object is added to the orderList array.
What I want to happen: Store the object in pushItem in the orderList array.
Your mapDispatchToProps doesn't pass the arguments to the action creator (see mapDispatchToProps function arguments - 2nd example):
const mapDispatchToProps = dispatch => ({
getItem: (...args) => dispatch(getItem(...args))
})
Even easier is to let react-redux handle the mapping by using mapDispatchToProps as an object:
const mapDispatchToProps = {
getItem
}
The state Selectedlan don't update on the click.. I think it is an error of reducer. Specifically when I the function handleGreeceLan is called the action should be called and the reducer but I think only the action is called
The file that I make the call
import { changeLanGR, changeLanEN } from '../actions/toolbarActions';
const handleGreeceLan = () => {
changeLanGR();
};
const handleUKLan = () => {
changeLanEN();
};
<MenuItem onClick={handleUKLan}>
...
</MenuItem>
<MenuItem onClick={handleGreeceLan}>
...
</MenuItem>
...
const mapStateToProps = state => {
return { Selectedlan: state.Selectedlan };
};
export default connect(
mapStateToProps,
{ changeLanGR, changeLanEN }
)(Language);
Reducer
import { combineReducers } from 'redux';
import { LAN_GR, LAN_EN } from '../actions/types';
const selectLanguage = (state = LAN_GR, action) => {
if (action.type === LAN_GR) {
return { ...state, Selectedlan: LAN_GR};
}
else if (action.type === LAN_EN) {
return { ...state, Selectedlan: LAN_EN};
}
return state;
};
export default combineReducers({
Selectedlan: selectLanguage
});
Action
import { LAN_GR, LAN_EN } from './types';
export const changeLanGR = () => {
return {
type: LAN_GR
};
};
export const changeLanEN = () => {
return {
type: LAN_EN
};
};
You are mapping the dispatch function to props, meaning you need to call those functions mapped from props:
const Language = ({
className,
changeLanGR,
changeLanEN,
...rest
}) => {
const handleGreeceLan = () => {
changeLanGR();
};
const handleUKLan = () => {
changeLanEN();
};
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 want to use the connect method from react-redux to pass the props into react hoc high order function.
The wrapper function:
export const withInfiniteScroll = (Component) =>
class WithInfiniteScroll extends React.Component {
componentDidMount() {
window.addEventListener('scroll', this.onScroll, false)
}
componentWillUnmount() {
this.props.initPage()
window.removeEventListener('scroll', this.onScroll, false)
}
onScroll = () => {
(
(window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 500)
&& this.props.stocks.length
)
&& this.props.onPaginatedSearch()
}
render() {
return <Component {...this.props}/>
}
}
And the component is wrapped:
const StockCard = ({ stocks }) =>
stocks.errmsg === 'ok' ?
stocks.data.map (
(stock, i) =>
<CardContainer key={i}>
<p>{stock.cinvcode}</p>
<p>{stock.cinvname}</p>
</CardContainer>
) : ''
Get the hoc function:
const StockCardWithInfiniteScroll = withInfiniteScroll(StockCard)
The reducer:
const initState = {
...
stocks: {
errcode: '',
errmsg: '',
data: []
}
}
export const PAGE_INIT = 'PAGE_INIT'
export const initPage = () => ({type: PAGE_INIT, payload: 0})
export const STOCK_FETCH = 'STOCK_FETCH'
export const updateStock = (res) => ({type: STOCK_FETCH, payload: res})
export const onPaginated = (searchStock) => {
return(dispatch) => {
const searchStockNextPage = {
...searchStock,
pageindex: searchStock.pageindex + 1
}
// console.log(searchStockNextPage)
getStock(searchStockNextPage)
.then(res => dispatch(updateStock(res)))
.then(res => console.log(res))
}
}
const stockReducer = (state = initState, action) => {
switch (action.type) {
...
case PAGE_INIT:
return {
...state,
searchStock: {...state.searchStock, page: 0}
}
case STOCK_FETCH:
return {
...state,
stocks: state.stocks.data.concat(action.payload.data)
}
default:
return state
}
}
Last use the connect method to pass the props from redux:
export default connect(
(state) => ({
stocks: state.stock.stocks
}),
{onPaginated, initPage}
)(StockCardWithInfiniteScroll)
I can see the feedback stocks props in browser console, but the StockCard can not get it.
I have to display a table with lot of data. There is a pagination button which display 10 record per page. I have one button called "FETCH" on-click of it table is populating. How do load my table on load of the page.
action.js
import fetch from 'isomorphic-fetch';
export const ADD_BENEFICIARY = 'ADD_BENEFICIARY';
export const REMOVE_BENEFICIARY = 'REMOVE_BENEFICIARY';
export const ADD_PLAN_TO_COMPARE = 'ADD_PLAN_COMPARE';
export const REMOVE_PLAN_FROM_COMPARE = 'REMOVE_PLAN_COMPARE';
export const SHOW_PLAN_COMPARE = 'SHOW_PLAN_COMPARE';
export const NEXT_PAGE_PLAN_LIST = 'NEXT_PAGE_PLAN_LIST';
export const PREV_PAGE_PLAN_LIST = 'PREV_PAGE_PLAN_LIST';
export const REQUEST_PAGE_PLAN_LIST = 'REQUEST_PAGE_PLAN_LIST';
export const RECEIVE_PAGE_PLAN_LIST = 'RECEIVE_PAGE_PLAN_LIST';
export const showNextPageOfPlans = () => {
return {
type: NEXT_PAGE_PLAN_LIST
}
}
export const showPreviousPageOfPlans = () => {
return {
type: PREV_PAGE_PLAN_LIST
}
}
export const requestPageOfPlans = (startIdx, pageSize) => {
return {
type: REQUEST_PAGE_PLAN_LIST,
start: startIdx,
pageSize: pageSize
}
}
export const receivePageOfPlans = (startIdx, json) => {
return {
type: RECEIVE_PAGE_PLAN_LIST,
start: startIdx,
plans: json
}
}
export const fetchPlans = (startIdx, pageSize) => {
var str = sessionStorage.getItem('formValue'); //JSON.stringify(formValues);
return function (dispatch) {
dispatch(requestPageOfPlans(startIdx, pageSize));
return fetch('http://172.16.32.57:9090/alternatePlans/plans/list/', {method: 'post', body: str, headers: new Headers({'Content-Type': 'application/json'}) })
.then(response => response.json())
.then(json =>
dispatch(receivePageOfPlans(startIdx, json))
)
}
}
reducer.js
import { REQUEST_PAGE_PLAN_LIST, RECEIVE_PAGE_PLAN_LIST,
NEXT_PAGE_PLAN_LIST, PREV_PAGE_PLAN_LIST } from './actions';
const initialPaging = {
startIndex: 0,
lastIndex: 0,
pageSize: 10
}
const paging = (state = initialCurrentPage, action) => {
switch (action.type) {
case NEXT_PAGE_PLAN_LIST:
if (state.startIndex+state.pageSize <= state.lastIndex) {
return { ...state, startIndex: state.startIndex+state.pageSize };
}
else {
return state;
}
case PREV_PAGE_PLAN_LIST:
if (state.startIndex-state.pageSize >= 0) {
return { ...state, startIndex: state.startIndex-state.pageSize };
}
else {
return state;
}
case REQUEST_PAGE_PLAN_LIST:
return { ...state, isFetching: true };
case RECEIVE_PAGE_PLAN_LIST:
return { ...state, isFetching: false };
default:
return state;
}
}
var initialPlans = [];
const plans = (state = initialPlans, action) => {
switch (action.type) {
case RECEIVE_PAGE_PLAN_LIST:
return action.plans.plans;
default:
return state;
}
}
const allReducers = (state = {}, action) => {
let items = plans(state.plans, action);
return {
plans: items,
paging: paging({ ...initialPaging, ...state.paging, lastIndex: items.length-1 }, action)
}
}
export default allReducers;
P.S. I am new to react-redux. Official Documentation is good but very less explanation is given.
You call it from the componentDidMount() of the react component for your page, or wherever it makes sense. So in that component file:
import { requestPageOfPlans } from 'actions';
import React from 'react';
import { connect } from 'react-redux';
class MyComponent extends React.Component {
componentDidMount() {
this.props.requestPageOfPlans();
}
}
export default connect((state) => state, { requestPageOfPlans })(MyComponent);
So the key here is the connect setup. The first parameter is how you wish to transform state (your reducers) into data for props. Adjust that as you need to. The second one is what actions you wish to bind. You can also import dispatch manually, but I personally like this pattern. It sets up the actions as props on the component, and you can call it that way. Just pass the arguments to it as you need to. This page here: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options explains the connect function in more detail.
Edit
Given you're paginating on the front end, you'll need to adjust the connect mapStateToProps function to pass the pagination data down to the component, and then loop through it to display what you need. Personally I would do the pagination on the back end, and just do new requests for each page. Depends on how many records you're expecting to have.
So in the connect do something like this:
export default connect((state) => {
return {
startIndex: state.paging.lastIndex,
pageSize: state.paging.pageSize,
lastIndex: state.paging.lastIndex,
plans: state.plans
};
}, { requestPageOfPlans })(MyComponent);
Then in your component, loop through the plans using those indexes:
render() {
const plans = [];
const maxIndex = Math.min(this.props.startIndex + this.props.pageSize, this.props.lastIndex);
for (let i = this.props.startIndex || 0; i < maxIndex; i++) {
const plan = this.props.plans[i];
plans.push(<Plan key={plan.id} plan={plan} />);
}
return (
<ul>{plans}</ul>
);
}
Making some assumptions again on how you plan to render it, if you have a component called Plan, etc. But that's roughly how you can work with it.
componentDidMount is a good timing to load your fist page.
BTW, In my opinion, the page change is not the action, it is just some param for load your data.
ActionCreator
const userurl = '/rest/users'
export const loadUsers = (page = 0) => {
return (dispatch) => {
axios.get(userurl+"?page="+page)
.then(function(resp) {
dispatch(loadUsersOK(resp.data._embedded.users, resp.data.page));
})
.catch(function(error) {
...
})
};
}
Component JSX
<span style={{marginRight:15, fontSize: 14, color: '#333'}}>{page.number*page.size+1}-{page.number*page.size+page.size} of {' '} {page.totalElements}</span>
<FlatButton style={flatButtonStyle} icon={<NavigationChevronLeft/>} onTouchTap={this.prevPage} />
<FlatButton style={flatButtonStyle} icon={<NavigationChevronRight/>} onTouchTap={this.nextPage} />
Component Handlers
prevPage() {
const { page } = this.props.users;
this.props.loadUsers(page.number - 1);
}
nextPage() {
const { page } = this.props.users;
this.props.loadUsers(page.number + 1);
}
Connect
const mapStateToProps = state => {
return {
users: state.users
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
loadUsers: (page=0) => dispatch(loadUsers(page))
}
}
const Wrapped = connect(
mapStateToProps,
mapDispatchToProps
)(Users)
ONLY ONE ACTION
export const loadUsersOK = (result, page) => ({
type: LOAD_USERS_OK,
result,
page
})
May it helps.