Images are crucial elements that will bring a strong visual power to a web page. Too many images, however, will deteriorate the performance of a web page due to too many requests to the server.
One of the optimisation technique we can use is CSS Sprite, which basically combining multiple images into one image. Then, you could use CSS background-position to shift around and only display part of it.
Pros of CSS Sprite
Reduce the requests to server and allow more parallelism request for other resources.
Reduce overhead of handshake and header for each HTTP requests.
Cons of CSS Sprite
Too large image files may slow down First Meaningful Paint metric and negatively impact other metrics as well. Always benchmark and measure!
May have some overhead to regenerate the image sprites every time you add a new image.
Creating CSS Sprite Icon
Today, we are going to try to create an Icon component using CSS sprite in React and see how it works. You can clone the starter project and follow the exercise.
For this post, we are not going to look into how to generate the sprite, this process will have integrate into your development flow such as your CI/CD flow. For this tutorial, we already have some generated image sprites that is ready to use in the public/images which contains some icons of social network:
icon.webp
icon@2x.webp
icon@3x.webp.
These icons are generate from image get from flaticon.com.
Let’s get started by adding some classes in App.css:
// Other CSS
+.icon {
+ background: url(/images/icon.webp) no-repeat top left;
+ width: 32px;
+ height: 32px;
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 2) {
+ .icon {
+ background: url(/images/icon@2x.webp) no-repeat top left;
+ background-size: 32px 128px;
+ }
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 3) {
+ .icon {
+ background: url(/images/icon@3x.webp) no-repeat top left;
+ background-size: 32px 128px;
+ }
+}
We added icon class and set the background to the image with width and height of 32px. Also, to make it display nicely in Retina display devices, we also added 2x and 3x images using -webkit-min-device-pixel-ratio media queries.
Next, we will create an Icon component to display the image. Let’s create a src/Icon.tsx:
By using iconIndex and iconSize, we can easily calculate the position of each image that we want to display. We will pass the iconName as the props to the Icon component and reuse the same component anywhere we want to.
In order to scale the icon, we have added a size props to allow us to change the size of the icon by calculating the scale ratio using the desire size against the actual icon size.
Lastly, let’s add some code to test our Icon component in App.tsx:
Great! We have successfully added 4 Icons in the page with different size and iconName and let’s run yarn start to see the result.
Now we should be able to see the 4 icons is render properly with different sizes.
Conclusion
CSS Sprite is one of the way to optimise your image assets as well as web loading speed.
There are many other image optimisation techniques exist in the industry such as lazy loading the image, using embedded SVG and etc to further allow you to put more images in your webpage without compromise the performance.
Here is the complete code for this tutorial if you interested.
Great news people! Create React App v5 is just released with a bunch of upgrades such as support for Webpack 5, PostCSS 8, and one of my favourites is that it comes with TailwindCSS v3.
You are no longer in need of craco or react-app-rewired to get TailwindCSS work with CRA. All you need is just a few simple steps and let me show you.
Localisation is very important for every web application to bring a better user experience to all the users from around the globe and not lose your potential customers.
Today, we will look into how to integrate localisation into your React web app including how to handle language changes, formatting, as well as how to load your localisation file from CDN and etc.
First, let’s start with a simple React app with language selections and a simple text display. You can clone or download the starter project to start with, or if you are impatient, you can get the completed code here.
Setting up react-i18next
Once you have the starter project, install the react-i18next dependency:
yarn add i18next react-i18next
Next, we need to setup react-i18next in our app:
1. Create an i18n.config.js inside the src folder. (Actually, you can name anything you want).
2. Add the following codes into the file.
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './resources/en.json';
import zh from './resources/zh.json';
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources: {
en: {
translation: en, // Add translation for English
},
zh: {
translation: zh, // Add translation for Chinese
},
},
fallbackLng: "en",
interpolation: {
escapeValue: false // No need to escape for react
}
});
3. Import the config in App.js to initialize
// Other import
import i18next from 'i18next';
import './i18n.config';
// Rest of the code
4. Create 2 new files in src/resources/en.json and src/resources/zh.json and insert the content as below.
// src/resources/en.json
{
"welcome": "Welcome to the world of wonder"
}
// src/resources/zh.json
{
"welcome": "欢迎来到奇幻的世界"
}
Translate using useTranslation hook
To start translating, you can use useTranslation hook provided by react-i18next. Now let’s add our first translated text inside section tag in App.js:
Then add the new translation text in src/resources/en.json and src/resources/zh.json .
// src/resources/en.json
{
"selectedLanguage": "Your selected language is: {{language}}"
}
// src/resources/zh.json
{
"selectedLanguage": "您选择的语言是: {{language}}"
}
The differences between the welcome text and the selectedLanguage text is that we pass the language as the second parameter for t method to replace the placeholder in the translation text.
Another powerful feature that i18n provides is you can format the interpolation value. You can either use built-in formatting functions based on Intl API or build your own format function.
To have a better understanding of formatting, we will build a simple uppercase format function. Now, add a simple format method in src/i18n.config.js :
// Other codes
i18n
.use(initReactI18next)
.init({
// Other codes
interpolation: {
escapeValue: false,
++ format: (value, format, lng) => {
++ if (format === 'uppercase') {
++ return value.toUpperCase();
++ }
++
++ return value;
}
}
});
The function is really self-explanatory: if the format is uppercase, it will convert the value to uppercase, otherwise, it just returns the value.
Next, we can apply the uppercase format in our selectedLanguage text, let’s update the text in src/resources/en.json and src/resources/zh.json :
// src/resources/en.json
{
--"selectedLanguage": "Your selected language is: {{language}}"
++"selectedLanguage": "Your selected language is: {{language, uppercase}}"
}
// src/resources/zh.json
{
--"selectedLanguage": "您选择的语言是: {{language, uppercase}}",
++"selectedLanguage": "Your selected language is: {{language, uppercase}}"
}
If you refresh the page now, you could see the language text has been converted to uppercase.
Another common case that we definitely need when doing the translation is pluralisation, and of course, i18next got it for you too. We will simply count how many times have we changed the language to demonstrate the pluralisation in i18next.
First, let’s add the necessary text in src/resources/en.json and src/resources/zh.json.
// src/resources/en.json
{
++"numOfTimesSwitchingLanguage": "Your have switch language for {{count}} time",
++"numOfTimesSwitchingLanguage_zero": "Your have switch language for {{count}} time",
++"numOfTimesSwitchingLanguage_other": "Your have switch language for {{count}} times"
}
// src/resources/zh.json
{
++"numOfTimesSwitchingLanguage": "您已更换了语言{{count}}次",
++"numOfTimesSwitchingLanguage_zero": "您已更换了语言{{count}}次",
++"numOfTimesSwitchingLanguage_plural": "您已更换了语言{{count}}次"
}
Next, let’s add the logic for counting the number of times we change the language in App.js .
Great! we can see it changes to plurals once the count is more than 1. One important note here is that we must use count in order for pluralisation to work.
You can do more complex pluralisation with i18next. Please feel free to check out the i18next plural document if you like to.
Persisting and Detecting Language
We have explored a couple of functionality of i18next, but there is one imperfection of our language changes app because it does not persist in the language selection after you refresh the page. Luckily, there is a i18next-browser-languagedetector plugin that going to help us achieve this. This plugin also helps us to detect languages from cookies, URL, browser settings.
Now, let’s install the plugin.
yarn install i18next-browser-languagedetector
Then, add the plugin in src/i18n.config.js:
// Other import
++import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(initReactI18next)
++.use(LanguageDetector)
.init({
// Other configs
});
Previously, we have set en as the default language, but now we want to use i18next.language as the default language. Thus, let’s make some changes in App.js.
Alright, we can try to switch language to zh and refresh the page now. We should be able to see the default language stays at zh.
Loading Translation Texts From Server
If you’re working with translators or to get better performance with CDN, you may want to load the translation texts from CDN instead of embedding everything in your code.
To do this, i18next-http-backend plugin is here to help us load the translation texts from the server easily.
First of all, let’s install the plugin.
yarn install i18next-http-backend
Then, we need to change some of the configurations in i18n.config.js.
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
--import en from './resources/en.json';
--import zh from './resources/zh.json';
import LanguageDetector from 'i18next-browser-languagedetector';
++import HttpApi from 'i18next-http-backend';
i18n
.use(initReactI18next)
.use(LanguageDetector)
++.use(HttpApi)
.init({
-- resources: {
-- en: {
-- translation: en,
-- },
-- zh: {
-- translation: zh
-- },
-- },
fallbackLng: 'en',
interpolation: {
escapeValue: false,
/**
* Add interpolation format method to customize the formatting
*/
format: (value, format, lng) => {
if (format === 'uppercase') {
return value.toUpperCase();
}
return value;
},
},
++ backend: {
++ loadPath: '/resources/{{lng}}.json',
++ },
});
Basically, we have made 2 changes: first, we tell the i18n to use the plugin and set the loadPath to /resources/{{lng}}.json where lng will be replaced by the currently selected language code. Secondly, we also remove resources key because it is no longer being used as we are loading the translation texts remotely.
Next, we need to move the src/resources folder to public/resources folder so that the files can be loaded publicly.
Lastly, we need to wrap our application with Suspense in index.js.
In practice, we may want to add Suspense around a specific component instead of at the root level. However, for demo purposes, we can just wrap it in the root level.
If you refresh the app, it should work as normal.
Bonus – Namespaces
Speaking of performance, there is another thing that you can do to speed up the loading time of your long translation texts if your application grows large, and that is namespacing. Namespacing allows you to load the small chunk/group of translation texts at a time when you need without preloading everything upfront which is a waste as well as slow down the first load time.
You just need to change your i18next-http-backend loaded path to use the namespaces and add the namespace as the argument to useTranslation .
i18next is a robust and sustainable solution to localise your web application. It has many features and plugins to achieve what you need for localisation.
In this post, we are just exploring the tip of the iceberg, there is still much more to learn. I will suggest having a read on react-i18next and i18next documentation to learn how to use them in depth.
I hope you learned something today and please feel free to give any feedback/comment below!
With the rise of React Hooks, many developers have added hooks into their open source libraries. If you are using react-redux libraries, they also provided hooks such as useSelector and useDispatch as well.
Although hooks allow us to use redux with ease in React, however, there is not much documentation about unit testing with hooks. Thus, I’m going to show you how to unit test your react-redux hooks.
In class component, we can create a mock of the redux actions and states and pass them into the component for unit testing. As a matter of fact, there is not much difference in unit testing functional components with hooks. Instead of passing the mock state into the components, we can mock the implementation of `useSelector` to return state.
We know useSelector is a function that takes a callback as the parameter. All we need to do is to mock the state and pass to the callback. The unit test would look like this:
If you check the snapshot, you will probably see something like this:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HelloWorld renders user with skills 1`] = `
<div>
<div>
<p>
First Name:
Tek Min
</p>
<p>
My Skills:
</p>
<ul>
<p>
Javascript
</p>
<p>
React
</p>
</ul>
</div>
</div>
`;
exports[`HelloWorld renders user without skills 1`] = `
<div>
<div>
<p>
First Name:
Tek Min
</p>
<p>
My Skills:
</p>
<ul />
</div>
</div>
`;
Great! we get the snapshot as we wanted, the components render correctly with the different redux states.
You may find another way of testing with redux hooks in Google, but I find that this is one of the simplest versions of doing it without additional libraries. Hope you find it helpful and have a nice day!