4

I'm trying to use the useCallback hook, to change the language using gatsby-intl plugin, they have a method (changeLocale()) what can be used to change the default language of the site. I wanted to avoid passing props in JSX arrow's functions despite the solution is working so I'm trying to use the useCallback hook.

onClick={()=>switchLanguage(language.iso)}

Here's my component:

import React, { useCallback, useState } from 'react';
import { changeLocale } from 'gatsby-plugin-intl';
import { useLanguages } from '../../../hooks/useLanguages';

export const LanguageSwitcher = (callback, deps) => {
  const languages = useLanguages();

  const switchLanguage = useCallback(language => changeLocale(language),[]);

  return <ul>
    {languages.map((language, index) => <li key={index} onClick={switchLanguage(language.iso)}>{language.name}</li>)}
  </ul>;

};

The code above creates an infinite rendering, the code is entering on switchLanguage function without clicking it.

However, if I remove the argument, it works as expected but I don't know what language is the user clicking.

  const switchLanguage = useCallback(()=> console.log('item clicked'),[]);

I've also tried to use other hooks such as useState but it creates too many renders.

How can I pass an argument to the useCallback? If it is not possible, which will be the best workaround or approach?

Ferran Buireu
  • 28,630
  • 6
  • 39
  • 67

1 Answers1

4

onClick={switchLanguage(language.iso)} calls switchLanguage and sets its return value as onClick, just like onClick = switchLanguage(language.iso) would outside JSX.

To bind the argument to it, you'd use a wrapper function:

onClick={() => switchLanguage(language.iso)}

...or bind, though it also creates a function:

onClick={switchLanguage.bind(null, language.iso)}

But: There's probably not much to be gained by using useCallback in your example. That being the case, you probably don't need switchLanguage at all:

import React, { useCallback, useState } from 'react';
import { changeLocale } from 'gatsby-plugin-intl';
import { useLanguages } from '../../../hooks/useLanguages';

export const LanguageSwitcher = (callback, deps) => {
  const languages = useLanguages();

  return <ul>
    {languages.map((language, index) => <li key={index} onClick={() => changeLocale(language.iso)}>{language.name}</li>)}
  </ul>;
};
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks for you reply! Yes that was my first approach but I wanted to avoid to bind functions directly on JSX props. I guess my only option is to use something like this: `const switchLanguage = (language) => changeLocale(language);` and call switchLanguage `onClick` event – Ferran Buireu Mar 28 '20 at 13:22
  • 1
    @FerranBuireu - Seems to be fairly standard practice these days. The thing to remember with `useCallback` is you're still recreating the function every time your component renders, it's just sometimes you're not using that new function. One of the fundamental assumptions of React hooks seems to be that creating functions is very, very fast. (I know modern engines reuse the code across instances of the functions, so it should be basically like creating an object.) – T.J. Crowder Mar 28 '20 at 13:26
  • 1
    @FerranBuireu - *"I guess my only option is to use something like this: ` const switchLanguage = (language) => changeLocale(language);` and call switchLanguage onc"* I don't think theres really any reason for `switchLanguage` at that point, is there? Or does it do more than just call `changeLocale`? – T.J. Crowder Mar 28 '20 at 13:27
  • Better to keep it simple and bind directly on JSX rather than create a useless function I guess. Thanks for the masterclass. – Ferran Buireu Mar 28 '20 at 13:28
  • 1
    @FerranBuireu - LOL, as if. :-) Glad to help! Happy coding. – T.J. Crowder Mar 28 '20 at 13:29