How to create infinite scroll in React and Redux? - reactjs

import React, {useState, useEffect} from 'react';
import {connect} from 'react-redux';
import {
fetchRecipes
} from '../../store/actions';
import './BeerRecipes.css';
const BeerRecipes = ({recipesData, fetchRecipes}) => {
const [page, setPage] = useState(1);
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRecipes();
}, [])
return (
<div className='beer_recipes_block'>
<div className='title_wrapper'>
<h2 className='title'>Beer recipes</h2>
</div>
<div className='beer_recipes'>
<ul className='beer_recipes_items'>
{
recipesData && recipesData.recipes && recipesData.recipes.map(recipe =>
<li className='beer_recipes_item' id={recipe.id}>{recipe.name}</li>
)
}
</ul>
</div>
</div>
);
};
const mapStateToProps = state => {
return {
recipesData: state.recipes
}
}
const mapDispatchToProps = dispatch => {
return {
fetchRecipes: () => dispatch(fetchRecipes())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BeerRecipes);
this is my component where I would like to create infinite scroll and below is my redux-action with axios:
import axios from "axios";
import * as actionTypes from "./actionTypes";
export const fetchRecipesRequest = () => {
return {
type: actionTypes.FETCH_RECIPES_REQUEST
}
}
export const fetchRecipesSuccess = recipes => {
return {
type: actionTypes.FETCH_RECIPES_SUCCESS,
payload: recipes
}
}
export const fetchRecipesFailure = error => {
return {
type: actionTypes.FETCH_RECIPES_FAILURE,
payload: error
}
}
export const fetchRecipes = (page) => {
return (dispatch) => {
dispatch(fetchRecipesRequest)
axios
.get('https://api.punkapi.com/v2/beers?page=1')
.then(response => {
const recipes = response.data;
dispatch(fetchRecipesSuccess(recipes));
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchRecipesFailure(errorMsg));
})
}
}
I want to create a scroll. I need, firstly, to display first 10 elements and then to add 5 elements with every loading. I have 25 elements altogether and when the list is done it should start from the first five again.

Assuming you already have everything ready to load your next page. You can probably simplify the entire process by using a package like react-in-viewport so you don't have to deal with all the scroll listeners.
then you use it like this way.
import handleViewport from 'react-in-viewport';
const Block = (props: { inViewport: boolean }) => {
const { inViewport, forwardedRef } = props;
const color = inViewport ? '#217ac0' : '#ff9800';
const text = inViewport ? 'In viewport' : 'Not in viewport';
return (
<div className="viewport-block" ref={forwardedRef}>
<h3>{ text }</h3>
<div style={{ width: '400px', height: '300px', background: color }} />
</div>
);
};
const ViewportBlock = handleViewport(Block, /** options: {}, config: {} **/);
const Component = (props) => (
<div>
<div style={{ height: '100vh' }}>
<h2>Scroll down to make component in viewport</h2>
</div>
<ViewportBlock
onEnterViewport={() => console.log('This is the bottom of the content, lets dispatch to load more post ')}
onLeaveViewport={() => console.log('We can choose not to use this.')} />
</div>
))
What happen here is, it creates a 'div' which is outside the viewport, once it comes into the view port ( it means user already scrolled to the bottom ), you can call a function to load more post.
To Note: Remember to add some kind of throttle to your fetch function.

Related

useEffect dosn't save data in localstorage

