How to achieve 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.

Related

React - fetching data from API

I have custom hook which is catching data from dummyjson API. When I render products, it works fine and perfectly. When I try to catch only one product with this hook via parameter passed via url with useParams in the end it catch this one product, but it cannot render. It seems that a single product didn't manage to load with the help of the hook before it renders. So what is difference when all products are catched are rendered correctly
import axios, { Canceler } from 'axios';
import { useEffect, useState } from 'react';
import { dummyProductType } from '../types/types';
export const useFetch = ({ limit, id }: any) => {
const [products, setProducts] = useState<dummyProductType[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasMore, setHasMore] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
let cancel: Canceler;
const config =
id === null || id === undefined
? {
method: 'GET',
url: `https://dummyjson.com/products/`,
params: { limit: limit },
cancelToken: new axios.CancelToken((c) => (cancel = c)),
}
: {
method: 'GET',
url: `https://dummyjson.com/products/${id}`,
cancelToken: new axios.CancelToken((c) => (cancel = c)),
};
async function fetchData() {
setIsLoading(true);
{
await axios(config)
.then((response) => {
if (Object.hasOwn(config, 'params')) {
setProducts((prev) => {
return [...prev, ...response.data.products];
});
} else {
setProducts({ ...response.data });
}
if (products.length < response.data.total) setHasMore(true);
setIsLoading(false);
})
.catch((err) => {
if (axios.isCancel(err)) return;
setError(true);
});
}
}
fetchData();
return () => cancel();
}, [limit, id]);
return { products, isLoading, error, hasMore };
};
import React, { useCallback, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
import { CardProduct } from '../CardProduct';
import styles from './Cards.module.scss';
const { wrapperContainer } = styles;
const Cards = () => {
const [limit, setLimit] = useState(10);
const { products, isLoading, hasMore } = useFetch({ limit: limit });
const observer = useRef<IntersectionObserver | null>(null);
const lastProduct = useCallback(
(node: Element) => {
if (isLoading) {
return;
}
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setLimit((prev) => prev + 10);
}
});
if (node) observer.current.observe(node);
},
[isLoading, hasMore]
);
console.log(products);
return (
<div className={wrapperContainer}>
{products.map((product, index) => {
if (products.length === index + 1) {
return (
<Link to={`books/${index + 1}`}>
<CardProduct
key={`${index} ${product.title}`}
{...product}
innerRef={lastProduct}
/>
</Link>
);
} else {
return (
<Link to={`books/${index + 1}`}>
<CardProduct key={`${index} ${product.title}`} {...product} />
</Link>
);
}
})}
</div>
);
};
export default Cards;
import {
Button,
CardContent,
Card,
CardHeader,
CardMedia,
dividerClasses,
} from '#mui/material';
import { useParams } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
export const CardDetail = () => {
const { id } = useParams();
console.log(id);
const { products, isLoading, hasMore } = useFetch({
id: Number.parseInt(id),
});
console.log(products, isLoading, hasMore);
return (
<Card key={id}>
<CardHeader title={products[0].title}></CardHeader>
<CardMedia
component='img'
image={products[0].thumbnail}
sx={{ height: '150px' }}></CardMedia>
</Card>
);
};
What am I doing wrong? Or maybe it should be done different?

React: setItems Error when using localStorage

