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.
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.
I have a button named yes in the child component where I delete a list item from array and local storage using the props.id passed from the parent component.
The problem is the item is deleted from array and local storage but is still visible on the screen until I press delete button on another item in the parent component.
when I press delete button in the parent component it opens an overlay. When I press yes button on overlay I want list item to be removed from the screen immediately.
here is the code in the child component.
import React, { useCallback, useState } from "react";
import styles from "./Delete.module.css";
function Delete(props) {
// console.log();
const store = props.store;
const [no, setNo] = useState(false);
let [deleted, setDelted] = useState(store);
console.log(deleted);
console.log("Length :" + store.length);
const noBtnHandler = () => {
console.log("clicked");
setNo(!no);
props.setDel(false);
};
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([...filteredStore]);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
return (
<div className={`${no === false ? styles.del : styles.delOn}`}>
<section>
<div>
<h3>Are you Sure ?</h3>
</div>
<div>
<button type="button" onClick={yesBtnHandler}>
{" "}
Yes{" "}
</button>
<button type="button" onClick={noBtnHandler}>
{" "}
No{" "}
</button>
</div>
</section>
</div>
);
}
export default Delete;
You are passing store from the parent component to the Delete Component and setting a new state here 'deleted'. so you are only calling the setDeleted on the Delete component which wont affect the parent component.
The correct implementation is to have the store state in the parent component if you don't already have it. It is will still be same like deleted state but possibly with a better name. Say const [store, setStore] = useState([])
Define a function to filter out a particular record just like you have in the yesBtnHandler handler. but this function will be defined in the parent component. Say as an example
const removeRecord = (id) => {
const filteredStore = store.filter((task) => task.id !== id);
localStorage.removeItem(id);
setStore(filteredStore);
}
You now need to pass the a function to the Delete Component from the parent rather than passing the whole store. Like
<Delete removeFunc= {() => { removeRecord(id) }} />
After passing this function, you need to call it in your yesBtnHandler function. Like
function Delete({removeFunc}) {
...
const yesBtnHandler = () => {
removeFunc();
setNo(!no);
};
}
Try remove the trailing ...
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([filteredStore]);
//or setDelted(filteredStore);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
my understanding of this is that you're trying to change the state of a parent component from a child component. If that's what you're intending to do then you can do the following:
Define the function Delete(id) {...} inside the parent component rather than the child component.
Next, you'll have to pass both the function and the array to your child component, something like this: <ChildComponent array={array} onDelete={Delete}, where array is the array in your parent component and Delete is the function to delete an item from the array.
Finally, in your child component, with the props passed in correctly, i.e, function ChildComponent({array, Delete}) {...}you can now have access to the array in your parent component, and actually modify it like you'd like. To fire the event listener on the yes button in the child component, do this: <button type="button" onClick={() => Delete(array.id)}> {" "} Yes{" "} </button>
I hope this will be helpful
I am having an issue with pagination on a page in my React application. On the page, search results are rendered when one types into the search bar (naturally). I think my issue arises from how pagination is set up on this page.
Pagination works fine as long as the user clicks back to the first page before searching for anything else. For example, if the user is on page 3 and then types something new into the search bar, the new search will not display without the user clicking 'page 1' again on the pagination bar. However if they returned to page 1 of their initial search before doing the new search, page 1 of the new search displays properly. Hopefully this makes sense. Here is the page where the issue occurs:
import React, { useState } from "react";
import Pagination from "#material-ui/core/Pagination";
import usePagination from "./usePagination.js";
export default function Main({reviews, web3}) {
const [search, setSearch] = useState("");
const [page, setPage] = useState(1);
const updateSearch = (event) => {
setSearch(event.target.value.substr(0, 20));
}
let filteredReviews = reviews.filter(
(review) => {
return review.restaurantName.indexOf(web3.utils.toHex(search)) !== -1;
});
let paginatedReviews = usePagination(filteredReviews, 2);
const handleChange = (e, p) => {
setPage(p);
paginatedReviews.jumpPage(p);
}
return (
<div className="container-fluid mt-5" style={{ minHeight: "100vh" }}>
<div className="row">
<main role="main" className="col-lg-12 ml-auto mr-auto" style={{ maxWidth: '500px' }}>
<div className="content mr-auto ml-auto">
<input type="text" className="form-control" value={search} onChange={updateSearch} />
{filteredReviews.length > 0 ? paginatedReviews.pageData().map((review, key) => {
return (
<>
<div key={key}>
// search result item
</div>
</>
)
})
{filteredReviews.length > 1
? <Pagination
count={paginatedReviews.maxPage}
page={page}
onChange={handleChange}
/>
: null
)
</div>
</main>
</div>
</div>
);
}
and here is usePagination:
import { useState } from "react";
export default function usePagination(allReviews, perPage) {
const [currentPage, setCurrentPage] = useState(1);
const maxPage = Math.ceil(allReviews.length / perPage);
function pageData() {
const start = (currentPage - 1) * perPage;
const end = start + perPage
return allReviews.slice(start, end);
}
function jumpPage(page) {
const pageNumber = Math.max(1, page);
setCurrentPage((currentPage) => Math.min(pageNumber, maxPage));
}
return { jumpPage, pageData, currentPage, maxPage }
}
I thought I could resolve the issue I'm having by adding setPage(1) to updateSearch in order to have the page automatically move to page 1 for each new search, but that didn't work, as you still had to click page 1 on the actual pagination bar for the results to show up.
Edit: I tried renaming currentPage and setCurrentPage in the hook so that they shared the same names as on my page, but that also did not work.
Thanks in advance for any help you can offer. If you need me to elaborate on anything I will happily do so.
How about updating the page in a useEffect? That way you'll make sure all hooks have run and their return values are up-to-date (useEffect runs after render). If you reset the page too early, at the same time as the search query, jumpPage might rely on stale data: your search results and the internal usePagination values like maxPage will not have had a chance to recalculate yet.
Here is a working example based off your codesandbox: https://codesandbox.io/s/restless-dust-28351
Note that to make sure useEffect runs on search change only, you need to wrap jumpPage in a useCallback so that the jumpPage function reference remains the same. In general, I'd recommend you do that to methods returned from custom hooks. This way those methods are safer to consume anywhere, including as dependencies to useEffect, useCallback etc.
Also I'd recommend destructuring the custom hook return values, so that each of them can be used on its own as a hook dependency, like jumpPage in my example above.
I've also removed the page state from App, as it's already tracked in usePagination and returned from there. Having usePagination as a single source of truth that encapsulates all your pagination stuff makes things simpler. Simplicity is a great ideal to strive for:)
Lastly, a small side note: it's best not use <br /> purely as a spacer. It clutters up the markup without contributing any useful semantics, and it's better to leave the spacing concern to CSS.
And good luck with your React endeavors, you're doing great!
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.
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.