I have a simple app, sorta for chat purpuses. I fetch data from static file in json format. So this app shows all the messages from that file but also I want to edit the messeges, delete them and add via local storage. For that I used useEffect, but after refresh all the changes I do disappear.
This is my component:
export const WorkChat = (props) => {
const [messageValue, setMessageValue] = useState('');
const [edit, setEdit] = useState(null);
const [editmessageValue, setMessageEditValue] = useState('')
const submitMessage = () => {
const newMessage = {
id: Math.floor(Math.random() * 10000),
message: messageValue
}
props.addMessage(newMessage);
setMessageValue('')
}
const removeMsg = (id) => {
props.deleteMessage(id)
}
const goToEditMode = (message) => {
setEdit(message.id);
setMessageEditValue(message.message)
}
const saveChanges = (id) => {
const newMessagesArray = props.messages.map(m => {
if(m.id === id){
m.message = editmessageValue
}
return m
})
props.updateMessage(newMessagesArray);
setEdit(null)
}
useEffect(()=> {
let data = localStorage.getItem('work-messages');
if(data){
props.setMessages(JSON.parse(data))
}
}, []);
useEffect(()=> {
localStorage.setItem('work-messages', JSON.stringify(props.messages))
},[props.messages])
return (
<div className={s.workChatContainer}>
<input className={s.workInput} placeholder='Enter work message...' onChange={(e)=> setMessageValue(e.target.value)} value={messageValue}/>
<button className={`${s.btn} ${s.sendBtn}`} onClick={()=>submitMessage()}><SendIcon style={{fontSize: 20}}/></button>
<div>
{props.messages.map(m => (
<div key={m.id} className={s.messages}>
{edit !== m.id ? <div>
<span className={s.message}>{m.message}</span>
<button className={`${s.btn} ${s.deleteBtn}`} onClick={()=> removeMsg(m.id)}><DeleteOutlineIcon style={{fontSize: 15}}/></button>
<button className={`${s.btn} ${s.editBtn}`} onClick={()=> goToEditMode(m)}><EditIcon style={{fontSize: 15}}/></button>
</div>
:
<form>
<input className={s.editInput} value={editmessageValue} onChange={(e)=> setMessageEditValue(e.target.value)}/>
<button className={`${s.btn} ${s.saveBtn}`} onClick={()=> saveChanges(m.id)}><BeenhereIcon style={{fontSize: 15}}/></button>
</form>
}
</div>
))}
</div>
</div>
)
}
Just in case, this is my container component:
import { connect } from "react-redux"
import { setFloodMessagesAC, addFloodMessageAC, deleteFloodMessageAC, upadateMessageAC } from "../../redux/flood-reducer"
import { FloodChat } from "./FloodChat"
import { useEffect } from 'react'
import data from '../../StaticState/dataForFlood.json'
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)
Why useEffect doesn't work? It seems to me like it should, but it doesnt.
I figured it out. Since I use data from static file, I need to implement functions that get/set data from/to local storage right where I import it which is container component. Once I put those useEffect functions in container component it works perfectly well.
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
useEffect(()=> {
let data = JSON.parse(localStorage.getItem('flood-messages'));
if(data){
props.setFloodMessages(data)
}
console.log('get')
}, [])
useEffect(() => {
localStorage.setItem('flood-messages', JSON.stringify(props.messages));
console.log('set')
}, [props.messages]);
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)

reset new array reactjs infinite scroll

