I'm trying to make a simulation of a blackjack hand - first user get two random cards, and with each 'hit' they get another one, however after a few 'hit' the app crashes and the 'undefined' error comes up in (array is undefined therefore can't get length). I've tried to save the deck again in the original shuffle, tried putting it all in one, however I can never get it to fully work.
I suspect it's something to do with useState being used incorrectly, however I'm not sure how to fix it.
Here's my code:
import {useState, useEffect} from 'react'
import Card from '../components/Card';
import Total from '../components/Total';
import {deckArray} from '../utils/data'
export default function Home(){
const initialHand = 2
const [dealCards, setDealCards] = useState(false)
const [isStarted, setIsStarted] = useState(false)
const [isReset, setIsReset] = useState(false)
const [hand, setHand] = useState(initialHand)
const [deck, setDeck] = useState(deckArray)
const [total, setTotal] = useState(0)
const [usersCards, setUsersCards] = useState([])
function shuffle(deck){
console.log("shuffle called")
setIsStarted(true)
let i = deck.length;
while (--i > 0) {
let randIndex = Math.floor(Math.random() * (i + 1));
[deck[randIndex], deck[i]] = [deck[i], deck[randIndex]];
}
setUsersCards(deck.slice(-initialHand))
console.log(usersCards)
console.log(deck)
}
useEffect(() => {
if(dealCards===true){
const randomCard = deck[Math.floor(Math.random()*deck.length)];
const newCardsArray = deck.filter(el => el.index !== randomCard.index)
const chosenCardArray = deck.filter(el => el.index === randomCard.index)
const chosenCard = chosenCardArray[0]
setDeck(newCardsArray)
setUsersCards(prevCards => [...prevCards, chosenCard])
console.log(newCardsArray.length)
setDealCards(false)
}
}, [usersCards, dealCards, deck])
useEffect(() => {
if(isReset){
setUsersCards([])
setDeck(shuffle(deckArray))
setDealCards(false)
setTotal(0)
setIsStarted(true)
}
setIsReset(false)
},[isReset, setIsReset])
useEffect(() => {
if(total>=22){
setIsStarted(true)
setIsReset(true)
setDeck(shuffle(deckArray))
}
}, [total, setTotal])
return (
<>
{isStarted ? <>
<Total usersCards={usersCards} total={total} setTotal={setTotal}/>
<Card usersCards={usersCards} />
<button onClick={() => setDealCards(true)}>HIT</button>
<button>STAND</button>
<button onClick={() => setIsReset(true)}>START OVER</button>
</> :
<>
<p>Game over!</p>
<button onClick={() => shuffle(deck)}>PLAY AGAIN</button></>}
</>
)
}
any help much appreciated!
I have a hook that rules my requests. It has state "loading" that becoming true while loading
export const useHttp = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const request = useCallback(async (url, method = 'GET', body = null, headers = {'Content-Type': 'application/json' }) => {
setLoading(true);
try {
const res = await fetch(url, {method, body, headers});
if (!res.ok) {
throw new Error(`Could not fetch ${url}, status: ${res.status}`);
}
const data = await res.json();
setLoading(false);
return data;
} catch(e) {
setLoading(false);
setError(e.message);
throw e;
}
}, [])
const clearError = useCallback(() => {
setError(null);
}, [])
return {
loading,
error,
request,
clearError
}
}
Also i have a service that makes requests:
import { useHttp } from "../hooks/http.hook";
const useNasaService = () => {
const { loading, error, request, clearError } = useHttp();
const _apiBase = 'https://api.nasa.gov/';
const _apiKey = 'api_key=DEMO_KEY';
const getMissionManifest = async (rover) => {
const res = await request(`${_apiBase}mars-photos/api/v1/manifests/${rover}/?${_apiKey}`);
return _transformManifestData(res.photo_manifest);
}
const getImagesData = async (rover, sol, page = 1) => {
const res = await request(`${_apiBase}mars-photos/api/v1/rovers/${rover}/photos?sol=${sol}&page=${page}&${_apiKey}`);
return res.photos.map(_transformImagesData);
}
const _transformImagesData = (data) => {
return {
id: data.id,
sol: data.sol,
earthDate: data.earth_date,
path: data.img_src,
camera: data.camera.full_name,
rover: data.rover.name
}
}
const _transformManifestData = (data) => {
return {
landingDate: data.landing_date,
launchDate: data.launch_date,
maxDate: data.max_date,
maxSol: data.max_sol,
name: data.name,
photos: data.photos,
status: data.status,
totalPhotos: data.total_photos
}
}
return {
loading,
error,
clearError,
getImagesData,
getMissionManifest
}
}
export default useNasaService;
Finally i have a component that needs state "loading" for disabling the inputs.
The question is why "loading" is never getting true in this component:
import useNasaService from '../../services/useNasaService';
const RoverFilter = (props) => {
const { loading } = useNasaService();
console.log(loading); /* always false */
const onRadioChange = (e) => {
props.onRoverSelected(e.target.value);
props.onRoverClicked(e.target.value);
}
return (
<div className="roverFilter" >
<h2 className="roverFilter__title">Select rover</h2>
<div className="roverFilter__inputs">
<label htmlFor="curiosity">Curiosity</label>
<input disabled={loading} type="radio" name="rover-choise" id="curiosity" value="curiosity" onChange={onRadioChange}/>
<label htmlFor="opportunity">Opportunity</label>
<input disabled={loading} type="radio" name="rover-choise" id="opportunity" value="opportunity" onChange={onRadioChange}/>
<label htmlFor="spirit">Spirit</label>
<input disabled={loading} type="radio" name="rover-choise" id="spirit" value="spirit" onChange={onRadioChange}/>
<label htmlFor="perseverance">Perseverance</label>
<input disabled={loading} type="radio" name="rover-choise" id="perseverance" value="perseverance" onChange={onRadioChange}/>
</div>
</div>
)
}
export default RoverFilter;
By the way, in my app there are another components, where "loading" becoming true without any problems. I cant see the difference.
for example, here loading works good:
import { useEffect, useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import useNasaService from '../../services/useNasaService';
import ImageGallerySkeleton from '../imageGallerySkeleton/ImageGallerySkeleton';
import Spinner from '../spinner/Spinner';
import ErrorMessage from '../errorMessage/ErrorMessage';
import SliderModal from '../sliderModal/SliderModal';
const ImageGallery = (props) => {
const {loading, getImagesData, clearError, error} = useNasaService();
const [imagesData, setImagesData] = useState([]);
const [nextPage, setNextPage] = useState(1);
const [firstLoading, setFirstLoading] = useState(true);
const [imagesDataLoaded, setImagesDataLoaded] = useState(false);
const [itemIndex, setItemIndex] = useState(0);
const [sliderOpen, setSliderOpen] = useState(false);
const transitionDuration = 1000;
const onImagesDataLoaded = (newData) => {
setImagesData(data => [...data, ...newData]);
setNextPage(page => page + 1);
setFirstLoading(false);
setImagesDataLoaded(true);
}
const onRequestImages = (rover, sol, page) => {
clearError();
if (!rover || !sol) return;
getImagesData(rover, sol, page)
.then(onImagesDataLoaded);
}
const onSliderClosed = () => {
setSliderOpen(false);
}
useEffect(() => {
onRequestImages(props.selectedRover, props.selectedSol, nextPage);
// eslint-disable-next-line
}, [props.selectedRover, props.selectedSol])
if (sliderOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "visible";
}
function renderItemList(arr) {
const itemList = arr.map((item, i) => {
return (
<CSSTransition
key={item.id}
in={imagesDataLoaded}
timeout={transitionDuration}
classNames='imageGallery__card'>
<li className="imageGallery__card"
onClick={() => {
setSliderOpen(true);
setItemIndex(i);
}}>
<img src={item.path} alt="img from mars"/>
<div className="imageGallery__descr">
<ul>
<li>Rover: {item.rover}</li>
<li>Earth_date: {item.earthDate}</li>
<li>Sol: {item.sol}</li>
<li>{item.camera}</li>
</ul>
</div>
</li>
</CSSTransition>
)
})
return (
<ul className="imageGallery__list">
<TransitionGroup component={null}>
{itemList}
</TransitionGroup>
</ul>
)
}
const spinner = loading && firstLoading ? <Spinner/> : null;
const skeleton = imagesData.length === 0 && firstLoading && !loading && !error ? <ImageGallerySkeleton/> : null;
const items = renderItemList(imagesData);
const errorMessage = error ? <ErrorMessage/> : null;
const counter = imagesData.length === 0 || error ? null :
<h2 className="imageGallery__title">
Showed {loading ? "..." : imagesData.length} photos of {props.totalPhotosInSol}
</h2>
const button = props.totalPhotosInSol === imagesData.length ? null :
<button
onClick={() => onRequestImages(props.selectedRover, props.selectedSol, nextPage)}
disabled={loading}
className="imageGallery__btn">{loading ? "Loading..." : "Load next page" }
</button>
const slider = <SliderModal
open={sliderOpen}
items={imagesData}
slideIndex={itemIndex}
onSliderClosed={onSliderClosed} />
const wrapStyles = firstLoading && loading ? {"padding": "50px"} : null;
return (
<section className="imageGallery" style={wrapStyles}>
{counter}
{spinner}
{skeleton}
{imagesData.length === 0 && !firstLoading ?
<h2 className="imageGallery__title">There is no photo for this sol</h2> :
items
}
{button}
{errorMessage}
{slider}
</section>
)
}
export default ImageGallery;
When you call useState in two different components, those states are independant from eachother. This is still true if you move the useState calls inside a custom hook. If two components call useNasaService (which calls useHttp, which calls useState), then the two components are creating their own states and own functions. If component A starts loading data, that will have no effect on component B.
So ImageGallery is working because it makes a call to getImagesData. This sets the loading state of ImageGallery to true. No other components are affected by this though. When the loading finishes, ImageGallery will set state to have the new data, but again, no other components can use this. RoverFilter on the other hand never calls getImagesData, so its loading state stays false, and it never gets any data.
In react, the typical way to share data is to lift state up. You have a component higher up in the tree, which is responsible for loading the data and setting state. That component then passes the data and functions down to any children that need it. You can either pass the data down using props, or if you need to pass the data a long distance you can consider using context instead.
There's also a wide variety of 3rd party libraries which can be used to manage global state. For example, Redux, Jotai, Zustand, MobX. These can make it simpler to share data between components in far-flung parts of the component tree.
export default function HorizontalLabelPositionBelowStepper() {
const [activeStep, setActiveStep] = React.useState(0);
const [completed, setCompleted] = React.useState({});
const handleComplete = () => {
const newCompleted = completed;
newCompleted[activeStep] = true;
setCompleted(newCompleted);
handleNext();
};
<button
className="footerSaveButton"
disabled={
selectedCoach || selectedValue || selectedCustomer
? disabled
: !disabled
}
onClick={routeChange}
// onClick= {() => {routeChange();handleComplete();}}
>
Save and Continue
</button>
There is a const type functional component inside an export default function. I want to access that functional component on my button click that is also inside a function from an external JS file.
I have in my file index.Js :
const toto = ({tokenMode = false}) => {
const bPrice = usePriceB();
const aPrice = usePriceA();
return (
<SectionWrapper
section={{
title: tokenMode ? 'Pools' : 'Farming',
id: tokenMode ? 'pools' : 'farming',
cards: generateCards().slice(0, 3),
}}
>
<Card cards={generateCards().slice(0, 3)} ethereum={window.ethereum}/>
</SectionWrapper>
);
}
export default toto;
How to call variable aPrice and bPrice in other file .js ?
Edit : I just added my return of this function
might need more info about intended use, but based on the info provided I'd guess you can do
const toto = ({tokenMode = false}) => {
const bPrice = usePriceB();
const aPrice = usePriceA();
return {aPrice, bPrice};
}
export default toto;
this will return the variables as an object that you can then get the values from
you could then use...
const toto = require('./toto.js');
const tamto = toto();
console.log(tamto.aPrice);
console.log(tamto.bPrice);
Update
In the context of a react app , you could use useState
import React, { useState, useEffect } from 'react';
export const getters = {
aPrice:null,
bPrice:null
}
const setters = {
aPrice:null,
bPrice:null
}
export const Toto = () => {
[getters.aPrice , setters.aPrice] = useState(null);
[getters.bPrice , setters.bPrice] = useState(null);
// runs once
useEffect(() => {
setters.aPrice(usePriceA());
setters.bPrice(usePriceB());
}, []);
return (
<SectionWrapper>
...
</SectionWrapper>
)
}
Then you can have the getters and Toto available
import {Toto, getters} from `index.js`
//...
console.log(getters.aPrice);
I am making a pdf viewer using react app. I have a pdf with almost 150 pages and I am using zoom-in and zoom-out icons, I am using scale prop in Page component to accomplish that. But whenever I zoom-in or zoom-out there is a slight delay in pdf file to re-render with zoomed pages. And that delay doesnt happen for small pdf.
Here is my code -
const [pages, setPages] = React.useState(0);
const [pagesArr, setPagesArr] = React.useState([]);
const [scrolledValue, setScrolledValue] = React.useState(0);
const [pageNumber, setPageNumber] = React.useState(1);
const [pdfScale, setPdfScale] = React.useState(1.0);
function onLoadSuccess(param) {
setPages(param.numPages);
const numArr = [];
for (let num = 1; num <= param.numPages; num++) {
numArr.push(num);
}
setPagesArr(numArr);
}
function handleZoominIcon() {
setPdfScale(prev => {
if (prev > 2.1) {
return 2.2;
} else {
return prev + 0.1;
}
});
}
function handleZoomoutIcon() {
setPdfScale(prev => {
console.log(prev);
if (prev <= 0.5) {
return 0.5;
} else {
return prev - 0.1;
}
});
}
return (
<section className = 'ppt-view-pdf' style = {scrolledStyle[1], {zoom: '100%'}} ref = {props.pdfViewRef}>
<Document file = {PPT} onLoadSuccess = {onLoadSuccess}>
{pagesArr.map(element => <Page scale = {pdfScale} key = {element} pageNumber = {element}></Page>)}
</Document>
</section>
)
Is there any solution for this?
I had the same issue. The performance was poor because changing the scale factor will re-render all pages. I solved it using react-window that virtualises the invisible components in the dom. I used VariableSizeList because it was more performant than FixedSizeList. Here is a minimal example:
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Document, Page } from "react-pdf/dist/esm/entry.webpack";
import { VariableSizeList } from "react-window";
const PDFDoc = () => {
const [scale, setScale] = useState(1);
const [numPages, setNumPages] = useState(0);
const docRef = useRef<HTMLDivElement>(null);
const [docWidth, setDocWidth] = useState(0);
const [docHeight, setDocHeight] = useState(0);
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
setNumPages(numPages);
const rect = docRef.current?.getBoundingClientRect();
if (rect) {
setDocWidth(rect.width);
setDocHeight(rect.height);
}
};
return (
<div ref={docRef}>
{/* TODO: add your zoom controls here */}
<Document file={"sample.pdf"} onLoadSuccess={onDocumentLoadSuccess}>
<VariableSizeList
width={docWidth}
height={docHeight}
itemCount={numPages}
estimatedItemSize={numPages}
itemSize={() => scale * docWidth}
>
{({ style, index }) => {
const currPage = index + 1;
return (
<div style={style}>
<Page scale={scale} pageNumber={currPage} />
</div>
);
}}
</VariableSizeList>
</Document>
</div>
);
};
You can install react-window here.
Update
Due to some UI issues with react-window, I decided to switch to react-pdf-viewer at the end. It's working pretty well for me so far.