Number of current page doesnt change in react - reactjs

I am using react-paginate (https://www.npmjs.com/package/react-paginate) to make pagination for my app. Everything is fine but I cannot increase the current number of the page. So for this here is my parent component:
import React, { useEffect, useState } from "react";
import Product from "../components/sub-components/Product";
import SimpleBox from "../components/sub-components/SimpleBox";
import BoxWithSearch from "../components/sub-components/BoxWithSearch";
import LoadingBox from "../components/sub-components/LoadingBox";
import MessageBox from "../components/sub-components/MessageBox";
import Cart from "../components/sub-components/Cart";
import { useDispatch, useSelector } from "react-redux";
import { listProducts } from "../actions/productActions";
import ReactPaginate from "react-paginate";
export default function HomeScreen() {
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { loading, error, products } = productList;
const [currentPage, setCurrentPage] = useState(1);
const [pageCount, setpageCount] = useState(0);
useEffect(() => {
dispatch(listProducts(currentPage));
console.log(currentPage);
}, [dispatch]);
const handlePageClick = (data) => {
setCurrentPage(data.selected + 1);
// scroll to the top
//window.scrollTo(0, 0)
};
return (
<div className="container">
<div className="row">
<div className="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<h2 className="title">Products</h2>
<div className="product-type-filter">
<button>Mug</button>
<button className="clicked">Shirt</button>
</div>
<div className="products">
<div className="row">
<div>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<div className="row center">
{products.map((product) => (
<Product key={product.added} product={product}></Product>
))}
</div>
)}
</div>
</div>
</div>
<ReactPaginate
previousLabel={"Prev"}
nextLabel={"Next"}
pageCount={40}
marginPagesDisplayed={4}
pageRangeDisplayed={1}
onPageChange={handlePageClick}
containerClassName={"pagination justify-content-center"}
pageClassName={"page-item"}
pageLinkClassName={"page-link"}
previousClassName={"page-item"}
previousLinkClassName={"page-link"}
nextClassName={"page-item"}
nextLinkClassName={"page-link"}
breakClassName={"page-item"}
breakLinkClassName={"page-link"}
activeClassName={"active"}
/>
</div>
</div>
</div>
);
}
And my action for fetching the data:
import Axios from 'axios';
import {
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
} from '../constants/productConstants';
export const listProducts = () => async (dispatch, currentPage) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`http://localhost:3000/items?_page=${currentPage}&_limit=16`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
But the problem is since the currentPage doesn't change, I cannot go to the other page. Do you have a solution for this?
Thanks...

If you are updating the current page and wanting to fetch new data then you might want to add currentPage to the useEffect dependency array so the next current page of products is fetched/listed.
useEffect(() => {
dispatch(listProducts(currentPage));
console.log(currentPage);
}, [currentPage, dispatch]);
Update
When I write console.log(currentPage) in action, I am getting this:
ƒ getState() { var state = unliftState(liftedStore.getState()); if (state !== undefined) { lastDefinedState = state; } return lastDefinedState; } How can I pass the currentpage number into
action?
In thunks, the second argument is the getState function to be called and get the current redux state. Your listProducts action creator is naming the getState callback currentPage. Also, any arguments being passed to listProducts are being ignored (note the empty arg list in the outer function).
export const listProducts = () => async (dispatch, currentPage) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`http://localhost:3000/items?_page=${currentPage}&_limit=16`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
listProducts needs to consume the passed currentPage argument in the outer functions, and enclose it in function scope.
export const listProducts = (currentPage) => async (dispatch) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`http://localhost:3000/items?_page=${currentPage}&_limit=16`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};

Related

Redux updating state with null before actual data

