I am trying to create a sort button which when clicked will sort me menu cards alphabetically. My question is how should I have the sort function coded in the Reducer and Actions? I added pseudo-code for sorting in the Reducer as well. When I click the button I am getting "(TypeError): state.slice is not a function".
Edit:
Added my button component and main Container.
Actions:
export const sortMenus = () => {
return dispatch => {
dispatch({ type: "LOADING_MENUS" });
fetch(`/api/menus`)
.then(res => res.json())
.then(responseJSON => {
dispatch({ type: "SORT_MENUS", cards: responseJSON });
});
};
};
Reducer:
export default function MenusReducer(
state = {
cards: [],
loading: false
},
action
) {
switch (action.type) {
case "LOADING_MENUS":
return {
...state
};
case "ADD_MENUS":
return {
...state,
cards: action.cards
};
case "SORT_MENUS":
return state.slice().sort(function(menu1, menu2) {
if (menu1.name < menu2.name) return -1;
if (menu1.name < menu2.name) return 1;
return 0;
});
default:
return state;
}
}
Button Component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { sortMenus } from ".././actions/dataActions";
import Row from "react-bootstrap/Row";
import Container from "react-bootstrap/Container";
class SortButton extends Component {
constructor() {
super();
this.state = { menus: [] };
}
handleMenuSort = e => {
this.props.sortMenus()
};
render() {
return (
<Container>
<Row>
<div>
<button id="sort-button" title="Sort Menus" onClick= {this.handleMenuSort}>Sort Menus</button>
</div>
</Row>
</Container>
)
}
}
const mapStateToProps = state => {
return {
menus: state.menus
}
};
const mapDispatchToProps = dispatch => {
return {
sortMenus: params => dispatch(sortMenus(params)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(SortButton)
Container:
class MainContainer extends Component {
displayCards = () => {
switch(this.props.path) {
case "menus":
return (this.props.menus.cards.map(card => (
<NavLink style={{ color: "black" }} to={`/menus/${card.id}`} key={card.id}><MenuCard view={this.props.displayObject} info={card} /></NavLink>
)));
default:
return (<div>Empty</div>)
}
};
render() {
return (
<CardColumns>
{this.displayCards()}
</CardColumns>
)
}
}
const mapStateToProps = state => {
return {
menus: state.menus
}
};
const mapDispatchToProps = dispatch => {
return {
displayObject: (id, category, type) => dispatch(displayObject(id, category, type)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(MainContainer)
Your state is an object, not an array. You likely mean to sort the stored cards array.
state.cards.slice(... instead of state.slice(...
case "SORT_MENUS":
return state.cards.slice().sort(function(menu1, menu2) {
if (menu1.name < menu2.name) return -1;
if (menu1.name < menu2.name) return 1;
return 0;
});
Side note: You may also want to clear/set your loading state upon successful data fetching. ;)
EDIT
You are mapping undefined state within mapStateToProps, then mapping over it in the component. Change mapStateToProps to access the correct defined property.
const mapStateToProps = state => ({
cards: state.cards,
});
Then you can iterate over the new cards prop.
case "menus":
return (this.props.cards.map(card => (
<NavLink
style={{ color: "black" }}
to={`/menus/${card.id}`}
key={card.id}
>
<MenuCard view={this.props.displayObject} info={card} />
</NavLink>
)));
You can simply store the fetched menu in application state.
You can have standalone action say SORT_MENU_BY_ALPHABET.
You can simply dispatch this action on button handler as well as on Ajax success. this dispatch may not have any payload associated.
hope it helps.
in reducer you defined state as object and you're trying to do array operation on it. state.slice().
slice is a function available for arrays. so its throwing error.
you should be doing
state.cards.slice().sort((a,b)=> a-b)
Related
I want the states of my textinput to be dispatched to the reducer and change the state of of that reducer by the new state. I also want to be able to view these changed states in a new screen (globally) using mapStateToProps. I am roughly new to redux and I have tried everything possible, but I can seem to get it to work.
Is there any way to do that? I researched and found examples but not the way I want to do it.
I clarify my code is just an example so that you understand what I want to do, do not take it as a guide as I do not know if it works that way
Below are the important part of my code:
Process.Js
import { connect } from 'react-redux';
import {
receiverNameChange,
receiverPhoneChange,
totalParcelsChange } from './actions';
class Process extends Component {
static navigationOptions = {
header: null,
}
constructor(props) {
super(props);
this.state = {
totalParcels: 1,
receiverPhone: '',
receiverName: '',
};
}
onChangeTotalParcels(number) {
const totalParcels = parseInt(number);
if (number.length === 0) {
this.setState({ totalParcels: '' });
} else {
this.setState({ totalParcels });
}
this.props.totalParcelsChange(number);
}
onChangeReceiverNumber = (receiverPhone) => {
this.setState({
receiverPhone
});
this.props.receiverPhoneChange(receiverPhone);
}
onChangeReceiverName = (receiverName) => {
this.setState({
receiverName
});
this.props.receiverNameChange(receiverName);
}
render() {
return (
<View style={styles.AndroidSafeArea}>
<InputField
placeholder={"Enter receiver's name"}
onChangeText={this.onChangeReceiverName}
value={this.state.receiverName}
/>
<InputField
keyboardType={'phone-pad'}
onChangeText={this.onChangeReceiverNumber}
value={this.state.receiverPhone.toString()}
/>
<InputField
keyboardType={'phone-pad'}
onChangeText={this.onChangeTotalParcels}
value={this.state.totalParcels.toString()}
/>
</View>
);
}
}
const mapDispatchToProps = (dispatch) => ({
totalParcelsChange: number => {
dispatch(totalParcelsChange(number));
},
receiverNameChange: receiverName => {
dispatch(receiverNameChange(receiverName));
},
receiverPhoneChange: receiverPhone => {
dispatch(receiverPhoneChange(receiverPhone));
},
});
export default connect(null, mapDispatchToProps)(Process);
Receive.js
import { connect } from 'react-redux';
import {
receiverNameChange,
receiverPhoneChange,
totalParcelsChange } from './actions';
class Receive extends Component {
static navigationOptions = {
header: null,
}
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<View style={styles.AndroidSafeArea}>
<Text>{this.props.receiverNameChange}</Text>
<Text>{this.props.receiverPhoneChange}</Text>
<Text>{this.props.totalParcelsChange}</Text>
</View>
);
}
}
export default connect(null, null)(Receive);
Reduce.js
import { GET_TOTAL_PARCELS, GET_RECEIVER_NAME, GET_RECEIVER_PHONE } from './actions/types';
const initialState = {
receiverName: '',
receiverPhone: '',
number: 0,
};
const ProcessReducer = (state = initialState, action) => {
switch (action.type) {
case GET_TOTAL_PARCELS:
return {
...state,
number: action.payload
};
case GET_RECEIVER_PHONE:
return {
...state,
receiverPhone: action.payload
};
case GET_RECEIVER_NAME:
return {
...state,
receiverName: action.payload
};
default:
return state;
}
};
export default ProcessReducer;
Actions
import { GET_RECEIVER_NAME, GET_RECEIVER_PHONE, GET_TOTAL_PARCELS } from './types';
export const receiverNameChange = (receiverName) => {
return {
type: GET_RECEIVER_NAME,
payload: receiverName
};
};
export const receiverPhoneChange = (receiverPhone) => {
return {
type: GET_RECEIVER_PHONE,
payload: receiverPhone
};
};
export const totalParcelsChange = (number) => {
return {
type: GET_TOTAL_PARCELS,
payload: number
};
};
Kindly correct me where necessary.
I in order for your component in Receive.js to access the state from redux, you should have a mapStateToProps, use it like so:
class Receive extends Component {
...
render() {
return (
<View style={styles.AndroidSafeArea}>
<Text>{this.props.receiverName}</Text>
<Text>{this.props.receiverPhone}</Text>
<Text>{this.props.number}</Text>
</View>
);
}
}
}
const mapStateToProps = state => {
return {
receiverName: state.receiverName,
receiverPhone: state.receiverPhone,
number: state.number
}
}
export default connect(mapStateToProps, null)(Receive);
To have a single source of truth in your Process.Js, you should remove the local state in your Process component and replace the values same as we did in Receive component and you can remove setState.
Hope this helps your problem.
i have to implement chat system in react i m doing it first time and i m stuck.i have to pass page no to backend api to get new data every time.and i have to pass page no to api on scroll. i m using
[1]: https://www.npmjs.com/package/react-infinite-scroller
i m getting total data count and 9 data array per page from api.scroll upto which total count is available and when user scroll to top is should load more.i have tried lots of module but failed to implement pagination on scroll.using react-infinite-scroll module i m getting page no but its not working as i want.Please suggest me right way of doing it
here is my component code
const mapStateToProps = state => ({
users: state.sidebarUser.users,
total:state.sidebarUser.total,
routing: state.routing,
converSationId: state.getConversationId.data
});
const mapDispatchToProps = dispatch => ({
loadUserList: (page={}) => (dispatch(getSideBarUser(page))),
getConversationId: (userId) =>
dispatch(getConversationId(userId)),
loadUserContent: id => dispatch(UserChatList(id))
});
class SidebarContainer extends Component {
constructor(props) {
super(props);
this.state={
isLoading:false,
sidebar:[],
page:0,
hasMore: true,
}
this.getPosts=this.getPosts.bind(this);
}
componentDidMount() {
const {
location: { search }
} = this.props.routing;
let userId = new URLSearchParams(search).get("id");
this.props.loadUserList({page:1});
this.setState({page:this.state.page+1});
this.props.getConversationId(userId);
}
getPosts(page) {
console.log("pgae---->",page)
console.log("this.props--->",this.props.users)
this.props.loadUserList({page:page});
}
render() {
const { users } = this.props;
const {hasMore,sidebar} =this.state;
return (
<div className="chatting-user-list-section" ref={(ref) => this.scrollParentRef = ref} >
<InfiniteScroll
initialLoad={false}
pageStart={0}
loadMore={this.getPosts.bind(this)}
hasMore={hasMore}
getScrollParent={() => this.scrollParentRef}
threshold={520}
loader={<div className="loader">Loading ...</div>}>
<SidebarComponent users={users} listClicked={this.listClicked} />
</InfiniteScroll>
</div>)
}
}
export const Sidebar = connect(
mapStateToProps,
mapDispatchToProps
)(SidebarContainer)
and here is my Reducer
import { SIDEBAR_USERS_SUCCESS, SIDEBAR_USERS_FAILURE } from './ActionTypes';
const initialState = {
users: [],
total: 0
}
export const sidebarUser = (state = initialState, { type, payload }) => {
switch (type) {
case SIDEBAR_USERS_SUCCESS: {
return { ...state, ...payload };
}
case SIDEBAR_USERS_FAILURE: {
return { ...state, error: payload }
}
default:
return state;
}
};
This is surely very simple but I dont understand how it works. I try to bind checkbox with state and with state display different string. It is in React with Redux. The code below (bold font)
container:
class DropingList extends Component {
**conditionHandler() {
if(this.props.pet === 'cat'){
return "YEAH!!!"
}else {return null;}**
}
render() {
return (
<div>
<AddHimHer
click={this.props.onAddMan}
/>
{ this.props.pers.map(per =>(
<NewPerson
key={per.id}
click={() => this.props.onManDown(per.id)}
name={per.name}
age={per.age}
**animal={this.conditionHandler(this.props.pet)}**
/>
))
}
</div>
)
}
}
const mapStateToProps = state => {
return {
pers: state.persons
}
}
const mapDispatchToProps = dispatch => {
return {
onAddMan: (name,age,**pet**) => dispatch({type:actionTypes.ADD_MAN, data: {nam: name, ag: age, **superp: pet**}}),
onManDown: (id) => dispatch({type:actionTypes.MAN_DOWN, Id: id})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(DropingList);
component:
const NewPerson = (props) => (
<div onClick={props.click}>
<h1>Is {props.name} a SUPERHERO? ? ???</h1>
<h2>He is {props.age} years old</h2>
**<h1>{props.animal}</h1>**
</div>
);
export default NewPerson;
reducer:
const initState = {
persons: []
}
const personReducer = (state = initState,action) => {
switch (action.type) {
case actionTypes.ADD_MAN:
const newMan = {
id: Math.random(),
name: action.data.nam,
age: action.data.ag,
**pet: action.data.superp**
};
return {
...state,
persons: state.persons.concat(newMan)
};
case actionTypes.MAN_DOWN:
return {
...state,
persons: state.persons.filter(person => person.id !== action.Id)
};
}
return state;
};
export default personReducer;
I am still newbe in React and Redux. I think I have ommited something.
Could you tell me whats wrong with my code?
Issue is pet is the part of the object (each object of the array), not a separate prop so you need to use per.pet in map callback function, like this:
{this.props.pers.map(per =>(
<NewPerson
key={per.id}
click={() => this.props.onManDown(per.id)}
name={per.name}
age={per.age}
animal={this.conditionHandler(per.pet)} // here
/>
))}
Now you are passing the pet value to function conditionHandler, so no need to use this.props.pet inside that directly use pet, like this:
conditionHandler(pet) {
if (pet === 'cat') {
return "YEAH!!!"
} else {
return null;
}
}
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.
I want to reuse a react component and share common actions & reducers. My app dashboard has 3 Lists, where each List is fetched with different query param.
All 3 List components have the same props because all 3 of them are being re-rendered once I receive props from reducer.
Is there an dynamic way to display Lists based on query parameter? What I was thinking is to call different reducer in the action file based on the query param. Is there a better way?
Dashboard.js
const Dashboard = () => {
return(
<div>
<List query={QUERY1} />
<List query={QUERY2} />
<List query={QUERY3} />
</div>
)
}
List.js
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
componentWillMount() {
const { query } = this.props;
this.props.onLoad(query);
}
componentWillReceiveProps() {
const { items } = this.props;
this.setState({ items });
}
render() {
return (
<div>
{
this.state.items.map((item, index) =>
<Item data={item} key={index}/>
)
}
</div>
)
}
}
function mapStateToProps(state) {
const { items } = state.item;
return {
items
}
}
function mapDispatchToProps(dispatch) {
return {
onLoad: bindActionCreators(actions.load, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(List);
action.js
export function load(query) {
return function (dispatch) {
fetch(`//api.example.com/list?type=${query}&limit=10`)
.then((response) => response.json())
.then((data) => {
dispatch(setItems(data));
});
};
}
reducer.js
export default function(state = [], action) {
switch (action.type) {
case actionTypes.ITEMS_SET:
return setItems(state, action);
}
return state;
}
function setItems(state, action) {
const { items } = action;
return { ...state, items };
}
Note I am a contributor on redux-subpace
redux-subspace came around to solve this problem of having the same component displayed on the page, without crossing over the store values.
It has a feature called namespacing that will allow you to isolate your load actions and components from each other.
const Dashboard = () => {
return(
<div>
<SubspaceProvider mapState={state => state.list1}, namespace='list1'>
<List query={QUERY1} />
</SubspaceProvider>
<SubspaceProvider mapState={state => state.list2}, namespace='list'>
<List query={QUERY2} />
</SubspaceProvider>
<SubspaceProvider mapState={state => state.list3}, namespace='list3'>
<List query={QUERY3} />
</SubspaceProvider>
</div>
)
}
You'll also need to namespace your reducers, you can see how to do that here.