Skip to content

Conversation

melanore
Copy link

@melanore melanore commented May 15, 2025

ReactComponent memo functionality was extended to:

  • Allow default shallow object comparison (call to memo without equality function)
  • Allow equalsButFunctions function that was initially introduced in Fable.Elmish - it uses the equality from fable js library, but ignores the function fields.
  • Allow custom js function passed to the attribute, - it is highlighted as `[<StringSyntax("javascript")>] and allows either inlined function declaration or usage of function imported/declared within file.

Additionally, I've added the aliases to the ReactComponentAttribute* family, so that consumers could use this syntax:

[<ReactComponent>]
[<ReactComponent.Memo>]
[<ReactComponent.Memo.EqualsButFunctions>]
[<ReactComponent.Memo "(x, y) => {
    const value = custom(x, y);
    return value || x === y;
}">]

Below you can see the examples usages:

Simple component

[<ReactComponent>]
let Anonymous(props: {|
    value: string
|}) =
    Html.span [ prop.text props.value ]
export function Anonymous(props) {
    return createElement("span", {
        children: props.value,
    });
}

Memo component

[<ReactComponent.Memo>]
let Memo(props: Example) =
    React.fragment [
        Html.span [ prop.text props.value ]
        Html.button [ prop.text "+"; prop.onClick (fun _ -> props.setValue (props.value + 1)) ]
    ]
export function Anonymous(props) {
    return createElement("span", {
        children: props.value,
    });
}

Memo.EqualsButFunctions component

[<ReactComponent.Memo.EqualsButFunctions>]
let MemoEqualsButFunctions(props: Example) =
    React.fragment [
        Html.span [ prop.text props.value ]
        Html.button [ prop.text "+"; prop.onClick (fun _ -> props.setValue (props.value + 1)) ]
    ]

Notes

  • MemoEqualsButFunctions requires equals from npm installed @fable-org/fable-library-js within the project.

  • The function MemoEqualsButFunctions(props) { ((props) => {...})(props); } - named function is required for memo to know the name of the component (otherwise it will be Anonymous). As Expr.Delegate is translated by Fable to the lambda - we emit the named function with self executing lambda.

export const MemoEqualsButFunctions = memo(
  function MemoEqualsButFunctions(props) {
    return ((props) => {
      const xs_2 = [
        createElement("span", {
          children: props.value,
        }),
        createElement("button", {
          children: "+",
          onClick: (_arg) => {
            props.setValue(props.value + 1);
          },
        }),
      ];
      return react.createElement(react.Fragment, {}, ...xs_2);
    })(props);
  },
  function equalsButFunctions(x, y) {
    if (x === y) {
      return true;
    } else if (typeof x === "object" && !x[Symbol.iterator] && !(y == null)) {
      const keys = Object.keys(x);
      const length = keys.length | 0;
      let i = 0;
      let result = true;
      while (i < length && result) {
        const key = keys[i];
        i = (i + 1) | 0;
        const xValue = x[key];
        result = typeof xValue === "function" ? true : equals(xValue, y[key]);
      }
      return result;
    } else {
      return equals(x, y);
    }
  }
);

Memo custom equality component

let [<Literal>] CUSTOM_EXAMPLE_EQUALITY = "myCustomEquals"
let [<CompiledName(CUSTOM_EXAMPLE_EQUALITY)>] private exampleEq (x: Example, y: Example) =
    x.value = y.value
[<ReactComponent.Memo(CUSTOM_EXAMPLE_EQUALITY)>]
let MemoEquals(props: Example) =
    React.fragment [
        Html.span [ prop.text props.value ]
        Html.button [ prop.text "+"; prop.onClick (fun _ -> props.setValue (props.value + 1)) ]
    ]
function myCustomEquals(x, y) {
  return x.value === y.value;
}

export const MemoEquals = memo(function MemoEquals(props) {
  return ((props) => {
    const xs_2 = [
      createElement("span", {
        children: props.value,
      }),
      createElement("button", {
        children: "+",
        onClick: (_arg) => {
          props.setValue(props.value + 1);
        },
      }),
    ];
    return react.createElement(react.Fragment, {}, ...xs_2);
  })(props);
}, myCustomEquals);

Memo imported custom equality component

Image

import { custom } from "@my/lib";

export const MemoEqualsImported = memo(
  function MemoEqualsImported(props) {
    return ((props) => {
      const xs_1 = [
        props.value,
        createElement("button", {
          children: "+",
          onClick: (_arg) => {
            props.setValue(props.value + 1);
          },
        }),
      ];
      return react.createElement(react.Fragment, {}, ...xs_1);
    })(props);
  },
  (x, y) => {
    const value = custom(x, y);
    return value || x === y;
  }
);

@melanore melanore mentioned this pull request May 15, 2025
@Freymaurer
Copy link
Contributor

Hey thanks for the work! I am currently working on a test setup to to make Feliz more robust. It is mostly done and can be seen in #664 . As we are encountering some issues with Fable JSX this takes longer than anticipated. As soon as we have [<ReactComponent>] compatible with Fable JSX output, i will notify you, It would be awesome if you could rebase your PR against that branch then. ❤️

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

Successfully merging this pull request may close these issues.

2 participants