I am dispatching an action that is supposed to take an input from the user and store it in a database. However, when I inspect my posts state in redux after the action is dispatched, there is a null value appended to the state array before the actual post. This is preventing me from working with the actual data in the posts array. Basically I'm wondering how to prevent null from being appended each time I dispatch a new post. Here are the relevant code snippets and images.
Post Reducer:
import { enableAllPlugins, produce } from 'immer';
enableAllPlugins();
const initialState = {
posts: [],
loading: false,
error: false,
uploading: false,
};
const postReducer = produce((draftstate, action = {}) => {
switch (action.type) {
case 'UPLOAD_START':
draftstate.loading = true;
draftstate.error = false;
case 'UPLOAD_SUCCESS':
draftstate.posts.push(action.data);
draftstate.uploading = false;
draftstate.error = false;
case 'UPLOAD_FAIL':
draftstate.uploading = false;
draftstate.error = true;
default:
return draftstate;
}
}, initialState);
export default postReducer;
Upload Post action:
export const uploadPost = (data) => async (dispatch) => {
dispatch({ type: 'UPLOAD_START' });
try {
const newPost = await UploadApi.uploadPost(data);
console.log('new post before', newPost);
dispatch({ type: 'UPLOAD_SUCCESS', data: newPost.data });
} catch (error) {
console.log(error);
dispatch({ type: 'UPLOAD_FAIL' });
}
};
Share Post code:
import React, { useState, useRef } from "react";
import ProfileImage from "../../img/profileImg.jpg";
import "./PostShare.css";
import { UilScenery } from "#iconscout/react-unicons";
import { UilPlayCircle } from "#iconscout/react-unicons";
import { UilLocationPoint } from "#iconscout/react-unicons";
import { UilSchedule } from "#iconscout/react-unicons";
import { UilTimes } from "#iconscout/react-unicons";
import { useSelector, useDispatch } from "react-redux";
import { uploadImage, uploadPost } from "../../actions/uploadAction";
const PostShare = () => {
const loading = useSelector((state) => state.postReducer.uploading);
const [image, setImage] = useState(null);
const imageRef = useRef();
const desc = useRef();
const dispatch = useDispatch();
const { user } = useSelector((state) => state.authReducer.authData);
// handle Image Change
const onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
let img = event.target.files[0];
setImage(img);
}
};
const reset = () => {
setImage(null);
desc.current.value = "";
};
const handleSubmit = async (e) => {
e.preventDefault();
const newPost = {
userId: user._id,
desc: desc.current.value,
};
if (image) {
const data = new FormData();
const filename = Date.now() + image.name;
data.append("name", filename);
data.append("file", image);
newPost.image = filename;
console.log(newPost);
try {
dispatch(uploadImage(data));
} catch (error) {
console.log(error);
}
}
dispatch(uploadPost(newPost));
reset();
};
return (
<div>
<div className="PostShare">
<img src={ProfileImage} alt="" />
<div>
<input
ref={desc}
required
type="text"
placeholder="What's happening"
/>
<div className="postOptions">
<div
className="option"
style={{ color: "var(--photo)" }}
onClick={() => imageRef.current.click()}
>
<UilScenery />
Photo
</div>
<div className="option" style={{ color: "var(--video" }}>
<UilPlayCircle />
Video
</div>
<div className="option" style={{ color: "var(--location)" }}>
<UilLocationPoint />
Location
</div>
<div className="option" style={{ color: "var(--shedule)" }}>
<UilSchedule />
Schedule
</div>
<button
className="button ps-button"
onClick={handleSubmit}
disabled={loading}
>
{loading ? "Uploading..." : "Share"}
</button>
<div style={{ display: "none" }}>
<input
type="file"
name="myImage"
ref={imageRef}
onChange={onImageChange}
/>
</div>
</div>
{image && (
<div className="previewImage">
<UilTimes onClick={() => setImage(null)} />
<img src={URL.createObjectURL(image)} alt="" />
</div>
)}
</div>
</div>
</div>
);
};
export default PostShare;
I would be glad to provide any other details if that helps.
Update with other portions of code:
Dispatcher of RETRIEVING_SUCCESS:
import * as PostApi from '../api/PostRequest';
export const getTimelinePosts = (id) => async (dispatch) => {
dispatch({ type: 'RETRIEVING_START' });
try {
const { data } = await PostApi.getTimelinePosts(id);
dispatch({ type: 'RETRIEVING_SUCCESS', data: data });
} catch (error) {
dispatch({ type: 'RETRIEVING_FAIL' });
console.log(error);
}
};
getTimelinePosts usage:
import React, { useEffect } from 'react';
import './Posts.css';
import { PostsData } from '../../Data/PostsData';
import { useDispatch, useSelector } from 'react-redux';
import { getTimelinePosts } from '../../actions/postAction';
import Post from '../Post/Post';
const Posts = () => {
const dispatch = useDispatch();
const { user } = useSelector((state) => state.authReducer.authData);
let { posts, loading } = useSelector((state) => state.postReducer);
console.log('posts content', posts);
useEffect(() => {
dispatch(getTimelinePosts(user._id));
}, []);
return (
<div className="Posts">
{/* {posts.map((post, id) => {
return <Post data={post} id={id}></Post>;
})} */}
</div>
);
};
export default Posts;
in postReducer, let's remove the default on the switch statement, we don't need it on reducer because other actions will come here and the code make all states return the initial state.

