i try to code two components within a game: One component is a timer-component. From here, the user can pause the game. By pressing a button within that component, a redux state (gamePaused: boolean) is changed. The second component contains a interval which should be paused/resumed by the redux state.
The problem is, that the second component will be re-rendered on the redux state change, so does the interval variable within the component. So the variable from the first rendering isnt responsive anymore and it cant be stopped or resumed.
So my first idea was, to store the interval value wihin a useMemo()-Hook. But i dont really know how the syntax should look in this case. Because i have to memoize an empty variable outside the start() and pause() functions since the interval variable has to be accessable for both functions.
Im thankful for every help. (And im open for solutions, other than those whith the useMemo Hook)
import React, { useState, useRef, useMemo } from "react";
import { connect, useDispatch } from "react-redux";
import { increment } from "../../../redux/slices/SavegameSlice";
import { addFirst } from "../../../redux/slices/OperationalDataSlice";
function GenerateNumber({ gamePaused }) {
var dispatch = useDispatch();
const [number, setnumber] = useState(Math.floor(Math.random() * 10));
const [disabled, setdisabled] = useState(false);
function increment() {
dispatch(increment(income));
dispatch(
addFirst({
icon: "",
name: "",
amount: number,
})
);
setnumber(Math.floor(Math.random() * 10));
var interval; // That is the variable i try to save
var width = 0;
var progressbar = useRef();
function start() {
clearInterval(interval);
interval = setInterval(frame, 25);
function frame() {
if (width >= 100) {
width = 0;
increment();
progressbar.current.style.width = width + "%";
clearInterval(interval);
} else {
width++;
progressbar.current.style.width = width + "%";
}
}
}
function pause() {
clearInterval(interval);
}
return (
<div className="parent">
<div className="header">
<div className="icon center-column">
</div>
<div className="caption">
<span className="caption-span">Generate Number</span>
</div>
</div>
<div className="low">
<div className="loadingbar-parent center-column">
<div className="loadingbar">
<div className="loadingbar-inner" ref={progressbar}></div>
</div>
</div>
<div className="buttonarea">
<div className="button-parent">
<button
className="workbutton center-column"
onClick={start}
disabled={disabled ? "true" : ""}
>
<span className="workbutton-span">Work</span>
</button>
<button className="workbutton center-column" onClick={pause}>
<span className="workbutton-span">Pause</span>
</button>
</div>
</div>
</div>
</div>
);
}
function mapStateToProps(state) {
return {
gamePaused: state.operationalData.value.gamePaused,
};
}
export default connect(mapStateToProps)(GenerateNumber);
Related
I'm practicing my skills in react, and I want to do this app: https://pokemon-game-xyz-vue.netlify.app/, my app is working fine, but there is something rare that is happening. I create a js file helper where in that function return the URL for the picture of the pokemon and also randomPokemon.
so... in the beginning works well, but after I click with the correct name, the color of my img change which is fine, but "gettingPokemon" trigger again and the image changes. so that means my function is triggering twice when the DOM changes.
import { useState } from "react";
import { GetImage } from "./components/GetImage";
import { useFetch } from "./hooks/usefetch";
import { gettingPokemon} from "./helpers/gettingRandomPokemon"
export const App = () => {
const {pokemon1,pokemon2,pokemon3,pokemon4,isLoading} = useFetch(`https://pokeapi.co/api/v2/pokemon`)
const [comparacion, setComparacion] = useState(false)
const {randomPokemon,urlRandomPokemn} = gettingPokemon(pokemon1,pokemon2,pokemon3,pokemon4)
const getName = (e) => {
let pickPokemonName = e.target.id
if(pickPokemonName === randomPokemon?.name) {
setComparacion(true)
} else {
setComparacion(false)
}
}
return (
<>
<h1>quien es ese pokemon?</h1>
<hr />
{
(!isLoading) && <GetImage urlRandomPokemon={urlRandomPokemn} comparacion={comparacion}></GetImage>
}
{
(isLoading) && <div className="alert alert-info text-center">
Loading...
</div>
}
{
(!isLoading) &&
<>
<ul>
<li><button onClick={getName} id={pokemon1.name} >{pokemon1.name}</button></li>
<li><button onClick={getName} id={pokemon2.name} >{pokemon2.name}</button></li>
<li><button onClick={getName} id={pokemon3.name} >{pokemon3.name}</button></li>
<li><button onClick={getName} id={pokemon4.name} >{pokemon4.name}</button></li>
</ul>
</>
}
</>
)
}
export const gettingPokemon = (pokemon1,pokemon2,pokemon3,pokemon4) => {
let pokemones = [pokemon1,pokemon2,pokemon3,pokemon4]
let randomPokemon = pokemones[Math.floor(Math.random() * pokemones.length)]
let urlRandomPokemn = randomPokemon?.sprites.other.dream_world.front_default
console.log('me dispare otra vez');
return {
randomPokemon,
urlRandomPokemn,
}
}
export const GetImage = ({urlRandomPokemon,comparacion}) => {
return (
<img src={urlRandomPokemon} alt='' className={(comparacion) ? 'claro' : 'oscuro'}/>
)
}
Functional React components are, as the name suggests, just functions. Each time you change any state in your component that React is aware of (such as from props, or via the setComparacion setter in your onClick handlers), the function will re-run again, and any code in it will be re-run as well.
If you want to preserve data across renders, you want to use useState to create and keep state variables, or (more likely in this case) useMemo to prevent code from re-running unless dependent variables change.
In this case, you probably want something like:
const { randomPokemon, urlRandomPokemn } = useMemo(
() => gettingPokemon(pokemon1, pokemon2, pokemon3, pokemon4),
[pokemon1, pokemon2, pokemon3, pokemon4]
);
What this does is say "run this function if hasn't been run at all, or when any of the values in the variables [pokemon1, pokemon2, pokemon3, pokemon4] change, otherwise give me the value of the last time it ran". The component may re-render as many times as necessary, but unless one of the four dependent values changes, gettingPokemon will not get re-run, which will preserve your randomly-selected value.
import { useState } from 'react'
import styles from '../styles/Login.module.css'
import {motion as m} from 'framer-motion'
const words = ['Sheetal','Jackie','Rohan','Narayan','Budwiser']
export default function Login() {
const [step,setStep] = useState(0)
const initial = {
transform:'translateX(100%)'
}
const animate = {
transform:'translateX(0)'
}
return (<main>
<div className={styles.carousel_parent}>
<div className={styles.track}>
{
<m.div initial={initial} animate={animate} className={styles.slides}><div>{words[step]}</div></m.div>
}
</div>
<div className={styles.controls}>
<button
disabled={step<1?true:false}
onClick={()=>{
const total = words.length;
if(step - 1 < 0){
return false
}else{
setStep(step-1)
}
}}>Prev</button>
<div className={styles.dots}>
</div>
<button
disabled={step+1>words.length-1?true:false}
onClick={()=>{
const total = words.length;
if(step + 1 > words.length-1){
return false
}else{
setStep(step+1)
}
}}>Next</button>
</div>
</div>
</main>)
}
The above is my code. What I want to acheive is when someone clicks on the next button, the slide should change with transition. I have acheived everything however the transition effect using framer motion is not working.
**What have I tried using : **
I have tried prefixing 'm.' in litrally every component but nothings seems to work.
I'm building a portfolio app in React JS and one of my pages is an About Me page. I stumbled upon a youtube video that builds an infinite carousel using vanilla JavaScript, and during my initial testing it worked. However, when I navigate away from my 'About Me' page and return, it explodes with a "TypeError: Cannot read property 'style' of null" within my About Me component "stepNext; src/components/about-me/AboutMe.js:34".
import React from "react";
import "./AboutMe.css"
import { Button,
Fade,
Grow,
Typography } from '#material-ui/core'
import { ArrowBackIos, ArrowForwardIos } from "#material-ui/icons";
import { Background, Adventures, Hobbies } from "./about-me-components/index";
export const AboutMe = () => {
const slider = document.querySelector('.slider-about-me')
const carousel = document.querySelector('.carousel-about-me')
let direction = 1
const stepPrevious = () => {
if (direction === 1) {
slider.appendChild(slider.firstElementChild)
}
direction = -1
console.log("Previous", direction)
carousel.style.justifyContent = 'flex-end'
slider.style.transform = 'translate(33%)'
}
const stepNext = () => {
if (direction === -1) {
slider.prepend(slider.lastElementChild)
}
direction = 1
console.log("Next", direction)
carousel.style.justifyContent = 'flex-start'
slider.style.transform = 'translate(-33%)'
}
const sliderAppend = () => {
if (direction === 1) {
slider.appendChild(slider.firstElementChild)
} else if (direction === -1) {
slider.prepend(slider.lastElementChild)
}
slider.style.transition = 'none'
slider.style.transform = 'translate(0)'
setTimeout(() => {slider.style.transition = 'all 0.5s'})
}
return (
<>
<Fade
in={true}
timeout={1500}
>
<div
id='about-me-container'
>
<div className="controls">
<div
className='arrow-span-about-me arrow-left-about-me'
>
<Button
className='button-about-me arrow-about-me'
variant='contained'
onClick={stepPrevious}
>
<ArrowBackIos
className="arrow-back-about-me"
/>
</Button>
</div>
<div
className='arrow-span-about-me arrow-right-about-me'
>
<Button
className='button-about-me arrow-about-me'
variant='contained'
onClick={stepNext}
>
<ArrowForwardIos
className="arrow-forward-about-me"
/>
</Button>
</div>
</div>
<div
id="about-me-carousel-container"
>
<div
className='carousel-about-me'
>
<div
className='slider-about-me'
onTransitionEnd={sliderAppend}
>
<section className='text-white'><Background /></section>
<section className='text-white'><Adventures /></section>
<section className='text-white'><Hobbies /></section>
</div>
</div>
</div>
</div>
</Fade>
</>
)
}
The only reason I chose this route is because I haven't been able to find a half decent infinite carousel module with easy customization abilities. As much as I would prefer for this to work, I'm open to suggestions and/or solutions. Much appreciated!
I would suggest using useRef instead of document.querySelector
document.querySelector happens outside of that lifecycle, making what it returns unreliable, while refs happen within it. (Though doesn’t get reset because of a lifecycle event like a re-render.) This ensures the object returned by the ref is an accurate representation of the current state of the virtual DOM.
I think this is the reason why you are encountering the said error when you go away and back from the About Page.
Here's an example:
https://codesandbox.io/s/objective-fast-vhc27?file=/src/modalAndButton.jsx:531-648
I'm having a problem with inline style changes that are reset when my dispatch is finished, because the state is being re-rendered, despite the other functionality of my component is working (you can still see that the counter is not stopping).
Here's a demonstration of what I mean.
You can see that the orange bar of the left box vanishes when the orange bar of the right bar finishes (the animation ends). Essentially what I'm doing here is changing the width property in inline styles.
import React, { useEffect, useRef } from "react";
import { useDispatch, connect } from "react-redux";
import { addProfessionExperience } from "../../actions/index";
import "./Professions.sass";
const timers = [];
const progressWidths = [];
const mapStateToProps = (state, ownProps) => {
const filterID = +ownProps.match.params.filter;
const { professions, professionExperience } = state;
return {
professions: professions.find(item => item.id === filterID),
professionExperience: professionExperience
};
};
const produceResource = (dispatch, profession, sub, subRef) => {
if(timers[sub.id]) return;
/*
* Begin the progress bar animation/width-change.
*/
Object.assign(subRef.current[sub.id].style, {
width: "100%",
transitionDuration: `${sub.duration}s`
});
/*
* Updates the progress text with the remaining time left until done.
*/
let timeLeft = sub.duration;
const timeLeftCountdown = _ => {
timeLeft--;
timeLeft > 0 ? setTimeout(timeLeftCountdown, 1000) : timeLeft = sub.duration;
subRef.current[sub.id].parentElement.setAttribute("data-duration", timeLeft + "s");
}
setTimeout(timeLeftCountdown, 1000);
/*
* Dispatch the added experience from profession ID and sub-profession level.
* We do not allow duplicate timers, only one can be run at a time.
*/
const timer = setTimeout(() => {
Object.assign(subRef.current[sub.id].style, {
width: "0%",
transitionDuration: "0.2s"
});
dispatch(addProfessionExperience({ id: profession.id, level: sub.level }));
delete timers[sub.id];
}, sub.duration * 1000);
timers[sub.id] = timer;
};
const isSubUnlocked = (professionMaxExperience, subLevel, professionExperience) => {
if(professionExperience <= 0 && subLevel > 1) return false;
return professionExperience >= getExperienceThreshold(professionMaxExperience, subLevel);
};
const getExperienceThreshold = (professionMaxExperience, subLevel) => (((subLevel - 1) * 1) * (professionMaxExperience / 10) * subLevel);
const ConnectedList = ({ professions, professionExperience }) => {
const currentExperience = professionExperience.find(item => item.profession === professions.id);
const subRef = useRef([]);
const dispatch = useDispatch();
useEffect(() => {
subRef.current = subRef.current.slice(0, professions.subProfessions.length);
}, [professions.subProfessions]);
return (
<div>
<div className="list">
<ul>
{professions.subProfessions.map(el => {
const unlocked = isSubUnlocked(
professions.maxExperience,
el.level,
(currentExperience ? currentExperience.amount : 0)
);
const remainingExperience = getExperienceThreshold(professions.maxExperience, el.level) - (currentExperience ? currentExperience.amount : 0);
return (
<li
key={Math.random()}
style={{ "opacity": unlocked ? "1" : "0.5" }}
>
<div className="sprite">
<img alt="" src={`/images/professions/${el.image}.png`} />
</div>
<div className="caption">{el.name}</div>
<div
className="progress-bar"
data-duration={unlocked ? `${el.duration}s` : `${remainingExperience} XP to Unlock`}
data-identifier={el.id}
>
<span ref={r => subRef.current[el.id] = r} ></span>
</div>
<div className="footer">
<button
className="btn"
onClick={() => unlocked ? produceResource(dispatch, professions, el, subRef) : false}
>
{unlocked ?
`Click` :
<i className="fa fa-lock"></i>
}
</button>
</div>
</li>
);
})}
</ul>
</div>
</div>
);
};
const List = connect(mapStateToProps)(ConnectedList);
export default List;
How can I make it so the orange bars persist on their own and not disappears when another one finishes?
One problem is that you're using Math.random() to generate your keys. Keys are what the virtual DOM uses to determine whether an element is the "same" as the one on a previous render. By using a random key, you're telling the virtual DOM that you want to spit out a brand new DOM element instead of reusing the prior one, which means the new one won't retain any of the side effects you placed on the original element. Read up on React's reconciliation for more info on this.
Try to use keys that logically represent the thing you're rendering. In the case of your code, el.id looks like it may be a unique identifier for the subprofession you're rendering. Use that for the key instead of Math.random().
Additionally, refs are going to make reasoning about your code really difficult. Rather than using refs to manipulate your DOM, use state manipulation and prop passing, and let React re-render your elements with the new attributes.
I'm really new with React and having trouble with something. I'm trying to set the a progress bar's progress equal to a variable I have inside of a constructor function in my react component. I'm just using a regular old bootstrap progress bar. The progress bar appears, but no width setting I've tried will actually fill the progress bar. If anyone can help out I'd be grateful. Here's my code:
import React, { Component } from 'react';
import SalesData from '../data/salesData';
class ProgressBar extends Component {
constructor() {
super();
this.ordersInProgress = 0;
this.totalSales = 0;
this.orderGoal = 260;
SalesData.forEach(item => {
this.ordersInProgress += item.orders;
this.totalSales += item.orders * item.price;
});
this.progressBarPercentage = Math.floor((this.ordersInProgress / this.orderGoal) * 100);
this.completedOrders = this.orderGoal - this.ordersInProgress;
}
render() {
return (
<div className="progressBarBlock">
<div className = "progress progressBar">
<div className='progress-bar progressBarColor'
role='progressbar'
aria-valuenow='70'
aria-valuemin='0'
aria-valuemax='100'
styles={{width: this.progressBarPercentage}}>
</div>
</div>
</div>
)
}
}
export default ProgressBar
tldr: I'm trying to set the progress bar progress equal to that progressBarPercentage I've calculated in my constructor function, and it's not filling. Again, thank you in advance for any help you can provide!
It is not recommemded to you class fields (e.g. this.ordersGoal) to keep data in, especially if your component depends on that data for rendering, as it will not track changes. this.state / this.setState() are designed fro this purpose.
2) It should be style, not styles, as already mentioned above
class ProgressBar extends Component {
constructor() {
super();
let ordersInProgress = 0;
let totalSales = 0;
let orderGoal = 260;
SalesData.forEach(item => {
ordersInProgress += item.orders;
totalSales += item.orders * item.price;
});
const progressBarPercentage = Math.floor((ordersInProgress / orderGoal) * 100);
const completedOrders = orderGoal - ordersInProgress;
this.state = { progressBarPercentage, completedOrders };
}
render() {
return (
<div className="progressBarBlock">
<div className = "progress progressBar">
<div className='progress-bar progressBarColor'
role='progressbar'
aria-valuenow='70'
aria-valuemin='0'
aria-valuemax='100'
style={{width: this.state.progressBarPercentage}}>
</div>
</div>
</div>
)
}
}
export default ProgressBar
3) After a closer study I found numerous problems: 1) data should come as props 2) state is not needed in this case 3) component becomes stateless functional 4) the percentage was always zero due to an error in calculation
This is the sandbox to see it working https://codesandbox.io/s/o51lnz7o99