What i want is to increase the count of each product, when it is opened(viewed), using react redux.
AllProductsPage.js(The page starts here)
import React, { useState } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Link } from "react-router-dom";
import ProductList from "./ProductList";
import Pagination from './Pagination'
import * as productActions from "../redux/actions/productActions";
import * as userActions from '../redux/actions/userActions'
import { Button } from "react-bootstrap";
import {FiSearch} from 'react-icons/fi'
import { Container, Row, Col} from "react-bootstrap";
const AllProductsPage =(props)=> {
const [quantity, showQuantity] = useState(true);
const [price, showPrice] = useState(true);
const [manufacturer,showManufacturer] = useState(true);
const data = {quantity,price,manufacturer};
const [search,setSearch]=useState("");
const loggedIn = props.loggedIn;
//Pagination Logic
const [currentPage,setCurrentPage] = useState(1)
const postsPerPage = 9
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = props.products.slice(indexOfFirstPost,indexOfLastPost)
//Change the page
const paginate =(pageNumber)=>{
setCurrentPage(pageNumber)
}
//const filteredSearch = props.products && props.products.filter(product=>product.name.toLowerCase().indexOf(search.toLowerCase())!==-1).sort( (a,b)=>(a.id>b.id)?1:-1 );
const filteredSearch = currentPosts && currentPosts.filter(product=>product.name.toLowerCase().indexOf(search.toLowerCase())!==-1).sort( (a,b)=>(a.id>b.id)?1:-1 );
return (
<div>
<div style={{"display":"flex","paddingTop":"30px"}} className="container">
{ loggedIn && <Link to="/addProduct"><Button variant="primary">Add Product</Button>{" "}</Link> }
<span style={{"marginLeft":"auto"}}><input type="text" onChange={event=>setSearch(event.target.value)}/> {" "} <FiSearch size="20px"/> </span>
</div>
<div style={{"display":"flex","justifyContent":"flex-end","alignItems":"space-between","paddingTop":"6px"}} className="container" >
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={quantity} onClick={()=>showQuantity(!quantity)}/>{" "}Quantity</label>
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={price} onClick={()=>showPrice(!price)}/>{" "}Price </label>
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={manufacturer} onClick={()=>showManufacturer(!manufacturer)}/>{" "}Manufacturer </label>
</div>
<hr></hr>
<div style={{minHeight:"100vh"}}>
<ProductList
products={filteredSearch}
data={data}
togglePrice={showPrice}
toggleQuantity={showQuantity}
toggleManufacturer={showManufacturer}
loggedIn={props.loggedIn}
/>
<br />
<Container>
<Row>
<Col></Col>
<Col xs="auto" sm="auto" md="auto" lg="auto">
<Pagination postsPerPage={postsPerPage} totalPosts={props.products.length} paginate={paginate} />
</Col>
<Col></Col>
</Row>
</Container>
</div>
<footer>
<p style={{"textAlign":"center","backgroundColor":"#333","color":"white","padding":"20px"}}>Copyright #2020, Rohit K F</p>
</footer>
</div>
);
}
function mapStateToProps(state, ownProps) {
return {
products: state.products,
users : state.users
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
userAction : bindActionCreators(userActions,dispatch)
};
}
export default (connect(mapStateToProps, mapDispatchToProps))(AllProductsPage);
ProductList.js(then it takes each product and passes it to Product.js)
import React from "react";
import Product from "./Product";
import { Container, Row, Col} from "react-bootstrap";
const chunk = (arr, chunkSize = 1, cache = []) => {
const tmp = [...arr]
if (chunkSize <= 0) return cache
while (tmp.length) cache.push(tmp.splice(0, chunkSize))
return cache
}
const ProductList = (props) => {
const productsChunks = chunk(props.products, 3)
const rows = productsChunks.map((productChunk, index) => {
const productsCols = productChunk.map((product, index) => {
return (
<Col xs="auto" sm="auto" md="auto" lg="auto" key={product.id} style={{"paddingBottom":"20px"}}>
<Product
key={product.id}
id={product.id}
quantity={product.quantity}
price={product.price}
name={product.name}
description={product.description}
manufacturer={product.manufacturer}
{...props}
/>
</Col>
);
});
return (
<Row key={index} style={{"paddingBottom":"20px"}}>
{productsCols}
</Row>
)});
return (
<Container>
{rows}
</Container>
)
}
export default ProductList;
Product.js(Here we show the each product)
import React,{useState} from "react";
import { Link } from "react-router-dom";
import { Prompt, withRouter } from "react-router";
import { connect } from "react-redux";
import * as productActions from "../redux/actions/productActions";
import { bindActionCreators } from "redux";
import { Card, Button } from "react-bootstrap";
import toastr from "toastr";
import EditProduct from './EditProduct'
import {MdDelete,MdVisibility,MdCreate} from 'react-icons/md'
const Product = (props) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const isLoggedIn = props.loggedIn
const checkUser = (e) => {
if (!isLoggedIn) {
e.preventDefault();
toastr.options = { positionClass: "toast-top-full-width",hideDuration: 300,timeOut: 2000,};
toastr.clear();
setTimeout(() => toastr.warning("Login to view details"), 0);
}
};
const deleteProduct = () => {
props.actions.deleteProduct(props.id)
};
//<Link to={'/ProductDetail/'+props.id} >
const product = {
id :props.id,name:props.name,quantity:props.quantity,description:props.description,manufacturer:props.manufacturer,price:props.price
}
return (
<>
<Card style={{ width: "18rem", "borderRadius":"30px","border":"3px solid" }}>
{isLoggedIn && (
<Prompt when={isLoggedIn}
message={(location) => location.pathname.includes("/ProductDetail/") ? `Are you sure you want to view the details ?` : true }
/>
)}
<Card.Body>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.name} </Card.Title>
{props.data.quantity && ( <Card.Text> Quantity : {props.quantity} </Card.Text> )}
{props.data.manufacturer && <Card.Text> Manufacturer : {props.manufacturer}</Card.Text>}
{props.data.price && <Card.Text>$ {props.price}</Card.Text>}
<div style={{ display: "flex", justifyContent: "space-around" }}>
<Link
to={{
pathname: `/ProductDetail/${props.id}`,
productName: {
id: props.id,
name: props.name,
price: props.price,
quantity: props.quantity,
description: props.description,
manufacturer: props.manufacturer,
},
}}
>
<Button variant="primary" onClick={(event) => checkUser(event)} style={{ "fontWeight":"bold" }} >
{!isLoggedIn && <span style={{"paddingRight":"5px"}}>View</span> }
{!isLoggedIn && <MdVisibility color="black"/> }
{isLoggedIn && <MdVisibility/>}
</Button>
</Link>
{isLoggedIn && <Button variant="success" style={{"fontWeight":"bold" }} onClick={() => handleShow()} ><MdCreate/></Button> }
{isLoggedIn && <Button variant="danger" style={{"fontWeight":"bold" }} onClick={() => deleteProduct()} ><MdDelete/> </Button>}
</div>
</Card.Body>
</Card>
<EditProduct show={show} handleClose={handleClose} actions={props.actions} product={product}/>
</>
);
};
function mapStateToProps(state, ownProps) {
return {
products: state.products,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(Product));
ProductDetail.js(When clicked on View, it goes to this page to view details of the product)
import React from 'react';
import { Link} from 'react-router-dom';
import {withRouter} from 'react-router'
import {Button, Card} from 'react-bootstrap'
const ProductDetail=(props)=>{
console.log(props)
const style={"display":"flex", "justifyContent":"center","alignItems":"center"}
return(
<div style={style}>
<Card style={{ width: "18rem","borderRadius":"30px" }}>
<Card.Body style={{style}}>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.location.productName.name} </Card.Title>
<Card.Text><strong>Quantity :</strong>{props.location.productName.quantity}</Card.Text>
<Card.Text><strong>Price :</strong>{props.location.productName.price}</Card.Text>
<Card.Text><strong>Manufacturer:</strong>{props.location.productName.manufacturer}</Card.Text>
<Card.Text><strong>Description :</strong>{props.location.productName.description}</Card.Text>
<div>
<Link to="/"><Button variant="primary" style={{ height: "6vh","fontWeight":"bold" }}>Back</Button></Link>
</div>
</Card.Body>
</Card>
</div>
);
}
export default withRouter(ProductDetail);
ProductReducer.js
import initialState from "./initialState";
import * as actionTypes from "../actions/actionTypes";
export default function productReducer(state = initialState.products, action) {
switch (action.type) {
case actionTypes.INIT:
return action.products;
case actionTypes.ADD:
return [...state, Object.assign({}, action.product)];
case actionTypes.DELETE:
return [...state.filter((product) => product.id !== action.id)];
case actionTypes.UPDATE:
return [
...state.filter((product) => product.id !== action.product.id),
Object.assign({}, action.product),
];
case actionTypes.VIEW:
return [
...state[action.product.id],
Object.assign({},action.product.view)
]
default:
return state;
}
}
ProductActions.js
import dataApi from "../../server/dataAPI";
import * as actionTypes from "../actions/actionTypes";
//======================LOADING A PRODUCT
export function loadProduct() {
return function (dispatch) {
return dataApi
.getAllProducts()
.then((products) => {
dispatch({ type: actionTypes.INIT, products });
})
.catch((error) => {
throw error;
});
};
}
//==========================ADDING A PRODUCT
export function addProduct(product) {
return function (dispatch) {
return dataApi
.addProduct(product)
.then((product) => {
dispatch({ type: actionTypes.ADD, product });
})
.catch((error) => {
throw error;
});
};
}
//==========================DELETE A PRODUCT
export function deleteProduct(id) {
return function (dispatch) {
return dataApi
.deleteProduct(id)
.then((product) => {
dispatch({ type: actionTypes.DELETE, id});
})
.catch((error) => {
throw error;
});
};
}
//==========================UPDATE A PRODUCT
export function updateProduct(product) {
return function (dispatch) {
return dataApi
.updateProduct(product)
.then((product) => {
dispatch({ type: actionTypes.UPDATE, product });
})
.catch((error) => {
throw error;
});
};
}
//Increase View Count of product
export function addView(product){
return function (dispatch){
return dataApi.addView(product)
.then(product=>{
dispatch({type:actionTypes.VIEW, product})
})
}
}
dataAPI.js(to add,delete,update to json server with axios)
import axios from 'axios'
class dataAPI {
static getAllProducts() {
return axios.get('http://localhost:4000/products?_sort=id&_order=asc').then(response=>response.data);
}
static addProduct(product) {
return axios.post('http://localhost:4000/products',product).then(response=>response.data);
}
static updateProduct(product){
return axios.patch('http://localhost:4000/products/'+product.id,product)
.then(response=>response.data);
}
static deleteProduct(id){
return axios.delete(`http://localhost:4000/products/${id}`).then(response=>response.data);
}
static getAllUsers(){
return axios.get('http://localhost:4000/users').then(response=>response.data);
}
static addUser(user) {
return axios.post('http://localhost:4000/users',user).then(response=>response.data);
}
}
export default dataAPI;
db.json(the file that contains all the data)
{
"products": [
{
"id": 1,
"name": "Moto G5 Ultra",
"quantity": 3,
"price": 10000,
"description": "Moto G5",
"manufacturer": "Motorola",
"views" : 0
},
{
"id": 2,
"name": "Racold Geyser",
"quantity": 2,
"price": 60000,
"description": "Moto G5",
"manufacturer": "Motorola",
"views" : 0
},
{
"name": "Lenovo G5",
"quantity": 3,
"price": 55000,
"manufacturer": "Lenovo",
"description": "A gaming laptop",
"id": 3,
"views" : 0
},
{
"name": "Acer Swift ",
"quantity": 5,
"price": 35000,
"manufacturer": "Acer",
"description": "Business Laptop",
"id": 4,
"views" : 0
},
{
"name": "Acer Nitro 7",
"quantity": 4,
"price": 75000,
"manufacturer": "Acer",
"description": "A gaming laptop",
"id": 5,
"views" : 0
},
"users": [
{
"id": 1,
"email": "vi#gmail.com",
"password": "truth",
"name": {
"firstName": "Rick",
"lastName": "Garner"
},
"location": "Canada",
"mobile": "55643980"
},
{
"id": 2,
"email": "t#t.com",
"password": "123",
"name": {
"firstName": "Ram",
"lastName": "Shankar"
},
"location": "Delhi",
"mobile": "9895454860"
},
{
"email": "e#e.com",
"password": "123456789",
"name": {
"firstName": "RAGAV",
"lastName": "Shant"
},
"location": "Karnataka",
"mobile": "1234567891",
"id": 3
},
{
"email": "k#k.com",
"password": "123456789",
"name": {
"firstName": "sd",
"lastName": "dv"
},
"location": "dfv",
"mobile": "12345678231",
"id": 4
}
]
}
You may want to dispatch update products action in useEffect inside ProductDetail.jsx page.
useEffect(() => {
updateProduct({
...props.location.productName,
views: props.location.productName + 1,
});
}, []);
Of course you will also need to pass views from Product.jsx.
This will increase views every time user opens/refreshes page.
EDIT:
If you want to have separate API endpoint for incrementing view count, you can implement its increment logic on server side. In that case, it won't change anything in current reducer file ProductReducer.js.
But I think there is no need for it. You can use updateProduct API , just for this reason. No need to change reducer in this case also.
EDIT 2:
If addView API is returning product id and incremented view, then you you can write reducer as -
case actionTypes.VIEW:
return [
...state.map((product) => {
if (product.id === action.product.id) {
product.views = action.product.views;
}
return product;
})
]
So what i did was I added a useEffect() to my ProductDetail.js file and fired the Action from there.
ProductDetail.js
import React,{useEffect} from 'react';
import { Link} from 'react-router-dom';
import {withRouter} from 'react-router'
import {Button, Card} from 'react-bootstrap'
import { connect } from "react-redux";
import * as productActions from "../redux/actions/productActions";
import { bindActionCreators } from "redux";
const ProductDetail=(props)=>{
useEffect(() => {
console.log("PROPIES ",props.location.productName.id+" "+props.location.productName.views)
props.actions.addView(props.location.productName.id,props.location.productName.views)
},[props.actions,props.location.productName.id,props.location.productName.views])
const style={"display":"flex", "justifyContent":"center","alignItems":"center","minHeight":"100vh"}
return(
<div style={style}>
<Card style={{ width: "18rem","borderRadius":"30px" }} >
<Card.Body style={{style}}>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.location.productName.name} </Card.Title>
<Card.Text><strong>Quantity :</strong>{props.location.productName.quantity}</Card.Text>
<Card.Text><strong>Price :</strong>{props.location.productName.price}</Card.Text>
<Card.Text><strong>Manufacturer:</strong>{props.location.productName.manufacturer}</Card.Text>
<Card.Text><strong>Description :</strong>{props.location.productName.description}</Card.Text>
<div>
<Link to="/"><Button variant="primary" style={{ height: "6vh","fontWeight":"bold" }}>Back</Button></Link>
</div>
</Card.Body>
</Card>
</div>
);
}
function mapStateToProps(state, ownProps) {
return {
products: state.products,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(ProductDetail));
It then fires this action
//Increase View Count of product
export function addView(id,count){
console.log("func called")
return function (dispatch){
console.log("api to be called")
return dataApi.addView(id,count)
.then((product)=>{
console.log("dispatched")
dispatch({type:actionTypes.VIEW, id: product.id})
})
}
}
So it updates the view on the server first and then in the reducer state
dataAPI.js
static addView(id,count){
return axios.patch('http://localhost:4000/products/'+id,{views:count+1})
.then(response=>response.data);
}
productReducer.js
import initialState from "./initialState";
import * as actionTypes from "../actions/actionTypes";
export default function productReducer(state = initialState.products, action) {
switch (action.type) {
case actionTypes.INIT:
return action.products;
case actionTypes.ADD:
return [...state, Object.assign({}, action.product)];
case actionTypes.DELETE:
return [...state.filter((product) => product.id !== action.id)];
case actionTypes.UPDATE:
return [
...state.filter((product) => product.id !== action.product.id),
Object.assign({}, action.product),
].sort( (a,b)=>(a.id>b.id)?1:-1 );
case actionTypes.VIEW:
let prod = [...state][action.id-1];
prod.views++;
//eslint-disable-next-line
let addView =()=>( [
...state.filter(product => product.id !== action.id),
Object.assign({}, prod)
])
return state;
default:
return state;
}
}
I had to write the ActionType.VIEW case in switch like this
case actionTypes.VIEW:
let prod = [...state][action.id-1];
prod.views++;
//eslint-disable-next-line
let addView =()=>( [
...state.filter(product => product.id !== action.id),
Object.assign({}, prod)
])
return state;
I had to put the state modification part inside a function called addView(), otherwise I saw that the function was repeatedly getting called infinitly. I'd appreciate it is someone could help me with a around that
I am working on a React application and I am using Redux to store the state. I have the following code:
category-arrows.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increaseCategoryRank, decreaseCategoryRank } from '../../redux/menu/menu.actions';
import './category-arrows.styles.scss';
class CategoryArrows extends Component {
handleClick = (id) => {
this.props.increaseCategoryRank(id);
}
render() {
const { categoryRank, categoryId, increaseCategoryRank, decreaseCategoryRank } = this.props;
return (
<div class="arrows-container">
<div class="up-arrow" onClick={this.handleClick(categoryId)}></div>
<div class="category-rank">
<p>{categoryRank}</p>
</div>
<div class="down-arrow"></div>
</div>
)
}
}
export default connect( { increaseCategoryRank, decreaseCategoryRank } )(CategoryArrows);
menu-category.component.jsx:
import React from 'react';
import { connect } from 'react-redux';
import MenuItem from '../../components/menu-item/menu-item.component';
import MenuCarousel from '../../components/menu-carousel/menu-carousel.component';
import NewItemCard from '../../components/new-item-card/new-item-card.component';
import DeleteCategory from '../../components/delete-category/delete-category.component';
import CategoryArrows from
'../../components/category-arrows/category-arrows.component';
import { editCategory } from '../../redux/menu/menu.actions';
import { MANAGER } from '../../redux/user/user.staff-types';
import './menu-category.styles.scss';
const MenuCategory = ({ currentUser, editCategory, isEditing, ...category }) => {
const isManager = currentUser && currentUser.type === MANAGER;
const editableProps = {
className: isManager ? 'category-editable' : null,
contentEditable: !!isManager,
suppressContentEditableWarning: true
};
return (
<div key={category._id} className='menu-category'>
<h2 {...editableProps} onBlur={event => editCategory({ ...category, name: event.target.innerText })}>
{category.name}
</h2>
<p {...editableProps} onBlur={event => editCategory({ ...category, description: event.target.innerText })} >
{category.description}
</p>
<MenuCarousel>
{isManager && isEditing ? <CategoryArrows className='category-rank-arrows' categoryRank={category.rank} categoryId={category._id} /> : null}
{isManager ? <NewItemCard categoryId={category._id} /> : null}
{category.items.map(menuItem => <MenuItem key={menuItem._id} categoryId={category._id} {...menuItem} />)}
{isManager ? <DeleteCategory name={category.name} categoryId={category._id} className='delete-bin' /> : null}
</MenuCarousel>
</div>
)
}
const mapStateToProps = state => ({
currentUser: state.user.currentUser
})
export default connect(mapStateToProps, { editCategory })(MenuCategory);
menu.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import MenuCategory from '../../components/menu-category/menu-category.component'
import NewCategoryButton from '../../components/new-category-button/new-category-button.component';
import EditMenuButton from '../../components/edit-menu-button/edit-menu-button.component';
import './menu.styles.scss';
class MenuPage extends Component {
state = {
menuEditable: false
}
toggleMenuEditable = () => this.setState({ menuEditable: !this.state.menuEditable })
render() {
return (
<div className='menu-page'>
{this.props.menu ? this.props.menu.map(category => <MenuCategory key={category._id} {...category} isEditing={this.state.menuEditable} />) : null}
<div className='edit-menu-buttons'>
<div className='menu-button'>
{this.props.currentUser ? <NewCategoryButton /> : null}
</div>
<div className='menu-button'>
{this.props.currentUser ? <EditMenuButton onClick={this.toggleMenuEditable} isEditing={this.state.menuEditable} /> : null}
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => ({
currentUser: state.user.currentUser,
menu: state.menu
})
export default connect(mapStateToProps)(MenuPage);
menu.actions.js:
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK } from './menu.types';
export const increaseCategoryRank = categoryId => dispatch => {
dispatch({ type: INCREASE_CATEGORY_RANK, payload: categoryId })
}
export const decreaseCategoryRank = categoryId => dispatch => {
dispatch({ type: DECREASE_CATEGORY_RANK, payload: categoryId })
}
menu.types.js:
export const INCREASE_CATEGORY_RANK = "INCREASE_CATEGORY_RANK";
export const DECREASE_CATEGORY_RANK = "DECREASE_CATEGORY_RANK";
menu.reducer.js:
import INITIAL_STATE from './menu.data';
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK } from './menu.types';
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK:
console.log(action.payload._id);
return;
case DECREASE_CATEGORY_RANK:
console.log(action.payload._id);
return;
default:
return state;
}
}
menu.data.js:
export default [
{
"_id": "c0daac6ab8954a40606dd8b81d24a0ef",
"name": "Entree",
"rank": "0",
"items": [
{
"title": "Curry Puffs",
"price": 14,
"_id": "615caa7dd573bcf84781c9e4382b520d"
},
{
"title": "Spring Rolls",
"price": 12,
"_id": "542f711856b7854b71d9862797620e23"
},
{
"title": "Tandoori Cauliflower",
"price": 20,
"_id": "f0c0f2fa02e392ad4e74dfaaf6068fb1"
}
]
},
{
"_id": "934aeba1e96e6d6a4207cd5ba207b52a",
"name": "Lunch",
"rank": "1",
"items": [
{
"title": "Spaghetti",
"price": 20,
"_id": "db414e2b9951ed621fbf6fb40df36ee3"
},
{
"title": "Spaghetti",
"price": 20,
"_id": "253592733a8f7835f390d3d9ed8bda95"
},
{
"title": "Spaghetti",
"price": 20,
"_id": "a22741f27a346cda93d3cf752e371779"
}
]
}
]
In the above code, I have a CategoryArrows component that is rendered in the MenuCategory component only if the isEditing value which is passed from the MenuPage component to the MenuCategory component as a props is true. When the EditMenuButton component is clicked, isEditing is set to true, and the CategoryArrows components appear on the menu page.
When the up-arrow div is clicked I want to dispatch the action increaseCategoryRank, which takes the category id as a parameter. I have added code for this action.
However, when I click the EditMenuButton component, the CategoryArrows do not appear and the screen turns blank white. I get the following error when I inspect the page:
Uncaught Error: Invalid value of type object for mapStateToProps argument when connecting component CategoryArrows.
I am not sure what this error means and how to resolve it. Any insights are appreciated.
You have not passed correct parameter inputs to redux connect function in your code.
The connect function first argument must be the mapStateToProps function which returns the reduced state.
export default connect( null, { increaseCategoryRank, decreaseCategoryRank } )(CategoryArrows);
Pass null as the first parameter to connect since you don't seem to pass any props from the redux store.
Please update your category-arrows.component.jsx connect as given above.
Your first argument to connect is an object here:
export default connect( { increaseCategoryRank, decreaseCategoryRank } )(CategoryArrows);
The first argument to connect should be mapStateToProps which is a function. If you want to access dispatch, but don't need state, you need to pass null as first argument to connect:
connect(null, mapStateToDispatch)
I am working on a React application and I am using Redux to store the state. I have the following code:
requests.data.js:
export default {
requests: [
{
"id": 9,
"timestamp": Date.now(),
"description": "Need help with ordering",
"status": "Completed"
},
{
"id": 2,
"timestamp": Date.now(),
"description": "Need help with ordering",
"status": "Assistance Requested"
},
{
"id": 4,
"timestamp": Date.now(),
"description": "Need help with ordering",
"status": "Assistance Requested"
},
{
"id": 7,
"timestamp": Date.now(),
"description": "Need help with ordering",
"status": "Assistance Requested"
}
]
}
I have the following Request component.
request.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { changeRequest } from '../../redux/requests/requests.actions';
import './request.styles.scss';
class Request extends Component {
handleClick = (id, status) => {
this.props.changeRequest(id, status);
}
render() {
return (
<div className="request-box">
<div className="request-details">
<div>
<h1>Table {this.props.id}, {this.props.timestamp}</h1>
<h2>{this.props.description}</h2>
</div>
<div className="status-button">
<button type="button" className="request-button" onClick={() => this.handleClick(this.props.id, this.props.status)}>{this.props.status}</button>
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
requests: state.requests.requests
}
}
const mapDispatchToProps = (dispatch) => {
return {
changeRequest: (id, status) => { dispatch(changeRequest(id, status)) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Request);
This component is displayed in the following page.
requests.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Request from '../../components/request/request.component';
import './requests.styles.scss';
class RequestListPage extends Component {
render() {
const { requests } = this.props;
const requestList = requests.length ? (
requests.map(request => {
return(
<Request request={request} key={request.id}/>
)
})
) : (
<div>No requests yet.</div>
)
return (
<div className="requests-page">
<h1>Requests</h1>
<div className="requests-container">
<div className="request-list">
{requestList}
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
requests: state.requests.requests
}
}
export default connect(mapStateToProps)(RequestListPage);
In the above code, the RequestListPage component maps each request object from the state to a Request component, so that a Request component is concerned about a single Request and not a list.
However, when I run my application, I get the following:
The data in the Request component is not getting displayed. I am not sure why this is occurring. Any insights are appreciated.
You're sending each request as
<Request request={request} key={request.id}/>
so you should use it as
<h2>{this.props.request.description}</h2>
You can also spread the request object like
<Request {...request} key={request.id}/>
to use them directly(this.props.description) as you were using.
or you can also destructure them inside the Request component if you like
const { description, status, id, timestamp } = this.props.request;
and use them like
<h2>{description}</h2>
The issue you were having is that the properties of the request in the Request component were in this.props.request rather than this.props directly. This is because you were creating the elements using: <Request request={request} key={request.id}/> rather than <Request {...request} key={request.id}/>
class Request extends Component {
handleClick = (id, status) => {
this.props.changeRequest(id, status);
}
render() {
return (
<div className="request-box">
<div className="request-details">
<div>
<h1>Table {this.request.props.id}, {this.props.request.timestamp}</h1>
<h2>{this.props.request.description}</h2>
</div>
<div className="status-button">
<button type="button" className="request-button" onClick={() => this.handleClick(this.props.request.id, this.props.status)}>{this.props.request.status}</button>
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
requests: state.requests.requests
}
}
const mapDispatchToProps = (dispatch) => {
return {
changeRequest: (id, status) => { dispatch(changeRequest(id, status)) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Request);
Looks like you may need to spread in the request object's properties since in Request you access them as this.props.XXX versus this.props.request.XXX.
<Request {...request} key={request.id}/>
Defining required propTypes would have caught this for you.
I'm new to reactjs. I could map the data from json API. My objective is when we save a number in contactlist, it should show the name in message list also. I want to create 2 fetch data i.e., messageData, ContactData that should compare the Phone numbers of message API and COntact API. If Phone number is same in contact API and Message API then it should return name otherwise it should return only phone number.
Contact json data would be like
[
{
"id": 1,
"name": "ABC",
"phone": "+91 789654123",
"email": "abcyz#gmail.com"
},
{
"id": 2,
"name": "DEF",
"phone": "+91 123456987",
"email": "defvu#gmail.com"
}
]
Contact Component:
import React, { Component } from "react";
import { Grid, Header, Button, Icon } from "semantic-ui-react";
import { Link } from 'react-router-dom'
class ComponentList extends Component {
state = {
peopleData: []
};
componentDidMount() {
fetch('./people.json')
.then(response => response.json())
.then(data => this.setState({ peopleData: data }));
}
pnum(num) {
return num.replace(/^\D/g, "");
}
render() {
const {peopleData} = this.state;
return(
<div>
{ peopleData.map(people =>
<Grid>
<Grid.Row key={people.id}>
<Grid.Column>
<Header>{people.name}</Header>
<span>{people.phone}</span>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Button trigger={<Link to={`/displayChat/${this.pnum(people.phone)}`}>
<Icon name='comment alternate outline' color='teal' /></Link>}/>
</Grid.Row>
</Grid>
)}
</div>
);
}
}
export default ComponentList;
Message API:
[
{
"id": 1,
"phone": "+91 789654123",
"message": "Hello everyone",
"isrespond": true,
},
{
"id": 2,
"phone": "+91 123456987",
"message": "hi",
"isrespond": false,
}
]
DisplayChat component:
fetchChat() {
const { phone } = this.props.match.params ;
fetch(`api/conversation/${phone}`)
.then(response => response.json())
.then(data => this.setState({ messageList: data})
);
}
render(){
const {messageList} = this.state;
const { phone } = this.props.match.params ;
return(
<div>
<ViewHeader phone={this.props.match.params}/>
<Container className="viewMessage ">
{messageList.map(data =>
<Grid>
<Grid.Row key={data.id}>
<p>{data.message}</p>
</Grid.Column>
</Grid.Row>
</Grid>
)}
</Container>
<TypeHere phone={this.props.match.params}/>
</div>
);
}
}
Can anyone help me in this?
const URL1= "contactApi"
const URL2= "messageApi"
I have used promise.all along with fetch to resolve both the endpoints
Promise.all([
fetch(URL1),
fetch(URL2),
]).then(responses => responses
).then(responses => Promise.all(responses.map(r => r.json())))
.then(data=>writeLogic(data));
function writeLogic(data){
let nameOrPhone;
const contactApiData = data[0]
const messageApiData = data[1]
let isPresent
for(let m of messageApiData){
isPresent=false
for(let c of contactApiData){
if(m.phone === c.phone){
isPresent=true
nameOrPhone.push(c.name)
}
}
if(!isPresent){
nameOrPhone.push(m.phone)
}
}
//update your state here if you are using React
//this.setState({nameOrPhone})
console.log(nameOrPhone)
}
data will contain result of both the endpoint in an array.
you can use the data and write your logic on top of it.
Ref: https://javascript.info/promise-api
I have create VenueList component. I want to display list using FlatList component in react native app. I am getting error: Invariant Violation tried to get frame out of range index (See screenshot).
Code:
VenueList.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import { fetchVenues } from '../actions/venueAction';
class VenueList extends Component {
componentWillMount () {
this.props.fetchVenues();
}
renderItem = ({ item }) => (
<View style={styles.item}>
<Text>{item.attributes.name}</Text>
</View>
);
render() {
return (
<FlatList
styles={styles.container}
data={this.props.venues}
renderItem={this.renderItem}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
item: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#ccc'
}
});
VenueList.propTypes = {
fetchVenues: PropTypes.func.isRequired,
venues: PropTypes.array.isRequired
}
const mapStateToProps = state => ({
venues: state.venues.items
})
export default connect (mapStateToProps, { fetchVenues })(VenueList);
venueReducer.js:
import { FETCH_VENUES } from '../actions/types';
const initialState = {
items: []
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_VENUES:
return {
...state,
items: action.payload
};
default:
return state;
}
}
venueAction.js:
import { FETCH_VENUES } from './types';
import axios from 'axios';
export const fetchVenues = () => dispatch => {
axios.get(`my_api_link`)
.then( venues =>
dispatch({
type: FETCH_VENUES,
payload: venues
})
)
.catch( error => {
console.log(error);
});
};
The data which I want to display from API endpoint has json data as follows:
{
"data": [
{
"type": "venues",
"id": "nb",
"attributes": {
"name": "Barasti Beach",
"description": "Barasti Beach is lotacated in the awesome barasti beach",
"price_range": "$$$",
"opening_hours": "10:30-12:40/16:00-2:00",
"organization": {
"id": "GD",
"legal_name": "Barasti",
"brand": "Barasti"
},
"place": {
"address": "Le Meridien Mina Seyahi Beach Resort & Marina, Dubai Marina - Dubai - United Arab Emirates",
"latitude": "25.092648",
"location": [
"Marina Bay",
"Dubai",
"Arab Emirate United"
]
}
}
}
],
"meta": {
"total": 1,
"cursor": {
"current": 1,
"prev": null,
"next": null,
"count": 25
}
}
}
See screenshot:
As per the the above response for the api request,
The problem is with the payload which is set in the actions. You need to pass the data from the api to the Flatlist since it accepts only arrays.
axios.get(`my_api_link`)
.then( venues =>
dispatch({
type: FETCH_VENUES,
payload: venues.data
})
)
EDIT:
Adding in VenueList.js component (if the api is returning values inside data key):
renderItem = ({ item }) => (
<View style={styles.item}>
<Text>{item.attributes.name}</Text>
</View>
);
render() {
return (
<FlatList
styles={styles.container}
data={this.props.venues.data}
renderItem={this.renderItem}
/>
);
}