Last week I was working on a task of adding multiple language support to the React Native app that is under developing. I used the package i18next as the backhone to support the feature. React Redux is also used to store the language choice as one of the app settings. During the development, I encountered an unexpected bug: The home screen does not re-render once I changed the language setting. It took me around 2.5 hours to resolve it, hence, I consider it worth a blog to write the think process and the solution down.
Context of the Issue
First, let us go through the context of the issue.
Store the Language Settings in React Redux
In the i18next, it has a global variable i18n.language
to store language choice and is applied to the whole app. However, it is not enough, if we do not store the language setting, once the app is closed, the user choice will lost. Therefore, I used React Redux to store the language setting, as shown below:
1 | ... |
The Expected Scenario
The project is structured as blow. The language
in redux is set under set-language/index.jsx, along with i18n.language
. And set-language is routed from my-account. What I expected is that if I change the language
, once I switch back to the home screen, it should re-render and present with the changed language.
1 | -- root |
The Re-render Issue
Things did no go as I expected. Once I change the language setting and switch back to the home screen, it changes nothing, the scren still show the preivous language.
RCA (Root Cause Analysis)
I added a useEffct
hook into (tabs)/home/index.jsx, and would like to see why the home screen does not re-render. The code is shown below:
1 | useEffect(() => { |
Then I changed the language
and i18n.language
from the set-language again, I found out that as soon as I changed the variables, the logs were shown on the terminal (means that the useEffect
hook in the home screen was triggered). When I switched back to the home screen, the terminal did not print out the logs again (means that useEffect
hook was not trigered).
Hence, I reached to the conclusion that under the tabs navigation of Expo Router, the tabs are somehow binded together, i.e. change states in one tab, will cause the useEffect
hooks in the other tabs triggered. In this case, the other tabs consider themselves are re-rendered already. For example, in my case, I changed the language
state under my-account/set-language, the useEffect
hook in the home screen is triggerd, since I am still in the set-language screen, the components in the home screen will not be re-endered. When I switch back to the home screen, as there is no state change, the useEffect
hook will not be triggered, and the language in the home screen stay with the old one.
The Solution
After identifying the root cause, the thought for the solution is obvious: find a method to ‘force’ the (tabs)/home screen to be re-rendered when change the language state in the (tabs)/my-account/set-language screen.
Enlightened by the information provided from Device context doesn’t work with Expo Router, I use the i18n.language
as the key value for the Stack
component under app/_layout.js (which is the parent componet for (tabs) ). Therefore, everytime the i18n.language
is changed, the key value of the Stack
component of app/_layout.js will be changed, thus cause all tabs under the (tabs) directory be re-rendered. The source code is shown below:
1 | ... |
Compile and run the app again. Bingo! The feature works as expcted :)