I have tried infinite scroll for reactjs from this link https://www.youtube.com/watch?v=NZKUirTtxcg&t=303 and work perfectly. But I want to improve with my condition.
I have make infite scroll for case products, the product has sub_category and sub_category has one category. For example I have one page showing all products by category (it's showing all sub_category).
The user can choose the product base sub_category (the page showing just what user choose for sub_category).
And my problem is I don't know to reset product variable as new array to fullfill products from sub_category.
I have two component ListInfiteTwo.jsx and UseProductSearch.jsx
ListInfiteTwo.jsx
import React, { useEffect, useState, useRef, useCallback } from 'react';
import axios from 'axios';
import { makeStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import '../styleProduct.css';
import { NavbarPageListProduct, NotFoundPage, COBottomNav } from '../../../components';
import configAPI from '../../../api/configAPI';
import productAPI from '../../../api/productAPI';
import Kcard from '../../Card/Kcard';
import UseProductSearch from './UseProductSearch';
export default function ListInfiniteTwo(props) {
const classes = useStyles();
const [totQtyItem, setTotQtyItem] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
const [category, setCategory] = useState(props.match.params.id);
const [subCategory, setSubCategory] = useState(null);
const [subCategories, setSubCategories] = useState([]);
const [amount, setAmount] = useState(0);
const [limit, setLimit] = useState(6);
const [selectedSubCategory, setSelectedSubCategory] = useState('selectedSubCategory');
const {
loading,
error,
products,
hasMore
} = UseProductSearch(pageNumber, category, limit, subCategory)
const observer = useRef()
const lastProductElementRef = useCallback(node => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore) {
setPageNumber(prevPageNumber => prevPageNumber + 1)
}
})
if (node) observer.current.observe(node)
}, [loading, hasMore])
useEffect(() => {
let getSubCategoriesAct = configAPI.getSubCategory(kategori);
getSubCategoriesAct.then((response) => {
setSubCategories(response.data)
}).catch(error => {
console.log(error)
});
},[])
const callBackAddItemTotal = (data) => {
setTotQtyItem(data)
}
const callBackDeleteItemTotal = (data) => {
setTotQtyItem(data)
}
const callBackCalculateAmount = (data) => {
setAmount(data);
}
const selectSubCategory = (id) => {
setSubKategori(id)
setPageNumber(1)
}
return (
<>
<NavbarPageListProduct
titleView="List Product"
viewPrev="detailOrder"
totalQtyItem={totQtyItem}
cHistoryId={props.match.params.id}
/>
<div className={classes.root}>
<div className="css-ovr-auto">
<div className="css-ovr-auto">
<div className="css-c-1hj8">
<div className="css-c-2k3l">
{
<>
<div className={ selectedSubCategory === 'selectedSubCategory' ? 'css-sb-sl-top-active' : 'css-sb-sl-top'} >
<div className="css-sb-sl-label">
<span className="css-sb-sl-val"> All on Category </span>
</div>
</div>
{subCategories.map((x, z) =>
<div className="css-sb-sl-top" onClick={() => selectSubCategory(x._id) }>
<div className="css-sb-sl-label">
<span className="css-sb-sl-val" >{x.name}</span>
</div>
</div>
)}
</>
}
</div>
</div>
</div>
</div>
<Grid container spacing={1}>
<Grid container item xs={12} spacing={1}>
{
products.length >= 1 ?
products.map((pr, index) =>
<React.Fragment>
<div ref={lastProductElementRef}></div>
<Kcard
ref={lastProductElementRef}
product={pr}
callBackAddItemTotal={callBackAddItemTotal}
callBackDeleteItemTotal={callBackDeleteItemTotal}
callBackCalculateAmount={callBackCalculateAmount}
/>
</React.Fragment>
)
:
<NotFoundPage
content="No Products"
/>
}
</Grid>
</Grid>
</div>
<div>{loading && 'Loading...'}</div>
<div>{error && 'Error'}</div>
{
amount > 0 ?
<COBottomNav
titleBottom="Total Pay"
amount={amount}
titleBtnBottom="Process"
action='proces_list'
/>
:
""
}
</>
)
}
UseProductSearch.jsx
import { useEffect, useState } from 'react';
import axios from 'axios';
export default function UseProductSearch(pageNumber, category, limit, subCategory) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
const [products, setProducts] = useState([])
const [hasMore, setHasMore] = useState(false)
const [lastPage, setLastPage] = useState(0)
useEffect(() => {
setProducts([])
}, [])
useEffect(() => {
setLoading(true)
setError(false)
let cancel
if (subCategory) {
setProducts([])
}
axios({
method: 'GET',
url: process.env.REACT_APP_API_URL + `data-product-pagination`,
params: {
orderby: 'newest',
type: 'verify',
page: pageNumber,
limit: limit,
xkategori: category,
subkategori: subCategory,
},
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(res => {
if (res.data.data) {
if (res.data.data.data.length > 0) {
setProducts(prevProducts => {
return [...new Set([...prevProducts, ...res.data.data.data])]
})
}
}
setHasMore(res.data.data.data.length > 0)
setLoading(false)
setLastPage(res.data.data.last_page)
}).catch(e => {
if (axios.isCancel(e)) return
setError(true)
})
return () => cancel()
}, [pageNumber, category, limit, subCategory])
return { loading, error, products, hasMore }
}
what I have tried to add code on UseProductSearch.jsx
if (subCategory) {
setProducts([])
}
it's work when user choose sub category the page showing new products base on sub_category, but when I scroll down it's reseting the product to empty array.
Thx, for your help...
Try including subCategory as a dependency in your first useEffect hook from useProductSearch instead. This would reset your array whenever the subCategory state changes.
useEffect(() => {
setProducts([])
}, [subCategory])

How to calculate shopping cart total

I have been working on a webpage for a while and am having trouble calculating the total for my shopping list products. I have created a "price" variable in another file, as well as a state for "quantity" in another file, and would like to be able to multiply these two variables together to calculate the "Total". The code for doing this, I have is as follows:
import React, { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux';
import {
getCartItems,
removeCartItem,
onSuccessBuy
} from '../../../_actions/user_actions';
import UserCardBlock from './Sections/UserCardBlock';
import { Result, Empty } from 'antd';
import Axios from 'axios';
import Paypal from '../../utils/Paypal';
function CartPage(props) {
const dispatch = useDispatch();
const [Total, setTotal] = useState(0)
const [ShowTotal, setShowTotal] = useState(false)
const [ShowSuccess, setShowSuccess] = useState(false)
useEffect(() => {
let cartItems = [];
if (props.user.userData && props.user.userData.cart) {
if (props.user.userData.cart.length > 0) {
props.user.userData.cart.forEach(item => {
cartItems.push(item.id)
});
dispatch(getCartItems(cartItems, props.user.userData.cart))
.then((response) => {
if (response.payload.length > 0) {
calculateTotal(response.payload)
}
})
}
}
}, [props.user.userData])
const calculateTotal = (cartDetail) => {
let total = 0;
cartDetail.map(props => {
total += parseInt(props.productData.price, 10) * props.productData.price
});
setTotal(total)
setShowTotal(true)
}
const removeFromCart = (productId) => {
dispatch(removeCartItem(productId))
.then((response) => {
if (response.payload.cartDetail.length <= 0) {
setShowTotal(false)
} else {
calculateTotal(response.payload.cartDetail)
}
})
}
const transactionSuccess = (data) => {
dispatch(onSuccessBuy({
cartDetail: props.user.cartDetail,
paymentData: data
}))
.then(response => {
if (response.payload.success) {
setShowSuccess(true)
setShowTotal(false)
}
})
}
const transactionError = () => {
console.log('Paypal error')
}
const transactionCanceled = () => {
console.log('Transaction canceled')
}
return (
<div style={{ width: '85%', margin: '3rem auto' }}>
<h1>My Cart</h1>
<div>
<UserCardBlock
productData={props.location.state.data}
products={props.user.cartDetail}
removeItem={removeFromCart}
/>
<div style={{ marginTop: '3rem' }}>
<h2>Total amount: ${Total} </h2>
</div>
</div>
{/* Paypal Button */}
{ShowTotal &&
<Paypal
toPay={Total}
onSuccess={transactionSuccess}
transactionError={transactionError}
transactionCanceled={transactionCanceled}
/>
}
</div>
)
}
export default CartPage
Does this look correct? I am a little confused about what to put in the function before the arrows. I know I am doing something wrong... just not sure what. Any help is greatly appreciated. Thank you
the mapping look correct. are you concern about the mapping or the way you change the data? and maybe you can provide more code

Redux state is updated but component state not updated

how is it that my Redux state is updated, and can be log out in the pokelist.js file,
but my state variable is not set properly, is cardList is still an empty array, how do I
set the state properly? I log out the collection in the pokelist.js file, which logs out
an empty array first then an array containing the elements.
// reducer.js file
import { GET_LIMIT_NAMES } from '../actions/PokedexActions';
const initialState = {
collection: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIMIT_NAMES:
return {
collection: action.data
};
default:
return state;
}
};
//===================================================================================================
// action.js file
import Pokemon from '../Pokemon';
export const GET_LIMIT_NAMES = "GET_LIMIT_NAMES";
export const getLimitNames = (limit = 100) => {
// redux-thunk
return async dispatch => {
try {
const allResponse = await fetch(`https://pokeapi.co/api/v2/pokemon/?limit=${limit}`);
const allUrlsData = await allResponse.json();
// console.log(allUrlsData.results);
const collection = [];
Promise.all(allUrlsData.results.map(urlData => {
var pokemon;
fetch(urlData.url).then(resp =>
resp.json()
).then(data => {
// console.log(data);
pokemon = new Pokemon(data);
// pokemon.log();
collection.push(pokemon)
}).catch(err => {
console.log(err);
})
return collection;
}))
// console.log(collection)
dispatch({
type: GET_LIMIT_NAMES,
data: collection
});
} catch (err) {
console.log(err);
}
};
};
//===================================================================================================
// I want to make a list of cards from the Redux state
// pokelist.js
import React, { useState, useEffect } from 'react';
import Card from 'react-bootstrap/Card';
import ListGroup from 'react-bootstrap/ListGroup';
import { useSelector } from 'react-redux';
const PokeList = () => {
const [cardList, setCardList] = useState();
const collection = useSelector(state => state.pokedex.collection);
useEffect(() => {
console.log(collection)
setCardList(collection.map(pokeData =>
<Card key={pokeData.id} style={{ width: '18rem' }}>
<Card.Img variant="top" src={pokeData.sprite + '/100px180'} />
<Card.Body>
<Card.Title>{pokeData.Name}</Card.Title>
<ListGroup className="list-group-flush">
<ListGroup.Item>{'Height: ' + pokeData.height}</ListGroup.Item>
<ListGroup.Item>{'Weight: ' + pokeData.weight}</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>))
}, [collection])
return (
<div>
{cardList}
</div>
)
}
export default PokeList;
//===================================================================================================
// search.js file where i render the component and call the dispatch function
import React, { useState, useEffect } from 'react';
import { Container, Row, Col, Image, Button } from 'react-bootstrap';
import { useDispatch } from 'react-redux';
import PokeList from './pokedex/PokeList';
import * as pokedexActions from './pokedex/actions/PokedexActions';
const Search = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(pokedexActions.getLimitNames(5))
}, [dispatch])
return (
<div>
<Container>
<h2>Search</h2>
<PokeList />
</Container>
</div>
);
}
export default Search;
useState() hook is just a function call. It returns value and function pair. values is just a constant it doesn't have any property binding.
// Think of this line
const [cardList, setCardList] = useState([]);
// As these two lines
const cardList = [];
const setCardList = someFunction;
When you call setCardList your variable is not changed immediately, it doesn't have property binding. It just tells react to return new value on next render.
See my answer React Hooks state always one step behind.
In your case you can simply skip useState hook
const PokeList = () => {
const collection = useSelector(state => state.pokedex.collection);
const cardList = collection.map(
pokeData => (
<Card key={pokeData.id} style={{ width: '18rem' }}>
<Card.Img variant="top" src={pokeData.sprite + '/100px180'} />
<Card.Body>
<Card.Title>{pokeData.Name}</Card.Title>
<ListGroup className="list-group-flush">
<ListGroup.Item>{'Height: ' + pokeData.height}</ListGroup.Item>
<ListGroup.Item>{'Weight: ' + pokeData.weight}</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>)
);
return (
<div>
{cardList}
</div>
)
}

