What i'm trying to accomplish :
after click on preview div, i want to expand it to screen size, showing some loader of my own, and when the new url is ready - show its content.
is it even possible ?
const Works = ({changed}) => {
const [works, setWorks] = useState([])
useEffect(() => {
const myWorks = workService.getWorks()
setWorks(myWorks)
}, [])
const openLink = async link => {
// await load the url and show some content
// show url content
}
return (
<Page className={styles.works} changed={changed}>
{
works.map(({id, title, link, imgUrl}, i) => {
return (
<div
key={id}
className={styles.work}
style={style}
onClick={() => handleClick(i)}>
<button onClick={() => openLink(link)}>Open App</button>
</div>
)
})
}
</Page>
)
}
you can hide the content you want till it's completely loaded and ready then display it.
Related
How can I wait until all images are loaded in React.JS?
The problem is that I do some calculations (image processing) using those images and the results are different between multiple refreshes of the browser because at certain times the calculations start before the images are 100% loaded. Is there a way to wait until 100% of all images are loaded?
I've tried something like this:
const [imagesLoaded, setImagesLoaded] = useState(false);
{images.map((image, index) => {
return <ShowImage source={image} index={index+1} key={index} onLoad={() => setImagesLoaded(index)} />
})}
const ShowImage: React.FC<{source:string, index: number, onLoad: () => void}> = ({source, index, onLoad}) => {
return (
<img src={source} width="640" height="480" id={'image' + index} alt={'image' + index} onLoad={onLoad}/>
)
}
And the 'checker':
useEffect(() => {
if(imagesLoaded === 5) {
... do something
}
}, [imagesLoaded]);
But the problem is that this is working only for the first page render, if I refresh is not working, but I refresh one more time is working again and sometimes needs more refreshes, what's the problem?
You could refactor your ShowImage component to only set imagesLoaded when the last image source URL is loaded.
function ShowImages({ urls, setImagesLoaded }) {
const onLoad = (index) => {
if (index === urls.length - 1) {
setImagesLoaded(true)
}
};
return (
<>
{urls.map((url, index) => (
<img src={url} onLoad={() => onLoad(index)} key={url} />
))}
</>
);
}
export default ShowImages;
And use the component something like this
const [imagesLoaded, setImagesLoaded] = useState(false);
...
useEffect(() => {
if(imagesLoaded) {
... do something
}
}, [imagesLoaded]);
...
<ShowImages urls={images} setImagesLoaded={setImagesLoaded}/>
Working example here
Your images might not load in the intended order. If your 4th image loads after the 5th one, then the value of imagesLoaded will be 4 at the end.
To prevent that, I would increment the value one at a time, so when all five images are loaded the value will be 5.
onLoad={() => setImagesLoaded(v => v + 1)}
import { useState } from "react";
function Image({ image }) {
const [favourite, setFavourite] = useState([]);
const storeFavourites = () => {
setFavourite((gif) => [...gif, image]);
console.log(favourite);
};
const viewFavourites = () => {
favourite.map((url) => {
<img src={url} />;
});
};
return (
<div>
<img src={image} />
<button onClick={storeFavourites}>Like</button>
<button onClick={viewFavourites}>View Favourites</button>
</div>
);
}
export default Image;
This is a giphy site which generates a random gif when loaded and will generate a different gif based on search results. There is a like button to like the image and it was able to store the image as favourites in a state under const [favourite, setFavourite], however I was unable to display any of the favourite images when I clicked on View Favourites.
Looks like you are returning the view from the function which is not used anywhere inside the component's return function, which is the reason why nothing is rendered.
Here's something what you could do:
Store the favourite images in a list.
Display the image when a state value is toggled.
So, Here's what you could change:
import { useState } from "react";
function Image({ image }) {
const [favourite, setFavourite] = useState([]);
const [showFavourites, setShowFavourites] = useState(false);
const storeFavourites = () => {
setFavourite((gif) => [...gif, image]);
console.log(favourite);
};
const viewFavourites = () => {
setShowFavourites(!showFavourites);
};
return (
<div>
<img src={image} />
<button onClick={storeFavourites}>Like</button>
<button onClick={viewFavourites}>View Favourites</button>
{showFavourites && favourite.map((url, index) => {
<img src={url} key={index}
})}
</div>
);
}
export default Image;
If you don't wish to toggle the view but instead just it to change just one time, you could change the viewFavourites to this:
const viewFavourites = () => {
setShowFavourites(true);
};
I am new to React and trying to learn more by creating projects. I made an API call to display some images to the page and I would like to create a like button/icon for each image that changes to red when clicked. However, when I click one button all of the icons change to red. I believe this may be related to the way I have set up my state, but can't seem to figure out how to target each item individually. Any insight would be much appreciated.
`
//store api data
const [eventsData, setEventsData] = useState([]);
//state for like button
const [isLiked, setIsLiked] = useState(false);
useEffect(() => {
axios({
url: "https://app.ticketmaster.com/discovery/v2/events",
params: {
city: userInput,
countryCode: "ca",
},
})
.then((response) => {
setEventsData(response.data._embedded.events);
})
.catch((error) => {
console.log(error)
});
});
//here i've tried to filter and target each item and when i
console.log(event) it does render the clicked item, however all the icons
change to red at the same time
const handleLikeEvent = (id) => {
eventsData.filter((event) => {
if (event.id === id) {
setIsLiked(!isLiked);
}
});
};
return (
{eventsData.map((event) => {
return (
<div key={event.id}>
<img src={event.images[0].url} alt={event.name}></img>
<FontAwesomeIcon
icon={faHeart}
className={isLiked ? "redIcon" : "regularIcon"}
onClick={() => handleLikeEvent(event.id)}
/>
</div>
)
`
Store likes as array of ids
const [eventsData, setEventsData] = useState([]);
const [likes, setLikes] = useState([]);
const handleLikeEvent = (id) => {
setLikes(likes.concat(id));
};
return (
<>
{eventsData.map((event) => {
return (
<div key={event.id}>
<img src={event.images[0].url} alt={event.name}></img>
<FontAwesomeIcon
icon={faHeart}
className={likes.includes(event.id) ? "redIcon" : "regularIcon"}
onClick={() => handleLikeEvent(event.id)}
/>
</div>
);
})}
</>
);
Your issue is with your state, isLiked is just a boolean true or false, it has no way to tell the difference between button 1, or button 2 and so on, so you need a way to change the css property for an individual button, you can find one such implementation by looking Siva's answer, where you store their ids in an array
I am trying to implement a search feature, using custom hooks, and retrieving data from an external API. The problem is that when the user enters an invalid search term, it immediately returns the JSX element inside the if condition. How can I delay showing the JSX element using setTimeout, so first show the user a spinner or something, then redirect them to the previous page. This is what I have:
if (!movies[0])
return (
<>
<Spinner />
<h1>NO RESULTS FOUND</h1>
</>
);
This is what i'd like to do theoretically:
if (!movies[0]) => show Spinner (for 200ms) => setTimeout(show error and redirect after 200ms, 200)
how could I implement this in react?
You can maybe try something like this :
const [loading, setLoading] = useState(true);
useEffect(() => {
let timer = setTimeout(() => {
setLoading(false);
}, 2000);
return () => { clearTimeout(timer) };
}, [];
return (
<>
{loading ? <div>Loading</div> : <div>...<div>}
</>
);
So I'm building a simple react app that fetches a bunch of images and displays them as cards.
The intention is to show an info message until all the images have loaded, then removing the notice again.
const App = () => {
const [cardInfo, setCardInfo] = useContext(CardInfoContext)
useEffect(() => {
fetchData(setCardInfo)
}, [])
useEffect(() => {
const app = document.querySelector('.app')
for(const child of app.children){
app.removeChild(child)
}
const loadingNotice = document.createElement('h1')
loadingNotice.innerHTML = "Fetching data ..."
app.appendChild(loadingNotice) //<-- this never shows up
cardInfo.forEach( info => {
const img = document.createElement('img')
img.src = info.image
app.appendChild(img)
})
app.removeChild(loadingNotice)
}, [cardInfo])
return (
<>
<div className="app">
<h1>Fetching data...</h1>
</div>
</>
)};
What instead happens is the app stays blank until all the images are loaded, then shows all the images at once -- but never the loading notice.
Can I somehow "push" the loading indicator change to the UI independent of the rest of the rendering?
Another thing I tried was
const App = () => {
const [cardInfo, setCardInfo] = useContext(CardInfoContext)
useEffect(() => {
fetchData(setCardInfo)
}, [])
useEffect(() => {
const app = document.querySelector('.app')
if(!cardInfo) return
const loadingNotice = app.querySelector(".loadingNotice")
loadingNotice.style.display = 'block' //<-- this never shows up
cardInfo.forEach( info => {
const img = document.createElement('img')
img.src = info.image
app.appendChild(img)
})
loadingNotice.style.display = 'none'
}, [cardInfo])
return (
<>
<div className="app">
<h1 className="loadingNotice">Fetching data...</h1>
</div>
</>
)}
Which would be incorrect because I do need to remove all the images, at least, but even that only displayed the loading notice for a fraction of a second, then the component goes blank until all the images can be displayed.
useEffect observes when cardInfo is changed, not when the render the comes after fired. You can use useLayoutEffect instead.
...but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
BTW, I wouldn't combine direct DOM manipulation with React to avoid issues like this (among other reasons)
Something like
const App = () => {
const [isLoadgin, setIsLoading] = useState(true)
const [cardInfo, setCardInfo] = useContext(CardInfoContext)
useEffect(() => {
fetchData(result => {
setCardInfo(result);
setIsLoading(false);
})
}, [])
return (
<>
<div className="app">
{isLoading && <h1 className="loadingNotice">Fetching data...</h1>}
{
cardInfo.map(card => <img src={card.image} />)
}
</div>
</>
)}
You need conditional rendering instead of all that DOM manipulation you are trying in the useEffect.
...
return(
<>
<div className="app">
{ !cardInfo ? <h1 className="loadingNotice">Fetching data...</h1> : <Cards info={cardInfo} /> }
</div>
</>
)
Note: I am assuming you have something like <Cards> component that displays cards details.