PATCH request seems like a step behind

Hey folks really hope someone can help me here. I'm successfully updating my object in my mongo cluster, it updates but it does not render that update straight away to the browser. It will only update after a reload or when I run my update function again, it doesn't fetch that update straight away and I can't understand why. Does anyone have any suggestions?
I'm using context and reducer.
PlantDetails
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
console.log('updated')
setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
<input onChange={(e) => setNewWaterDate(e.target.value)}/>
<button onClick={updatePlant}>update</button>
<input value={watered} type="checkbox" id="toWater" onChange={() => setWatered(true)}/>
<label for="toWater">watered</label>
{watered && <CalendarComponent updatePlant={updatePlant} setNextWaterDate={setNewWaterDate}/>}
</div>
</div>
);
};
export default PlantDetails;
Context which wraps my
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p )
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My plantController (update)
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200)
.json(plant)
}
Home component
import { useEffect } from "react";
import PlantDetails from "../components/PlantDetails";
import PlantForm from "../components/PlantForm";
import CalendarComponent from "../components/CalendarComponent";
import { usePlantsContext } from "../hooks/usePlantsContext";
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
Any help would be greatly appreciated.
My patch requests were going through smoothly but my state would not update until I reloaded my page. It was not returning the document after the update was applied.
https://mongoosejs.com/docs/tutorials/findoneandupdate.html#:~:text=%3B%20//%2059-,You,-should%20set%20the

Can't use multiple dispatch in react redux