When I am using the setItem() Method, i get no errors and everything is working fine. But when I try to retrieve objects via the getItem() Method, im getting the error: "src\components\UserCourses.js Line 33:8: 'setItems' is not defined no-undef"
//LocalStorage
useEffect(() => {
localStorage.setItem('basket', JSON.stringify(basket));
}, [basket]);
useEffect(() => {
const basket = JSON.parse(localStorage.getItem('basket'));
if (basket) {
setItems(basket);
}
}, []);
basket is my empty array at the start, where i put in items.
import { useStateValue } from './StateProvider'
import {useState, useEffect} from 'react'
function UserCourses ({id, name, prof, language}) {
const [{basket}, dispatch] = useStateValue();
const navigate = useNavigate()
const goCourseDetail = () => navigate(`/course/id=${id}`)
const removeFromBasket = () => {
dispatch({
type: 'REMOVE_FROM_BASKET',
id: id,
});
}
useEffect(() => {
localStorage.setItem('basket', JSON.stringify(basket));
}, [basket]);
useEffect(() => {
const basket = JSON.parse(localStorage.getItem('basket'));
if (basket) {
setItems(basket);
}
}, []);
//Provider
import React, {createContext, useContext, useReducer} from 'react';
//data layer
export const StateContext = createContext();
//Provider
export const StateProvider = ({reducer, initialState, children}) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
//use inside of a component
export const useStateValue = () => useContext(StateContext);
I had a problem with localStorage in a shopping cart I was developing, if it helps. I was able to solve my problem, with the next code:
const initialState = []
const [carrito, setCarrito] = useState(initialState)
useEffect(() => {
const carritoLS = JSON.parse(localStorage.getItem('carrito'))
if (carritoLS) {
setCarrito(carritoLS)
}
}, [])
useEffect(() => {
if (carrito !== initialState){
localStorage.setItem('carrito', JSON.stringify(carrito))
}
}, [carrito])

Why my currentRole isn't set? [ReactJS & Firebase]

I attempted to set the state of currentRole, but it haven't changed anything when I set in setCurrentRole. At the same time, my console.log() is show the value of doc.data().role. How can I get'em?
import React, {useState, useEffect} from "react";
import firebase, {firestore} from "../utils/firebase";
export const AuthContext = React.createContext();
export const AuthProvider = ({children}) => {
const [loading,setLoading] = useState(true)
const [currentUser, setCurrentUser] = useState(null)
const [currentRole, setCurrentRole] = useState('')
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
setCurrentUser(user)
loadingRole(user)
setLoading(false)
console.log(currentRole)
})
}, [])
function loadingRole (user) {
firestore.collection('users').where(firebase.firestore.FieldPath.documentId(), '==', user.uid).get().then((user) => {
user.docs.forEach(doc => {
console.log(doc.data().role)
var roleStr = await doc.data().role
setCurrentRole(roleStr)
})
})
}
if (loading) {
return <p> Loading... </p>
}
return (
<AuthContext.Provider value={{currentUser,currentRole}}>
{children}
</AuthContext.Provider>
)
}
Output:
Auth.js:16 -Have nothing- (console.log(currentRole))
Auth.js:23 Admin (console.log(doc.data().role) line)
LoginComplete.js:14 TgHoVElWCUTViiArtfAKgfNI2J33

TaskList.js:25 Uncaught TypeError: todo.map is not a function

I am working with Reactjs and I can't resolve this problem
import React, { useEffect, useState } from 'react';
import axios from 'axios';
export default function TaskList() {
const [todo, setTodo] = useState([])
useEffect(() => {
const axiosMembers = async () => {
const response = await axios
.get('/todolist').then((res) => {
setTodo(res.data)
console.log(res.data)
})
.catch(err => {
console.log(err)
})
};
axiosMembers();
}, []);
return (
<div>
<ul>
{
todo.map((obj) => {
<li>{obj.title}</li>
})
}
</ul>
</div>
)
}

React custom hook state not 'always there'

I thought had a better grasp of hooks but I've clearly got something wrong here. Not all of the character objects will have what I'm trying to get but it wont work with those that do.
I cna't even build in a check for character.comics.available. Same errors appear. I'm presuming I'm getting them before the state is set? But {character.name} always works.
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
// from the Route path="/character/:id"
const { id } = useParams();
// custom hook. useCharacter.js
const [character] = useCharacter(id);
// this only works sometimes but errors if i refresh the page
// console.log(character.comics.available);
return (
<div>
<h2 className="ui header">Character Details</h2>
<p>Works every time: {character.name}</p>
<div className="ui segment"></div>
<pre></pre>
</div>
);
};
export default CharacterDetail;
Custom hook useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
const useCharacter = (id) => {
const [character, setCharacter] = useState({});
useEffect(() => {
loadItem();
return () => {};
}, [id]);
const loadItem = async (term) => {
const response = await marvel.get(`/characters/${id}`);
console.log(response.data.data.results[0]);
setCharacter(response.data.data.results[0]);
};
return [character];
};
export default useCharacter;
error when console is uncommented
Uncaught TypeError: Cannot read property 'available' of undefined
at CharacterDetail (CharacterDetail.js:11)
...
Here is the character object.
thanks to #Nikita for the pointers. Settled on this...
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
const { id } = useParams();
// custom hook. useCharacter.js
const { isLoading, character } = useCharacter(id);
const isArray = character instanceof Array;
if (!isLoading && isArray === false) {
console.log("isLoading", isArray);
const thumb =
character.thumbnail.path +
"/portrait_uncanny." +
character.thumbnail.extension;
return (
<div>
<h2 className="ui header">{character.name}</h2>
<img src={thumb} />
<div className="ui segment">{character.comics.available}</div>
<div className="ui segment">{character.series.available}</div>
<div className="ui segment">{character.stories.available}</div>
</div>
);
}
return <div>Loading...</div>;
};
export default CharacterDetail;
useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
function useCharacter(id) {
const [character, setCharacter] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
setIsLoading(true);
await marvel
.get(`/characters/${id}`)
.then((response) => {
/* DO STUFF WHEN THE CALLS SUCCEEDS */
setIsLoading(false);
setCharacter(response.data.data.results[0]);
})
.catch((e) => {
/* HANDLE THE ERROR (e) */
});
};
fetchData();
}, [id]);
return {
isLoading,
character,
};
}
export default useCharacter;

Resources