My function is triggering twice after the DOM changes react - javascript

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.

Related

Why my component don't change even when render function it's being recalled? React

Im changing a child component state from its parent using this method:
From parent:
this.changeBottomToolbar(newTool)
Method declared on Child's class
changeBottomToolbar(newToolbarName){
this.setState({selectedToolbar: newToolbarName})
}
It's state change and it re-renders as I check with the console.
The render it's a conditional render that uses a function to get what it should render
getBottomToolbar(){
switch(this.state.selectedToolbar){
case "SelectorAndText":
console.log("Devolviendo el selector")
return <TextBottomToolbar
ref={this.bottomToolbarRef}
fontSizeUpdater = {this.fontSizeUpdater}
fontColorUpdater = {this.fontColorUpdater}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
fontFamilyUpdater = {this.fontFamilyUpdater}
alignmentUpdater = {this.alignmentUpdater}
/>
break
case "FreeLine":
console.log("Devolviendo el Line")
return <LineBottomToolbar
ref={this.bottomToolbarRef}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
shadowColorUpdater={this.shadowColorUpdater}
shadowSizeUpdater={this.shadowSizeUpdater}
/>
break
}
}
And i call it on the render:
render(){
const { classes } = this.props
console.log(this.state.selectedToolbar)
return (
<React.Fragment>
{
this.getBottomToolbar()
}
</React.Fragment>
);
}
As you can see in this image, the code it's been executed correctly and it returns the other component when the state it's changed
But the component ITS NOT CHANGING even tho the render it's been called again and it's state it's changing, im completely shocked, I have no clue on why this happens, please help!!
So the problem was that i wrongly copy-pasted the import (facepalm)
This was the import I was doing so I thought it wasn't chaning
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/TextBottomToolbar'
This corrected the problem:
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/LineBottomToolbar '

React - problems with useState and Firebase

I was trying to resolve this problem, but I have no luck...
I'm using React and 'react-bootstrap'. Getting data from firebase with useState, as you can see in the next code. But also I'm calling a modal as a component, and this modal use useState to show and hide the modal.
export const Models = () => {
const [models, setModels] = useState(null);
useEffect(() => {
firebase.database().ref('Models').on('value', (snapshot) => {
setModels(snapshot.val())
});
}, []);
return models;
}
the problem result when I click on the url to access the modal, this one is shown and the main component goes to firebase and tries to get the data again. So, if I click 3 times on the modal, I will get my data from firebase 3 times.
How can I fix this? to get my data from firebase only one time, regardless of the times that you open the modal window?
The other part of the code
const Gallery = () => {
const [fireBaseDate, setFireBaseDate] = useState(null);
axios.post('https://us-central1-models-gallery-puq.cloudfunctions.net/date',{format:'DD/MM/YYYY'})
.then((response) => {
setFireBaseDate(response.data)
});
let content = Models();
let models = [];
const [imageModalShow, setImageModalShow] = useState(false);
const [selectedModel, setSelectedModel] = useState('');
if(content){
Object.keys(content).map((key, index) =>
models[index] = content[key]
);
models = shuffleArray(models);
console.log(models)
return(
<div className="appContentBody">
<Jumbo />
<Promotion models={models}/>
<div className="Gallery">
<h1>Galería - Under Patagonia</h1>
<Filter />
<div className="img-area">
{models.map((model, key) =>{
let myDate = new Date(model.creationDate);
let modelEndDate = new Date(myDate.setDate(myDate.getDate() + 30)).toLocaleDateString('en-GB')
if(fireBaseDate !== modelEndDate && model.active === true){
return (
<div className="img-card filterCard" key={key}>
<div className="flip-img">
<div className="flip-img-inner">
<div className="flip-img-front">
<img className="single-img card-img-top" src={model.thumbnail} alt="Model"/>
</div>
<div className="flip-img-back">
<h2>{model.certified ? 'Verificada!' : 'No Verificada'}</h2>
<p>Número: {model.contact_number}</p>
<p>Ciudad: {model.city}</p>
<p>Servicios: {model.services}</p>
</div>
</div>
</div>
<h5>{model.name}</h5>
<Button variant="danger" onClick={() => {
setImageModalShow(true)
setSelectedModel(model)}
}>
Ver
</Button>
</div>
);
}
return 0})}
</div>
<Image
show={imageModalShow}
onHide={() => setImageModalShow(false)}
model={selectedModel}
/>
</div>
<Footer />
</div>
)} else {
return (
<div className="loading">
<h1>Loading...</h1>
</div>
)}
}
export default Gallery;
Thanks for your time!
Models is a regular javascript function, not a functional component. So this is not a valid use of hooks, and will not work as expected. See docs on rules of hooks.
A functional component receives props and returns JSX or another React element.
Since it does not, it is basically restarting and calling your effect each time its called by the parent.
Looking at your edit, you should probably just remove the Models function and put the logic in the Gallery component.
The way I read your above component makes it seem like you've defined a custom hook for getting data from firebase.
So first off, I would rename it to useFbData and treat it as a custom hook, so that you can make use of the ESLint Plugin for Hooks and make sure you're following the rules of hooks.
The way you have this above, if it's a function within a component, your function will fire on every render, so the behaviour you are describing is what I would expect.
Depending on how expensive your request is/how often that component renders, this might be what you want, as you probably don't want to return stale data to your component. However, if you feel like the response from the DB should be cached and you have some logic to invalidate that data you could try something like this:
import { useEffect, useRef } from 'react';
const useFbData = invalidationFlag => {
const data = useRef(null);
useEffect(() => {
if (!data.current || invalidationFlag) {
firebase.database().ref('Data').on('value', (snapshot) => {
data.current = snapshot.val();
});
}
}, [invalidationFlag]);
return data.current;
};
export default useFbData;
This way, on the initial run and every time you changed the value of invalidationFlag, your effect inside the useFbData hook would run. Provided you keep track of invalidationFlag and set it as required, this could work out for you.
The reason I used a ref here instead of state, is so that the effect hook doesn't take the data in the dependency array (which would cause it to loop indefinitely if we used state).
This will persist the result of the db response between each call and prevent the call being made multiple times until you invalidate. Remember though, this will mean the data you're using is stale until you invalidate.

How to return an HTML <div> tag from a javascript function in React?

I am working on a React application where I am trying to render text on the screen when a button is clicked. I have defined a function onButtonClick which gets triggered whenever the button is clicked. However, the HTML that I am returning from the function is not rendered on the screen. I am in the learning stages of React so please excuse me if the question seems silly.
class App extends Component {
constructor() {
super();
this.state = {
blockno:0
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1})
return(
<div>
<h3>Some text</h3>
</div>
);
}
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
</div>
);
}
}
The value is being returned, but the framework/browser/etc. has no reason to do anything with that value.
Try thinking about this a different way, a "more React way". You don't want to return the value to be rendered, you want to update state. Something like this:
constructor() {
super();
this.state = {
blockno:0,
showDiv: false // <-- note the new property in state
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1, showDiv: true})
}
Now you're not returning anything, but rather updating the state of the component. Then in your render method you conditionally render the UI based on the current state:
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
{
this.state.showDiv
?
<div>
<h3>Some text</h3>
</div>
: ''
}
</div>
);
}
The click handler doesn't modify the page, it just modifies the state of the component you're writing. The render method is responsible for rendering the UI based on that state. Any time state changes, render will be called again to re-render the output.
(Note: It's not 100% clear if this is exactly the functionality you're looking for in the UI, since it's not really clear what you're trying to build. But the point here is to illustrate how to update state and render output in React. Your logic can be tweaked as needed from there.)
You have to make a render based on your state. Please check the tutorial at the react docs to learn more about how React works. It's really good
Here is a version of your code that works. Hope it helps
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
};
}
OnButtonClick = () => {
//updates the states
this.setState({ blockno: this.state.blockno + 1 });
};
//remember: every time there is an update to the state the render functions re-runs
render() {
//variable holding the blocks in an array
let blocks = []
//if blockno is greater than 0, it checks everytime that there is a state change
if (this.state.blockno > 0) {
//for every block added
for (let index = 0; index < this.state.blockno; index++) {
//We`re going to add to the array of blocks a new div with the block number
blocks.push(
<div>
<h3>My block number is {index}</h3>
</div>
);
}
}
return (
<div>
<div>
{/**button that updates the state on every click */}
<button onClick={this.OnButtonClick}>
Click me to add a new div!
</button>
</div>
{/**This render the blocks variable that holds the divs */}
{blocks}
</div>
);
}
}
What I see is that you are trying to build a counter. The value that you're returning from the click handler function can't be rendered, instead you need to manage it in the render function as follow:
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
}
}
OnButtonClick = () => {
this.setState(prevState => ({ blockno: prevState.blockno + 1 }));
}
render() {
return(
<div>
{this.state.blockno > 0 && <div>some text {this.state.blockno}</div>}
<Button onButtonClick={this.OnButtonClick} />
</div>
);
}
}
Also note that the setState method is asynchronous, please read the documentation https://reactjs.org/docs/react-component.html#setstate