I have a problem, so I am trying to put two or more "dispatch" in my application
but I don't know why it is just working one dispatch that I put in last
import axios from "axios";
const setDataBlog = (page) =>{
return (dispatch) => {
axios.get(`http://localhost:4000/api/blogs/?page=${page}&perPage=3`)
.then(result => {
const responseAPI = result.data
dispatch({type: 'UPDATE_PAGE', payload:
{currentPage: responseAPI.current_page,
totalPage: responseAPI.total_page}}) // this is not working
dispatch({type: 'SET_BLOGS', payload: responseAPI.data}) //just work in here
})
.catch(error => {
console.log('error',error);
})
}}
export default setDataBlog
but if I change the location of the dispatch
import axios from "axios";
const setDataBlog = (page) =>{
return (dispatch) => {
axios.get(`http://localhost:4000/api/blogs/?page=${page}&perPage=3`)
.then(result => {
const responseAPI = result.data
dispatch({type: 'SET_BLOGS', payload: responseAPI.data}) //not working
dispatch({type: 'UPDATE_PAGE', payload:
{currentPage: responseAPI.current_page,
totalPage: responseAPI.total_page}}) // working
})
.catch(error => {
console.log('error',error);
})
}}
export default setDataBlog
I'm trying to use it here
import { useEffect} from "react";
import CardBlog from "../components/CardBlog";
import Pagination from "../components/Pagination";
import {useDispatch, useSelector} from 'react-redux'
import {setDataBlog} from '../redux/action'
const Home = () => {
const {dataBlog, page} = useSelector(state => state.homeReducer);
const dispatch = useDispatch();
//check working or not
console.log('page', page);
useEffect(() => {
dispatch(setDataBlog(1))
}, [dispatch])
return (
<div className="max-w-screen-xl mx-auto px-4 py-16 sm:px-6 lg:px-8">
<div className=" md:grid md:grid-cols-2 lg:grid-cols-3">
{dataBlog?.map(blog => (
<CardBlog key={blog._id} image={`http://localhost:4000/${blog.image}`}
title={blog.title}
body={blog.body}
author={blog.author}
date={blog.createdAt}/>
))}
</div>
<div>
<Pagination/>
</div>
</div>
);
}
export default Home;
thanks, sorry for my bad English, but I hope you understand what I said
I can't tell you why your code is failing, but I'd like to offer some advice. Avoid firing multiple synchronous actions.
Think of an action as representing single thing that has happened: often a user event such as a button click or key press, or in this case a network response.
I recommend combining the two actions above into a single action, e.g.
dispatch({
type: 'BLOG_API_RESPONSE',
payload: {
currentPage: responseAPI.current_page,
totalPages: responseAPI.total_page,
data: responseAPI.data,
},
});
You can hook into BLOG_API_RESPONSE at multiple places in your reducers. Actions to state updates don't have to be one-to-one. One action can produce many state updates.
You'll find your code easier to rationalise and debug when you restrict yourself to firing single synchronous actions.
Try this:
const setDataBlog = (page) => async dispatch => {
try {
const { responseAPI } = await axios.get(`http://localhost:4000/api/blogs/?page=${page}&perPage=3`)
dispatch({type: 'SET_BLOGS', payload: responseAPI.data})
dispatch({type: 'UPDATE_PAGE', payload: {currentPage:responseAPI.current_page, totalPage: responseAPI.total_page}})})
catch(error => {
console.log('error',error);
})
}

Cast to ObjectId failed for value \"updateInstock\" (type string) at path \"_id\" for model \"Product\

I'm trying to adjust my countInStock based off of qty ordered. In my PlaceOrderScreen.js, I have items = item.map(product => ({ _id: product._id, countInStock: product.countInStock, qty: product.qty, new:(product.countInStock - product.qty) })) where item = cart.carItems. I'm trying to get this to my backend in my productRouter.js, so that I can update my products based off of their new countInStock, but I'm getting "Cast to ObjectId failed for value \"updateInstock\" (type string) at path \"_id\" for model \"Product\". I'm unsure of why this is happening, and I would really appreciate any help or advice. Thank you!
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {createOrder} from '../actions/orderActions';
import {updateInstock} from '../actions/productActions';
import { ORDER_CREATE_RESET } from '../constants/orderConstants';
export default function PlaceOrderScreen(props) {
const cart = useSelector((state) => state.cart);
if (!cart.paymentMethod) {
props.history.push('/payment');
}
const orderCreate = useSelector((state) => state.orderCreate);
const { loading, success, error, order } = orderCreate;
const item = cart.cartItems
const items = item.map(product => ({ _id: product._id, countInStock: product.countInStock, qty: product.qty, new:(product.countInStock - product.qty) }))
const dispatch = useDispatch();
const placeOrderHandler = () => {
dispatch(createOrder({ ...cart, orderItems: cart.cartItems }));
dispatch(updateInstock(items));
};
useEffect(() => {
if (success) {
props.history.push(`/order/${order._id}`);
dispatch({ type: ORDER_CREATE_RESET });
}
}, [dispatch, order, props.history, success]);
return (
<div>
<div className="row top">
<div className="col-1">
<div className="card card-body">
<ul>
<li>
<div className="row">
...
</div>
</li>
<li>
<button
type="button"
onClick={placeOrderHandler}
className="primary block"
disabled={cart.cartItems.length === 0}
>
Place Order
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
);
}
productActions.js
import Axios from 'axios';
import {
PRODUCT_UPDATEINSTOCK_REQUEST,
PRODUCT_UPDATEINSTOCK_SUCCESS,
PRODUCT_UPDATEINSTOCK_FAIL,
} from '../constants/productConstants';
import { BASE_URL } from '../constants/app.constants';
export const updateInstock = (items) => async (dispatch, getState) => {
dispatch({ type: PRODUCT_UPDATEINSTOCK_REQUEST, payload: items });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.put(`${BASE_URL}/api/products/updateInstock`, items, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: PRODUCT_UPDATEINSTOCK_SUCCESS, payload: data });
} catch (error) {
const message = error.response && error.response.data.message ? error.response.data.message : error.message;
dispatch({ type: PRODUCT_UPDATEINSTOCK_FAIL, error: message });
}
};
productRouter.js
productRouter.put(
'/updateInstock',
expressAsyncHandler(async (req, res) => {
console.log(req.body.items)
})
);
console.log(req.body.items) is not showing data in the terminal.

