Sagar Gor
Sagar Gor

Reputation: 121

React i18next languageChanged event called multiple times

I have used i18next for translation in my react js application. I have added a language dropdown in Header.jsx file which is common for all the pages. I'm fetching data based on the current language in my one of the pages quiz.jsx. So on language change api should be called again.

Problem Explanation

  1. languageChanged() event of i18Next function calls multiple times on language dropdown change. It should be called only one time. I don't know why this function is being called multiple time ?

  2. I want to implement languageChanged() event of i18Next only on one page but currently it is calling on each and every page. So api is fetching data in all the pages which is unnecessary data for that page.

i18Next Configuration

language.js

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import config from './config';
var resources = {};
//Dynamically reading languages from config file
config.supportedLanguages.forEach(element => {
  resources[element] = {
    translations: require('../locale/' + element + '.json')
  };
});
i18n.use(initReactI18next).init({
  fallbackLng: config.defaultLanguage,
  lng: config.defaultLanguage,
  resources,
  ns: ['translations'],
  defaultNS: 'translations',
  debug:true
});

i18n.languages = config.supportedLanguages;

export default i18n;

Header.jsx

import React, { useState, useEffect } from 'react';
import { Dropdown, DropdownButton } from 'react-bootstrap';
import { withTranslation, useTranslation } from 'react-i18next';

import * as api from "../../utils/api";

const TopHeader = ({ t }) => {
    const { i18n } = useTranslation();

    // language selector
    const [languageValue, setLanguageValue] = useState('')
    const [languages, setLanguages] = useState('');

    const languageChange = (data) => {
        setLanguageValue(data.language)
        i18n.changeLanguage(data.code);
        localStorage.setItem('language', JSON.stringify(data));
    }

    const getUserSelectedLanguage = () => {
        var user_selected_lang = localStorage.getItem('language');
        if (user_selected_lang && user_selected_lang !== undefined) {
            user_selected_lang = JSON.parse(user_selected_lang);
        }
        return user_selected_lang;
    }

    //api render
    useEffect(() => {

        api.getLanguages().then((response) => {
            if (!response.error) {
                setLanguages(response.data);
                var user_selected_lang = getUserSelectedLanguage();
                if (user_selected_lang) {
                    selectUserLanguage(user_selected_lang);
                } else {
                    var index = response.data.filter((data) => {
                        return data.code === config.defaultLanguage;
                    })
                    selectUserLanguage(index[0]);
                }
            }
        });

    }, []);

    return (
        <React.Fragment>
            <div className="small__top__header">
                <div className="row justify-content-between align-items-center">
                    <div className="col-md-6 col-12">
                        <div className="dropdown__language">
                            <DropdownButton className="inner-language__dropdown" title={languageValue ? languageValue : "Select Language"}>
                                {languages && languages.map((data, key) => {
                                    return (
                                        <Dropdown.Item onClick={() => languageChange(data)} value={languageValue} id={data.id} active={languageValue === data.language ? "active" : ""} key={data.language}>{data.language}</Dropdown.Item>
                                    )
                                })}
                            </DropdownButton>
                        </div>
                    </div>
                </div>
            </div>
        </React.Fragment>
    )
}
export default withTranslation()(TopHeader);

quiz.jsx

import React, { useState, useEffect } from 'react';
import { withTranslation, useTranslation } from 'react-i18next';
import { Spinner } from 'react-bootstrap';
const Quiz = ({ t }) => {
    const [category, setCategory] = useState({ all: '', selected: '' });
    const [subCategory, setsubCategory] = useState({ all: '', selected: '' });
    const [level, setLevel] = useState([]);
    const { i18n } = useTranslation();

    useEffect(() => {
        getAllData();
    }, []);

    i18n.on('languageChanged', () => {
        getAllData();
    });

    const getAllData = () => {
        // This function will call the Category , Subcategory and Level API to fetch the data
        // And set the local states
    }
    return (
        <React.Fragment>
            <Header />
            <div className="quizplay mb-5">
                <div className="container">
                    <div className="row morphisam mb-5">
                        <div className="col-xxl-3 col-xl-4 col-lg-4 col-md-12 col-12">
                            <div className="left-sec">
                                {/* left category sec*/}
                                <div className="bottom__left">
                                    <div className="cat__Box">
                                        <span className="left-line"></span>
                                        <h3 className="quizplay__title text-uppercase text-white font-weight-bold">{t('Categories')}</h3>
                                        <span className="right-line"></span>
                                    </div>
                                    <div className="bottom__cat__box">
                                        <ul className="inner__Cat__box">
                                            {
                                                category.all ? category.all.map((data, key) => {
                                                    return (
                                                        <li className='d-flex' key={key} onClick={() => handleChangeCategory(data)}>
                                                            <div className={`w-100 button ${category.selected && category.selected.id === data.id ? "active-one" : "unactive-one"}`}>
                                                                <span className="Box__icon">
                                                                    <img src={data.image} alt="" />
                                                                </span>
                                                                <p className="Box__text">{data.category_name}</p>
                                                            </div>
                                                        </li>
                                                    )
                                                })
                                                    :
                                                    <div className='text-center'>
                                                        <Spinner animation="border" role="status"></Spinner>
                                                    </div>
                                            }
                                        </ul>
                                    </div>
                                </div>
                            </div>
                        </div>

                        {/* sub category middle sec */}
                        <div className="col-xxl-9 col-xl-8 col-lg-8 col-md-12 col-12">
                            <div className="right-sec">
                                <SubCatslider data={subCategory.all} selected={subCategory.selected} onClick={handleChangeSubCategory} />
                            </div>

                            <div className="right__bottom cat__Box mt-4">
                                <span className="left-line"></span>
                                <h6 className="quizplay__title text-uppercase text-white font-weight-bold">{t('levels')}</h6>
                                <span className="right-line"></span>
                            </div>

                            {/* levels sec */}
                            <div className="row level-row">
                                <UnlockLevel count={level.count} category={category.selected} subcategory={subCategory.selected} unlockedLevel={level.unlockedLevel} />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </React.Fragment>
    )
}
export default withTranslation()(Quiz);

Upvotes: 3

Views: 5974

Answers (1)

adrai
adrai

Reputation: 3168

I suspect the problem is:

i18n.on('languageChanged', () => {
    getAllData();
});

Each time the component renders it will subscribe again for the languageChanged event...

Try to move it in the useEffect, something like:

const handleLanguageChanged = useCallback(() => {
    getAllData();
}, []);

useEffect(() => {
    i18n.on('languageChanged', handleLanguageChanged);
    return () => {
        i18n.off('languageChanged', handleLanguageChanged);
    };
}, [handleLanguageChanged]);

Upvotes: 6

Related Questions