React updating DOM in an unpredictable manner with setState and class names

I'm attempting to do an animation with React and CSS classes. I have created a live demo, if you visit it and click the Start button you will see the text fade in and up one by one. This is the desired animation that I am after.
However, there seems to be issues of consistency when you hit Start multiple times and I cannot pinpoint why.
The Issue: Below is a recording of the issue, you can see the number 1 is not behaving as expected.
live demo
The process: Clicking Start will cancel any previous requestAnimationFrame' and will reset the state to it's initial form. It then calls the showSegments() function with a clean state that has no classNames attached to it.
This function then maps through the state adding a isActive to each segment in the state. We then render out the dom with a map and apply the new state.
This should create a smooth segmented animation as each class gets dropped one by one. However when i test this in Chrome (Version 56.0.2924.87 (64-bit)) and also on iOS, it is very inconsistent, sometimes it works perfectly, other times the first DOM element won't animate, it will just stay in up and visible it's completed transitioned state with "isActive".
I tried to replicate this issue in safari but it worked perfectly fine, I'm quite new to react so i am not sure if this is the best way to go about things, hopefully someone can offer some insight as to why this is behaving quite erratic!
/* MotionText.js */
import React, { Component } from 'react';
import shortid from 'shortid';
class MotionText extends Component {
constructor(props) {
super(props);
this.showSegments = this.showSegments.bind(this);
this.handleClickStart = this.handleClickStart.bind(this);
this.handleClickStop = this.handleClickStop.bind(this);
this.initialState = () => { return {
curIndex: 0,
textSegments: [
...'123456789123456789123456789123456789'
].map(segment => ({
segment,
id: shortid.generate(),
className: null
}))
}};
this.state = this.initialState();
}
handleClickStop() {
cancelAnimationFrame(this.rafId);
}
handleClickStart(){
cancelAnimationFrame(this.rafId);
this.setState(this.initialState(), () => {
this.rafId = requestAnimationFrame(this.showSegments);
});
}
showSegments() {
this.rafId = requestAnimationFrame(this.showSegments);
const newState = Object.assign({}, this.state);
newState.textSegments[this.state.curIndex].className = 'isActive';
this.setState(
{
...newState,
curIndex: this.state.curIndex + 1
},
() => {
if (this.state.curIndex >= this.state.textSegments.length) {
cancelAnimationFrame(this.rafId);
}
}
);
}
render(){
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
}
export default MotionText;
Thank you for your time, If there any questions please ask
WebpackBin Demo
Changing the method to something like this works
render(){
let d = new Date();
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={d.getMilliseconds() + obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
How this helps is that, the key becomes different than previously assigned key to first span being rendered. Any way by which you can make the key different than previous will help you have this animation. Otherwise React will not render it again and hence you will never see this in animation.

React/webpack conditionally return require.ensure component (code splitting)

I have a sub component that does not need to be loaded immediately that I want to split out. I am trying to conditionally load in a react component via require.ensure. I am not getting any console errors but I am also not seeing anything being loaded. Here is the code I am calling :
renderContentzones() {
if (this.props.display ) {
return require.ensure([], () => {
const Component = require('./content-zones/component.jsx').default;
return (
<Component
content={this.props.display}
/>
);
});
}
return null;
}
It is just rendering a blank screen currently (no errors). This previously worked when I used import 'displayComponent' from './content-zones/component.jsx' and just returned it like you normally would in react, instead of this require.ensure but. Not sure what I am doing wrong here, any idea how to make something like this work? Thanks!
This is one way to do it, using the state to show the dynamic loaded component:
constructor(){
this.state = {cmp:null};
}
addComponent() {
const ctx = this;
require.ensure(['../ZonesComponent'], function (require) {
const ZonesComponent = require('../ZonesComponent').default;
ctx.setState({cmp:<ZonesComponent />});
});
}
render(){
return (
<div>
<div>Some info</div>
<div><button onClick={this.addComponent.bind(this)}>Add</button></div>
<div>
{this.state.cmp}
</div>
</div>
);
}
When you press the button add the component will be shown.
Hope this help.

Categories