Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Higher Order Components #304

Closed
alfonsogarciacaro opened this issue Dec 30, 2020 · 2 comments
Closed

Higher Order Components #304

alfonsogarciacaro opened this issue Dec 30, 2020 · 2 comments

Comments

@alfonsogarciacaro
Copy link
Contributor

Is there any solution/recommendation for working with React HOC in Feliz? I wonder if we could make them work with the ReactComponent attribute. Maybe something like:

[<ReactComponent(import="withFoo", from="react-foo", hoc=true)>]
let Component(name: string, flag: bool) = Html.p "do something here"

However, there's a recent issue in Fable.React repo to use "react-i18next" that presents a couple of challenges:

  1. The HOC must be called in curried form, like withTranslation()(MyComponent)
  2. The HOC adds props

Not sure what would be the best way to solve these. For 1. maybe we can have an hocTransform argument as an Emit template. And for 2. maybe optional arguments (although in that case we need a class member if we want to omit them on the call site):

type MyLibrary =
  [<ReactComponent(import="withTranslation", from="react-i18next", hocTransform="$0()($1))>]
  static member Component(name: string, ?t: (string -> ReactElement)) =
    Html.p(t.Value "my translated text")
@Zaid-Ajaj
Copy link
Owner

Hello @alfonsogarciacaro, it has been a while 😄

I have been thinking about this problem for a while. I think there are more challenges that the ones you pointed out. Even using something like the following

type MyLibrary =
  [<ReactComponent(import="withTranslation", from="react-i18next", hocTransform="$0()($1))>]
  static member Component(name: string, ?t: (string -> ReactElement)) =
    Html.p(t.Value "my translated text")

// call-site
MyLibrary.Component(name="Page title")

Won't really work because the compiler plugin needs to output two definitions (AST declarations):

  • One for component itself
  • Another for the transformed component (used by the call-site)

So that the generated code looks like this:

import { withTranslation } from 'react-i18next'
import { createElement } from 'react'

// the component definition itself
export const Component  = (inputComponentProps) => {
  const name = inputComponentProps.name
  const t = inputComponentProps.t
  return createElement("p", null, t("my translated text"))
};

// transformed component
export const ComponentWithTranslation = withTranslation()(Component);

// call site uses the transformed component which is defined *once*
createElement(ComponentWithTranslation, { name: "Page title' })

The current plugin implementation AFAIK cannot output two declarations during replacement.

In this specific case, however, I think using the HOC is not necessary because the the hook useTranslation is a far better and easier approach and it can be integrated nicely with Feliz.

I understand the issue is not about this specific library binding buts HOCs in general, so to summarize the recommendation:

  • Use hooks whenever possible in Feliz instead of HOCs because binding them is easy
  • To output a HOC properly, we need a plugin that can emit multiple declarations, not just one: i.e. override something like TransformMultiple alongside the Transform method which returns a list of declarations instead of just one.

On a side-note: I believe that doing translations using these libraries can be a real PITA when the only thing than the translate function can work with is a stringy key without parameters. I think for Feliz, all you need is a small subset of the of the library that propages just which language is being used and then you will be able to translate parts using functions:

module Translate

let welcome locale name= 
  match locale with
  | Locales.Dutch -> $"Welkom, %s{name}"
  | _ -> $"Welcome, %s{name}"

Then from your components

let locale = useCurrentLocale()
Html.h1 [ Translate.welcome locale "Alfonso" ]

That is a solution for another day 😉

@alfonsogarciacaro
Copy link
Contributor Author

It's been a while indeed! I hope everything is well 😊

Thanks a lot for your comments! You're definitely right, dealing with HOC is a pain. I believe that's actually one of the reasons the React team created the hooks. Ok, let's not dedicate too much energy on this and just hope React libraries replace HOC with hooks (as they should). If somebody still needs to use an HoC we can deal with that in a case by case basis as we've doing so far 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants