How To Cache Images in React? - javascript

Suppose I have a list of url's like so :
[ '/images/1', '/images/2', ... ]
And I want to prefetch n of those so that transitioning between images is faster. What I am doing now in componentWillMount is the following:
componentWillMount() {
const { props } = this;
const { prefetchLimit = 1, document = dummyDocument, imgNodes } = props;
const { images } = document;
const toPrefecth = take(prefetchLimit, images);
const merged = zip(toPrefecth, imgNodes);
merged.forEach(([url, node]) => {
node.src = url;
});
}
with imgNodes being defined like so:
imgNodes: times(_ => new window.Image(), props.prefetchLimit),
and times, zip, and take coming from ramda.
Now when I use those urls inside of react like so:
<img src={url} />
it hits the browser cache according to the Etag and Expire tags regardless of where the url is used. I also plan on using this to prefetch the next n images whenever we hit n - 1 inside of the view, reusing imgNodes in the same manner.
My question are:
Is this even a valid idea give 100+ components that will use this idea but only 1 will be visible at a time?
Will I run into memory issues by doing this? I am assuming that imgNodes will be garbage collected when the component is unmounted.
We are using redux so I could save these images in the store but that seems like I am handling the caching instead of leveraging the browser's natural cache.
How bad of an idea is this?

You don't need to do it in all of your components. As soon as an image is downloaded it gets cached by the browser and will be accessible in all components, so you can do this only once somewhere in a high-level component.
I don't know what exactly UX you are trying to create by caching images, however, your code only initiates downloading images but doesn't know whether an image is being downloaded, has been downloaded successfully or even failed. So, for example, you want to show a button to change images or add a class to a component only when the images have been downloaded (to make it smooth), your current code may let you down.
You may want to resolve this with Promises.
// create an utility function somewhere
const checkImage = path =>
new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => resolve(path)
img.onerror = () => reject()
img.src = path
})
...
// then in your component
class YourComponent extends Component {
this.state = { imagesLoaded: false }
componentDidMount = () =>
Promise.all(
R.take(limit, imgUrls).map(checkImage)
).then(() => this.setState(() => ({ imagesLoaded: true })),
() => console.error('could not load images'))
render = () =>
this.state.imagesLoaded
? <BeautifulComponent />
: <Skeleton />
}
Regarding memory consumption — I don't think anything bad will happen. Browsers normally limit the number of parallel xhr requests, so you won't be able to create a gigantic heap usage spike to crash anything, as unused images will garbage collected (yet, preserved in browser cache).
Redux store is a place to store the app state, not the app assets, but anyway you won't be able to store any actual images there.

This is simple and works fine:
//arraySrcs=["myImage1.png","myImage2.jpg", "myImage3.jpg", etc]
{arraySrcs.map((e) => (
<img src={e} style={{ display: "none" }} />
))}

Related

React Telemetry - Create span without passing parent span / Create nested span from different files

I am currently trying to implement custom tracing using telemetry. I am trying to trace the load time of the page load and until a certain network request is finished. What I want to know is how can i create a span from my jsx file and it will have some child span on the saga file.
Let's say in my index.jsx I have this
useEffect(() => {
const tracer = trace.getTracer('foo')
const parentSpan = tracer.startSpan('parentSpan')
context.with(trace.setSpan(context.active(), parentSpan), () => {
// dont know what to do here
parentSpan.end()
});
and on my saga file
const tracer = trace.getTracer('foo');
const childSpan = tracer.startSpan('childSpan', undefined, context.active());
context.with(trace.setSpan(context.active(), childSpan), () => {
// do network request here
childSpan.end();
});
However this only creates two separate spans.
My references can be found below
https://github.com/open-telemetry/opentelemetry-js/issues/1963
https://opentelemetry.io/docs/instrumentation/js/instrumentation/#using-sdk-trace-base-and-manually-propagating-span-context

Memory leak in React when using HTML5 Canvas

I have a project in my React app that draws to the canvas.
I'm invoking it in my component like:
function CanvasProject() {
const canvasRef = useRef(null);
const cleanup = useRef(null);
useEffect(() => {
var c = canvasRef.current;
const cleanup.current = render(c);
}, [])
return(
<canvas style={{width: "100%"}} ref={canvasRef}>
Your browser does not support the HTML5 canvas tag. Stop using Netscape.
</canvas>
)
}
The render function gets the "2d" context from the canvas a few times, gets image data with ctx.createImageData, and sets image data with ctx.putImageData.
render() also adds an onclick handler to the canvas, but I've tried commenting out that code and the memory leak is still present.
As for the memory leak itself, when I go back and forth between routes on my App, it starts to slow down the page. I'm going back and forth between routes that respectively do and do not have my component.
I've tried examining the memory usage with Firefox dev tools, and here's the memory profile when I first start the app:
Followed by the memory usage after I go back and forth between a few routes:
Obviously without seeing the full code I'm not expecting a "your bug is here" answer, but are there any things I should consider about the way canvas memory management works? Are there any other dev tools I could use to find out the source of the large chunks of Array and ArrayBuffer data that is being generated? Basically looking for any leads and a sanity check, to make sure I'm not missing something obvious.
Can't be positive as I do not have a lot of experience with canvas, however, if the render method creates listeners then they will likely persist after the component has unmounted.
Within the useEffect hook you could try adding a function to cleanup when the component dismounts. The most likely option I could think of is change the cleanup.current to null.
useEffect(() => {
var c = canvasRef.current;
const cleanup.current = render(c);
return () => {
cleanup.current = null;
}
}, [])

I try to display api data but nothing comes out, not even an error on React

I'm slowly understanding the services and fetch with React but when I try to show something, it shows me absolutely nothing. I put the code in case someone can help me. Maybe I have to look at how to work with JSON, I don't know.
let datosApi = [];
const recogerDatos = () => {
let json = "https://jsonplaceholder.typicode.com/albums";
let miApi = "http://localhost/dsw/api/";
fetch(json)
.then(data => data.json())
.then(info => {
console.log(info);
this.datosApi = info;
})
}
function Services() {
return (
<>
{datosApi.map(datos => (
<p>{datos.title}</p>
))}
</>
);
}
export default Services;
JSON data appears in https://jsonplaceholder.typicode.com/albums
I think your example is missing something or you've not done it.
Basically there's a few things wrong:
recogerDatos is never being called
datosApi is not declared, and even if it was, it's not stateful, thus won't cause a re-render of your items.
I've created a working sandbox here that shows it working, and the code is below:
const [result, setResult] = useState([]);
const recogerDatos = () => {
let json = "https://jsonplaceholder.typicode.com/albums";
fetch(json)
.then((data) => data.json())
.then((info) => {
console.log(info);
setResult(info);
});
};
useEffect(() => {
recogerDatos();
}, []);
return (
<div className="App">
{result.length > 0 && result.map((datos) => <p>{datos.title}</p>)}
</div>
);
The recogerDatos function is called on page load (see useEffect), and the result is updated when the fetch is successful. This causes a re-render and the items are shown.
You are displaying data from your list
let datosApi = [];
However it does not seem like you are populating your list with data from the API since the method recogerDatos() is never being called.
From your code it seems like you're missing some core React patterns like state management and the components lifecycle. Since React re-renders components a lot you want to store things like fetched data into state to avoid them constantly reset to their initial value. And when you want to fetch the data you usually don't want to do it at each re-render (that would cause A LOT of fetching), instead you usually want to trigger it based on different events, for example when component (that will be used to show this data) is mounted. Such things are usually using the components lifecycle methods / useEffect hooks to ensure that they happen at the right point in time. I recommend you to go into React documentation and study the basics a bit more so you can understand the main concepts when you're coding, but here's a quick sandbox with an example of how you could get the desired effect in React:
https://codesandbox.io/s/beautiful-frost-on9wmn?file=/src/App.js

I want to access my state variable from one component to other

I have a react query which writes the state variable- follower, and I want to access this variable in other component to find its .length can someone tell me how do I do it
const ModalFollower = ({profile}) => {
const [follower,setFollower] = useState([])
const {
data: followerName,
isLoading: followerLoading,
isFetching: followerFetching
} = useQuery(["invitations", profile?.id], () => {
getFollowers(profile?.id).then((response) => {
if (response) {
setFollower(response);
}
});
});
return(
{
!followerLoading && (
follower.map((e) => {
return(<>
<p>{e.requested_profile.Userlink}</p>
</>}
)
}
)
I want to access the length of follower in some other component
There is no need to copy data from react-query to local state, because react-query is a full-blown state manager for server state. As long as you use the same query key, you will get data from its cache. This is best abstracted away in custom hooks.
Please be aware that with the default values, you will get a "background refetch" if a new component mount, so you will see two network requests if you use it twice. That might look confusing at first, but it is intended, as it is not react-query's primary goal to reduce network requests, but to keep your data on the screen as up-to-date as possible. So when a new component mounts that uses a query, you'll get the stale data from the cache immediately, and then a background refetch will be done. This procedure is called stale-while-revalidate.
The best way to customize this behaviour is to set the staleTime property to tell react-query how long your resource is "valid". For that time, you will only get data from the cache if available. I've written about this topic in my blog here: React Query as a State Manager.
React Query also provides selectors, so if your second component is only interested in the length, this is what my code would look like:
const useInvitations = (profile, select) =>
useQuery(
["invitations", profile?.id],
() => getFollowers(profile?.id),
{
enabled: !!profile?.id
select
}
)
Note that I also added the enabled property because apparently, profile can be undefined and you likely wouldn't want to start fetching without that id.
Now we can call this in our main component:
const ModalFollower = ({profile}) => {
const { data } = useInvitations(profile)
}
and data will contain the result once the promise resolves.
In another component where we only want the length, we can do:
const { data } = useInvitations(profile, invitations => invitations.length)
and data will be of type number and you will only be subscribed to length changes. This works similar to redux selectors.

Image loading taking too long

I have started the development of an application with React, Redux and Firebase. But I have a problem when I am using Firebase Storage.
When I use my code:
getImage (image) {
let { state } = this
st.doGetArticlesImg(image).then((url) => {
state[image] = url
this.setState(state)
}).catch((error) => {
console.log(error);
})
}
My images load correctly but the loading is very long, they appear only 3 or 4 seconds after loading the page. And once in two they do not charge.
Surely a life cycle story of a component?
You get the state:
let { state } = this
And then modify it:
state[image] = url
Don't forget - you can't mutate the state. It seems like, after mutating the state react doesn't see the difference and doesn't call render(). I'd re-wrote this like:
this.setState({image: url})

Categories

Resources