Dismount and re-mount a component on each button click? - javascript

I want to conditionally render a custom notification component (not 100% sure how it works, it was written by others and it uses material-ui). I want this notification to appear every time a button gets clicked, but for some reason the notification component will stop re-appearing after it auto-hides itself after some time, even when I click the re-click the button - again, I'm not totally sure how this works.
I just want to know if there's a way to dismount and then re-mount this custom notification component so that it gets newly rendered on each button click (is that bad practice?). This is a simplified ver. of my current logic:
const RandComponent = ()=>{
const [notifType, setNotifType] = useState(1);
const toggleNotif = ()=>{
setNotifType(!notifType);
}
const getNotif = ()=>{
return <Notification type={notifType}/>
}
return (<>
....
<button onClick={toggleNotif}>TOGGLE</button>
{getNotif(notifType)}
</>)
}

You can do it like this
const RandComponent = () => {
const [showNotification,setShowNotification] = useState(true):
const toggleNotificationHandler = () => {
setShowNotification(p => !p);
}
return (<>
<button onClick={toggleNotificationHandler}>TOGGLE</button>
{showNotification && <Notification />}
</>)
}

Related

My function is triggering twice after the DOM changes react

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.

Add animation to filter items on click

I created a filter gallery. I want to animate the filter items every time I click to a buttons. But my codes are not doing it properly. It animates filter items like toggle. If I click on a button first time it animates items, then If I click on another button it shows nothing. After that If I click on another button it animates again. What's wrong with my code? Experts please help me to find out the proper solution. Thanks in advance.
Here is my code:
import React, { useState } from 'react';
import suggestData from '../data/suggest-data.json';
const allCategories = ['All', ...new Set(suggestData.map(item => item.area))];
const Suggest = () => {
const [suggestItem, setSuggestItem] = useState(suggestData);
const [butto, setButto] = useState(allCategories);
const [selectedIndex, setSelectedIndex] = useState(0);
const [anim, setAnim] = useState(false);
const filter = (button) => {
if (button === 'All') {
setSuggestItem(suggestData);
return;
}
const filteredData = suggestData.filter(item => item.area === button);
setSuggestItem(filteredData);
}
const handleAnim = () => {
setAnim(anim => !anim);
}
return (
<div>
<h1>Suggest</h1>
<div className="fil">
<div className="fil-btns">
<div className="fil-btn">
<button className='btn'>Hello</button>
{
butto.map((cat, index) => {
return <button type="button" key={index} onClick={() => { filter(cat); setSelectedIndex(index); handleAnim(); }} className={"btn" + (selectedIndex === index ? " btn-active" : "")}>{cat}</button>
})
}
</div>
</div>
<div className="fil-items">
{
suggestItem.map((item, index) => {
return (
<div className={"fil-item" + (anim ? " fil-item-active" : "")} key={index}>
<h1>{item.name}</h1>
<h2>{item.category}</h2>
<h3>{item.location}</h3>
<h4>{item.type}</h4>
<h5>{item.area}</h5>
</div>
);
})
}
</div>
</div>
</div>
);
}
export default Suggest;
In your handleAnim() function, you are simply toggling the value of anim state. So initially, its value is false and when you click the button for the first time, it is set to true. On clicking the next button, the anim state becomes false because the value of !true is false and hence your animation doesn't work. On next click, becomes true again since !false is true and the toggle continues again and again.
If you want to make your animations work on every click you will need to set the anim state value to true on every button click as below since you seem to depend on the value to set animations. As an alternative, I think it will do just fine if you simply add the animation directly to the enclosing div with class .filter-item instead of relying on states to trigger the animation since after every filter you apply, the elements will be re-rendered and the animation will happen after every re-render.
const handleAnim = () => {
setAnim(true);
}

React/animation - render animation everytime on button click when state or props change

I am trying to have the animation work everytime i click on the buttons in the below project
Code - https://codesandbox.io/s/8fjs8i
Description
There are 3 buttons named first, second and third. when i click on first, the purple box below shows first, when i click on second it shows second and so on, basically the div is updating everytime based on button click.
Problem : but the animation fadIn that i have given to the div works only on application load. how can i make it work everytime i click the button so the the box fadesIN with animation for every click.
const Renders = ({ arr }) => {
const [load, setLoad] = useState(false);
useEffect(() => {
setLoad(true);
setTimeout(() => {
setLoad(false);
}, 1);
}, [arr]);
if (load) return <></>;
return (
<div className="renders">
<div className="zoomers">{arr}</div>
</div>
);
};
This another way you can do it:
const Renders = ({ arr }) => {
const [load, setLoad] = useState(false);
useEffect(() => {
setLoad(true);
setTimeout(() => {
setLoad(false);
}, 1);
}, [arr]);
return (
<div className="renders">
<div className={load ? '' : "zoomers"}>{arr}</div>
</div>
);
};
Now look the different, before you load and unload, now its just a play with the class name, the idea that every time you load element with the animation class so its start to work

How to scroll beyond the focused element in React

I am working on a React App, and using antd as the UI-library. What I intend to do is whenever a button named Open Modal is clicked, a Modal will open up, and when user clicks on the Ok button in the modal, an input field will be displayed and page should automatically be scrolled to the top. However, since the input field contains focus, the scroll happens only until the input field becomes visible. Is there any way to make the page scroll to top, ignoring focused elements using JS only. This is the minimal code to reproduce the example:
const App = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [isInputVisible, setIsInputVisible] = useState(false);
const showModal = () => {
setIsModalVisible(true);
};
const handleOk = () => {
setIsInputVisible(true);
window.scroll(0, 0);
};
const handleCancel = () => {
setIsInputVisible(false);
setIsModalVisible(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
>
/*Some contents...*/
{isInputVisible && <input autoFocus />}
/*Some contents...*/
</Modal>
</>
);
};
This is the sandbox link to reproduce the use-case.
Code Sandbox
You can always use scrollIntoView method.
scrollIntoView
I tried with ref and useRef but it didnt work, so the other solution was to find the modal through it's class name and use the above method.
Check this
sandbox
I am not sure if it will work but I think you can work your way through with the useRef hook where you can focus after the click event. Refer this

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.

Categories