Skip to content

Fix key prop type #74

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

Merged
merged 8 commits into from
Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.11.0-rc.3

- Renamed `React.jsx(s)`, `ReactDOM.jsx(s)` to `React.jsx(s)NotKeyed`, `ReactDOM.jsx(s)NotKeyed`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs update.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for missing changelog and tests too often 😅
Updated b8fa0f3

- Added `React.jsx(s)`, `ReactDOM.jsx(s)` which are having key as optional argument.

## 0.11.0-rc.2

- Fixed JSX PPX V3 backward compatibility.
Expand Down
33 changes: 25 additions & 8 deletions src/React.bs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 25 additions & 15 deletions src/React.res
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,13 @@ type component<'props> = Jsx.component<'props>

let component = Jsx.component

%%private(
@inline
let addKeyProp = (p: 'props, k: string): 'props =>
Obj.magic(Js.Obj.assign(Obj.magic(p), {"key": k}))
)

@module("react")
external createElement: (component<'props>, 'props) => element = "createElement"

let createElementWithKey = (component, props, key) =>
createElement(component, addKeyProp(props, key))
let createElementWithKey = (~key=?, component, props) => {
let _ = Obj.magic(props)["key"] = key
createElement(component, props)
}

@module("react")
external cloneElement: (element, 'props) => element = "cloneElement"
Expand All @@ -36,21 +32,35 @@ external isValidElement: 'a => bool = "isValidElement"
external createElementVariadic: (component<'props>, 'props, array<element>) => element =
"createElement"

let createElementVariadicWithKey = (component, props, elements, key) =>
createElementVariadic(component, addKeyProp(props, key), elements)
let createElementVariadicWithKey = (~key=?, component, props, elements) => {
let _ = Obj.magic(props)["key"] = key
createElementVariadic(component, props, elements)
}

@module("react/jsx-runtime")
external jsxKeyed: (component<'props>, 'props, string) => element = "jsx"
external jsxNotKeyed: (component<'props>, 'props) => element = "jsx"

@module("react/jsx-runtime")
external jsx: (component<'props>, 'props) => element = "jsx"
external jsxKeyed: (component<'props>, 'props, string) => element = "jsx"

let jsx = (~key=?, component, props) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering whether this can be achieved directly with an external.
Btw where is the documentation that one can pass the key as third argument to the react jsxfunction?

Copy link
Member Author

@mununki mununki Oct 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

external jsxKeyed: (component<'props>, 'props, ~key: option<string>=?) => element = "jsx"

Like this? Shouldn't we have to move the labelled key argument position to first?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that can't be done because it needs to be before mandatory arguments.
Unless one adds a fake additional mandatory argument of type unit perhaps.

Copy link
Contributor

@cristianoc cristianoc Oct 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work?

type was wrong_edited_to_fix_type_of_key

external jsx: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work?

external jsx: (component<'props>, 'props, ~key: option<string>=?, @ignore unit) => element = "jsx"

I don't think it is working.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for cleanest generated output, one needs to expose 2 functions:
jsx without a key argument, and jsxKeyed with a mandatory 3rd argument of type option<string>.
Then at call site the PPX would call jsx when key is not passed, and would call jsxKeyed when key is passed.

Copy link
Member Author

@mununki mununki Oct 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I couldn't find the doc about the third argument as key, but I found it from the source code. https://github.com/facebook/react/blob/8e2bde6f2751aa6335f3cef488c05c3ea08e074a/packages/react/src/jsx/ReactJSXElement.js#L210

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the key argument needs to be optional not option<string> as mandatory. If it is option<string> as mandatory, then users should use it.

<C key=Some("k") />

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the key argument needs to be optional not option<string> as mandatory. If it is option<string> as mandatory, then users should use it.

<C key=Some("k") />

It does not have to be optional. The PPX can take care of passing the value in the appropriate way.
But it might be simpler to make it optional so the call PPX only needs to do one special thing: pass a final ().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for cleanest generated output, one needs to expose 2 functions: jsx without a key argument, and jsxKeyed with a mandatory 3rd argument of type option<string>. Then at call site the PPX would call jsx when key is not passed, and would call jsxKeyed when key is passed.

This would be easy fix, I'll fix the binding and make a PR for PPX.

switch key {
| Some(key) => jsxKeyed(component, props, key)
| None => jsxNotKeyed(component, props)
}

@module("react/jsx-runtime")
external jsxs: (component<'props>, 'props) => element = "jsxs"
external jsxsNotKeyed: (component<'props>, 'props) => element = "jsxs"

@module("react/jsx-runtime")
external jsxsKeyed: (component<'props>, 'props, string) => element = "jsxs"

let jsxs = (~key=?, component, props) =>
switch key {
| Some(key) => jsxsKeyed(component, props, key)
| None => jsxsNotKeyed(component, props)
}

type fragmentProps<'children> = {children: 'children}

@module("react/jsx-runtime") external jsxFragment: component<fragmentProps<'children>> = "Fragment"
Expand Down Expand Up @@ -413,13 +423,13 @@ external useInsertionEffect7: (

@module("react")
external useSyncExternalStore: (
~subscribe: @uncurry ((unit => unit) => ((. unit) => unit)),
~subscribe: @uncurry (unit => unit, . unit) => unit,
~getSnapshot: @uncurry unit => 'state,
) => 'state = "useSyncExternalStore"

@module("react")
external useSyncExternalStoreWithServerSnapshot: (
~subscribe: @uncurry ((unit => unit) => ((. unit) => unit)),
~subscribe: @uncurry (unit => unit, . unit) => unit,
~getSnapshot: @uncurry unit => 'state,
~getServerSnapshot: @uncurry unit => 'state,
) => 'state = "useSyncExternalStore"
Expand Down
21 changes: 20 additions & 1 deletion src/ReactDOM.bs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions src/ReactDOM.res
Original file line number Diff line number Diff line change
Expand Up @@ -1109,17 +1109,29 @@ external createDOMElementVariadic: (
external someElement: React.element => option<React.element> = "%identity"

@module("react/jsx-runtime")
external jsxKeyed: (string, JsxDOM.domProps, string) => Jsx.element = "jsx"
external jsxNotKeyed: (string, JsxDOM.domProps) => Jsx.element = "jsx"

@module("react/jsx-runtime")
external jsx: (string, JsxDOM.domProps) => Jsx.element = "jsx"
external jsxKeyed: (string, JsxDOM.domProps, string) => Jsx.element = "jsx"

let jsx = (~key=?, element, props) =>
switch key {
| Some(key) => jsxKeyed(element, props, key)
| None => jsxNotKeyed(element, props)
}

@module("react/jsx-runtime")
external jsxs: (string, JsxDOM.domProps) => Jsx.element = "jsxs"
external jsxsNotKeyed: (string, JsxDOM.domProps) => Jsx.element = "jsxs"

@module("react/jsx-runtime")
external jsxsKeyed: (string, JsxDOM.domProps, string) => Jsx.element = "jsxs"

let jsxs = (~key=?, element, props) =>
switch key {
| Some(key) => jsxsKeyed(element, props, key)
| None => jsxsNotKeyed(element, props)
}

// Currently, not used by JSX ppx
@deprecated("Please use ReactDOM.createElement instead.")
external stringToComponent: string => React.component<'a> = "%identity"
Expand Down