ReactN setGlobal()/useGlobal() not working for me

I'm trying to setGlobal() vars and objects in one page and retrieve them in the next page
StackOverflow.js
import React, {setGlobal, useGlobal, useEffect} from 'reactn'
setGlobal({
myVar: '',
myObj: {}
})
const StackOverflow = () => {
const [myVar, setMyVar] = useGlobal('myVar')
const [myObj, setMyObj] = useGlobal('myObj')
useEffect(() => {
setMyVar('Hello World')
setMyObj({name: 'Mr Magoo'})
}, [])
useEffect(() => {
console.log("MyVar: ", myVar, "\nMyObj: ", myObj)
}, [myVar, myObj])
return (
<div style={{padding: 10}}>
<h2>Click me</h2>
</div>
)
}
export default StackOverflow
StackOverflow2.js
import React, {useGlobal} from 'reactn'
const StackOverflow2 = () => {
const [myVar] = useGlobal('myVar')
const [myObj] = useGlobal('myObj')
return (
<div style={{padding: 10}}>
<h2>StackOverflow2</h2>
<div>My Var: {myVar}</div>
<div>My Obj: {myObj.name}</div>
</div>
)
}
export default StackOverflow2
I expect to be able to setGlobal() vars/objects on one page, and useGlobal() vars/objects in second page. I'm getting nothing.

Resources