Deleting an item from a collection in f, using React Redux

I'm trying to delete an item from a collection in Firestore by referencing the id of the selected item. I'm successfully passing on the id by mapDispatchToProps until the action but stops short when trying to delete in Firestore by the delete(). I think the problem may be in the my method to delete in firestore as it stops there. Can anyone kindly please tell me what could be wrong with my code?
import React from 'react'
import { connect } from 'react-redux'
import { firestoreConnect } from "react-redux-firebase";
import { compose } from 'redux'
import { Redirect } from 'react-router-dom'
import moment from 'moment'
import { deleteProject } from '../../store/actions/projectActions'
const handleClick = (e, prop) => {
e.preventDefault()
deleteProject(prop)
console.log(prop)
}
const ProjectDetails = (props) => {
const { auth, project } = props;
if (!auth.uid) return <Redirect to='/signin' />
if (project) {
return (
<div className="container section project-details">
<div className="card z-depth-0">
// content here
</div>
<button onClick={(e) => handleClick(e, props.id)}>Delete</button>
</div>
</div>
)
} else {
return (
<div className="container center">
<p>Loading...</p>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const id = ownProps.match.params.id;
const projects = state.firestore.data.projects;
const project = projects ? projects[id] : null
return {
project: project,
id: id,
auth: state.firebase.auth
}
}
const matchDispatchToProps = (dispatch) => {
return {
deleteProject: (id) => dispatch(deleteProject(id))
}
}
export default compose(
connect(mapStateToProps, matchDispatchToProps),
firestoreConnect([
{ collection: 'projects' }
])
)(ProjectDetails)
export const deleteProject = (id) => {
console.log("dispatch", id) \\ successfully shows "dispatch", id
return(dispatch, getState, {getFirestore}) => {
const firestore = getFirestore();
firestore.collection('projects').doc(id).delete()
.then(() => {
console.log('deleted') \\ does not show deleted here
dispatch({ type: 'DELETE_PROJECT_SUCCESS' });
}).catch(err => {
dispatch({ type: 'DELETE_PROJECT_ERROR' });
})
}
}
You are calling the imported version of deleteProject rather than the mapDispatchToProps version. This is a common gotcha.
One way to fix this (and prevent it happening in future) is to rename your action in your mapDispatchToProps to something different:
const matchDispatchToProps = (dispatch) => {
return {
dispatchDeleteProject: (e, id) => {
e.preventDefault()
dispatch(deleteProject(id))
})
}
}
Then you can destructure this out of your props and call it:
const ProjectDetails = (props) => {
const { auth, project, dispatchDeleteProject } = props;
if (!auth.uid) return <Redirect to='/signin' />
if (project) {
return (
<div className="container section project-details">
<div className="card z-depth-0">
// content here
</div>
<button onClick={e=>dispatchDeleteProject(e, props.id)}>Delete</button>
</div>
</div>
)
}
This is happening because your action deleteProject is not getting called from redux's dispatch.
If you will observer correctly, in your handleClick function, you are calling deleteProject function function action directly.
handleClick function should call deleteProject function from prop like this.
Your handleClick function should be -
const handleClick = (e, id, deleteProject) => { // passing deleteProject function from prop
e.preventDefault()
deleteProject(id)
console.log(id)
}
You HTML should be -
<button onClick={(e) => handleClick(e, props.id, props.deleteProject)}>Delete</button>

Resources