Another way of importing and handling image assets without using require()
, no third-party plugins required.
I’m currently trying to master React (Native) so this is probably a horrible hack. I’ll be updating this post as I find better ways of doing this.
One of the issues I’ve found while trying to build the proof of concept for an app is that you need to "pre-populate" it with some dummy data just to test stuff out quickly.
It’s all good and well for strings, numbers, objects, etc. But then if you need to display images it gets a bit tricky.
Assuming you don’t want to use image from an external source, the quickest way to have pre-existing images when testing in a simulator or a physical device is to load them in your project’s assets/
directory (or at least that’s the best solution I’ve found).
The problem
After reading Expo’s documentation it initially seemed like the only way to "import" an image file in your code was by using require(). Now, this being 2020 I thought, surely, there must be a better way.
Full disclosure: When I try to use require() in my project I get a compiler error saying "require is not defined". I’m not sure if I’ve caused it by migrating my project to Typescript.
Apparently it’s a Metro/Webpack config issue but I haven’t been able to figure out how to fix it. If you, reader, happen to know how to fix it please drop me a message, thanks!
Now let’s keep going.
The other way
Turns out there’s another way, maybe writing a one more line of code, but using purely ESModules, the future!
First you need to import your image like:
import exampleImage from './assets/images/example.png'
If you were to console.log(exampleImage)
you’d get a number printed, like 1
, or 8
. I assume this is just an id or mapped value, however you prefer to call it, generated by Metro’s asset loader.
Now, the important bit:
React Native’s official Image Component provides us with a method called resolveAssetSource(). This method takes a "number" (related to what I’ve mentioned above) or an ImageSource
object as its only parameter and returns an object with width
, height
, scale
and uri
properties, this last one being the one we care about in this case. This will look like:
const exampleImageUri = Image.resolveAssetSource(exampleImage).uri
Now, what’s the point of all this you ask. Well, to be honest you could always use require()
and it would work and that’s it right? Well not exactly.
The problem with using require() for image assets
Using require()
has a couple of caveats. First, it’s CommonJS and we’re working on the browser-side of things, where it doesn’t really belong, in the end you’re using just a synthetic require()
being provided by the bundler.
Second, we’d have a mixture of ESModule import
s and CommonJS require()
in our codebase. Something we can easily avoid it, at least in this case.
Third, the second to most important in my opinion. The syntax is different when you want to display your image in an <Image>
component.
require (CommonJS)
import { Image } from 'react-native';
const exampleImage = require('./assets/images/example.png')
<Image source={exampleImage} />
import (ESModule)
import { Image } from 'react-native';
import exampleImage from './assets/images/example.png'
const exampleImageUri = Image.resolveAssetSource(exampleImage).uri
<Image source={{uri: exampleImageUri}} />
Bonus (Dynamic imports)
I know it’s also possible for require()
, but you could even use dynamic imports, like:
import { Image } from 'react-native';
const {default: exampleImage } = await import('./assets/images/example.png')
const exampleImageUri = Image.resolveAssetSource(exampleImage).uri
And lastly, by using import you’ll provide the source attribute to the <Image/>
component in the same format you’ll need to provide it when you eventually finish your POC and start working with real data, fetching images from external URLs like CDNs and the like, that source format is and object with a uri
property, like:
{uri: exampleImageUri}
Conclusion
In conclusion, this way you have a consistent way of handling image assets. All the code and components you wrote to handle and display your images when getting started building your app will still be useful once you switch from dummy to real data from external sources. No need to refactor components and update source props and the like.
Anyways, I hope this is useful to you, I’m still trying to master React (Native), this post is one part note-taking for myself and one part writing it down so somebody having the same issues can find it. If something I’ve stated in this post is wrong feel free to leave a note and I’ll try to get it corrected.
A couple of things to know about the URI structure
This uri property is comprised of a normal looking URL, in the form of :
http://127.0.0.1:19001/assets/assets/images/examples.png?platform=<android|ios...>&hash=<asset_hash>?platform=<android|ios...>&dev=true&minify=false&hot=false
A couple of things worth noting:
There are two assets/ directories in the path, I’m assuming the top one relates to an internal directory for the bundler and the second one being part of the string path we’ve specified in our import statement example.
For some reason it also has 2 sets of query strings. I’m assuming the second set being for the dev server and the first one some other internal Expo-related service.
Now I’m not sure how this URI translates in production but, according to the Assets Guide in Expo’s official documentation, Expo uploads the project’s assets to Amazon CloudFront and I’m assuming creates some kind of map/replaces all assets references with the CDN URLs internally.
References
- Assets — Expo Documentation
- Asset SDK — Expo Documentation
- Image — Core Components — React Native
- import — Javascript | MDN
Originally posted in:
Top comments (2)
Hi,
Nice post, however there still is a problem with the
** Image.resolveAssetSource(item.avatar).uri ** when there are multiple images which needs to be loaded from assets folder.
Real world scenario:
I have an application, which uses the Flatlist (maximum of 20-30 items) for the order,
these line items can have any one of the five possible flags (read statuses),
originally my idea was to load from assets, since this 5 images would be easier to manage locally. however with this issues with react lead us to host those 5 images on the server and cache them for forever at least for now.
because even the dynamic loading fails at a time and the status does not load sometimes.
Hi,
Nice post, however there still is a problem with the
when there are multiple images which needs to be loaded from assets folder.
Real world scenario:
I have an application, which uses the Flatlist (maximum of 20-30 items) for the order,
these line items can have any one of the five possible flags (read statuses),
originally my idea was to load from assets, since this 5 images would be easier to manage locally. however with this issues with react lead us to host those 5 images on the server and cache them for forever at least for now.
because even the dynamic loading fails at a time and the status does not load sometimes.