question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Undefined as a return type for t from useTranslation is not allowed as React Element

See original GitHub issue

Describe the bug When you use the t function returned from useTranslation() in jsx, react/typescript will complain that undefined is not a valid JSX element type.

That is, because TFunction returns TResult and TResult can be undefined

type TFunction = <
    TResult extends string | object | Array<string | object> | undefined = string,
    TKeys extends string | TemplateStringsArray = string,
    TInterpolationMap extends object = StringMap
  >(
    key: TKeys | TKeys[],
    options?: TOptions<TInterpolationMap> | string,
  ) => TResult;

Occurs in react-i18next version 10.5.2

To Reproduce

function MyComp() {
const {t} = useTranslation();

return (<span>{t('')}</span>);
}

Expected behaviour Using t directly in JSX should work according to docs: https://react.i18next.com/latest/usetranslation-hook

OS (please complete the following information):

  • Device: Mac Mini
  • Browser: Chrome
  • Typescript: v 3.3.4000

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:13 (2 by maintainers)

github_iconTop GitHub Comments

4reactions
martinjlowmcommented, Nov 7, 2020

First off, sorry to raise up a question in such an old issue.

However, I’ve stumbled over this issue (in combination with [1]) and I suspect the nullish inference is just for convenience. Right off the bat, I don’t see any obvious place where translate would ever return undefined (looking at [2]) which leads me to think that the type signature is incorrect as it doesn’t reflect the actual implementation.

This could potentially result in undefined behavior and bugs, provided that the consumer expects these strict types. In fact, similarly, I don’t see any way either to distinguish string outputs from object/array outputs (as is tested here: [3]). You would need runtime information to determine if it’s the prior or the latter.

While I’m aware the TFunction-interface would need to change to be completely safe at runtime. I’d argue, the user should have to indicate which particular translations return objects and/or arrays. Something along the lines:

const obj = t('some:objectKey', { isObject: true }); // TFunctionResult = object
const arr = t('some:arrayKey', { isArray: true }); // TFunctionResult = Array<string>
const arrObj = t('some:arrayObjectKey', { isObject: true, isArray: true }); // TFunctionResult = Array<string | object>

As a quick fix to my actual problem I would have to augment TFunction with this overload, such that it will prioritized over the second signature (note the changed TResult):

export interface TFunction {
  <
    TResult extends NonNullable<TFunctionResult> = string,
    TKeys extends TFunctionKeys = string,
    TInterpolationMap extends object = StringMap
  >(
    key: TKeys | TKeys[],
    options?: TOptions<TInterpolationMap> | string,
  ): TResult;
  ...
}

Now, I just hope that no colleague of mine modifies the translations into an object based translation, resulting in [object Object] 😛


edit:

I guess you already support returnObjects which can provide some inference, but it breaks everything if it’s configured globally. At least it’s far more difficult to infer that part as there’s a global client that the user doesn’t control the initialization of and cannot easily pass around. He/she would have to mutate the client using the init interface and expose that mutated client as a cast version which enforces the returnObjects-based interface.

Here’s an inferrable version of TFunction that’s more flexible. Again, this is not correct if returnObjects is configured globally:

import { TOptions, TFunctionKeys, TFunctionResult, StringMap } from 'i18next';

declare module 'i18next' {
  type ReturnObjectsOutput = object | Array<string | object>;
  type ReturnObjectsConfig = { returnObjects: true };

  type NewTFunctionResult = Exclude<TFunctionResult, ReturnObjectsOutput>;

  export interface TFunction {
    // w/ returnObjects: true
    <
      TResult extends ReturnObjectsOutput,
      TKeys extends TFunctionKeys = string,
      TInterpolationMap extends object = StringMap
    >(
      key: TKeys | TKeys[],
      options: (TOptions<TInterpolationMap> & ReturnObjectsConfig) | string,
    ): TResult;

    // Two arguments
    <
      TResult extends NonNullable<NewTFunctionResult> = string,
      TKeys extends TFunctionKeys = string,
      TInterpolationMap extends object = StringMap
    >(
      key: TKeys | TKeys[],
      options?: TOptions<TInterpolationMap> | string,
    ): TResult;
    <
      TResult extends NewTFunctionResult = string,
      TKeys extends TFunctionKeys = string,
      TInterpolationMap extends object = StringMap
    >(
      key: TKeys | TKeys[],
      options?: TOptions<TInterpolationMap> | string,
    ): TResult;

    // Three arguments
    <
      TResult extends NonNullable<NewTFunctionResult> = string,
      TKeys extends TFunctionKeys = string,
      TInterpolationMap extends object = StringMap
    >(
      key: TKeys | TKeys[],
      defaultValue?: string,
      options?: TOptions<TInterpolationMap> | string,
    ): TResult;
    <
      TResult extends NewTFunctionResult = string,
      TKeys extends TFunctionKeys = string,
      TInterpolationMap extends object = StringMap
    >(
      key: TKeys | TKeys[],
      defaultValue?: string,
      options?: TOptions<TInterpolationMap> | string,
    ): TResult;
  }
}
0reactions
enoh-barbucommented, Nov 15, 2021

this still doesn’t work for me and I don’t understand, what’s the fix/solution?

“i18next”: “^21.4.0”, “react-i18next”: “^11.13.0”,

Read more comments on GitHub >

github_iconTop Results From Across the Web

using the t() from react-i18next outside of a component
I am trying to pass the t() as a parameter in a no component and i am receiving the error "TypeError: n is...
Read more >
useTranslation (hook) - react-i18next documentation
It gets the t function and i18n instance inside your functional component. ... The useTranslation hook will trigger a Suspense if not ready...
Read more >
TypeScript - react-i18next documentation
The keys and return type inference will not work, because TemplateStringsArray does not accept generic types yet. You can use Tagged Template Literal...
Read more >
Trans Component - react-i18next documentation
This component enables you to nest any React content to be translated as one cohesive string. It supports both plural and interpolation.
Read more >
withTranslation (HOC) - react-i18next documentation
Not using Suspense you will need to handle the not ready state yourself by eg. render a loading component as long !props.tReady ....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found