Skip to content

Commit 5ff3bf7

Browse files
davesnxjchavarri
andauthored
Support for React 19 (#846)
* Add useTransition with async support * Add useTransitionAsync into the interface * Add useOptimistic * Rename use to usePromise and add useContext * Move useTransitionAsync to Experimental * Add formStatus to interface * Leftover from removing useTransitionAsync to Experimental * Add action_ and action_Async in ReactDOM props * Fix reference to function on formStatus * Embed FormData for now * Add test of Form with useOptimistic * Embed FormData into ReactDOM * Move formStatus into ReactDOM * Run formatter * Bind React.useActionState instead of ReactDOM.useFormState * Remove React.use being 'a => 'b * Revert change on ReactDOM's prop 'action' * useAction state published in React.rei * Remove React.use being 'a => 'b * Use act from 'react' inseat of react-dom/test-utils * Snapshot with lower {} * Update src/React.re Co-authored-by: Javier Chávarri <[email protected]> * Update src/React.re Co-authored-by: Javier Chávarri <[email protected]> * Update src/React.re Co-authored-by: Javier Chávarri <[email protected]> * Add uri comment back on action * Add deprecations on ReactDOMTestUtils * Replace react dom's testing library with ReactTestingLibrary (#859) * Install melange-testing-library * Install melange-testing-library npm deps * Vendor melange-testing-library * Fix Form__test with RTL * Start migrating Hooks__test * Remove dependency * Remove unused code from Form__test * Add a jest-devtoolsgs * Add a jest-devtools * Migrate Hooks and Form into RTL * Add demo to manually test easily * Use Uncurried for tests * Migrate all React__test * Force install since we are dealing with R19 * Snapshot with lower {} * Remove jest from demo/dune * Add comment on install --force * Bind React.act and React.actAsync * Bind React.act and React.actAsync * Use React.act as async version only * Test react.act and react.actasync * Fix hola test :( * Enable ref as valid prop (#862) * Install melange-testing-library * Install melange-testing-library npm deps * Vendor melange-testing-library * Fix Form__test with RTL * Start migrating Hooks__test * Remove dependency * Remove unused code from Form__test * Add a jest-devtoolsgs * Add a jest-devtools * Migrate Hooks and Form into RTL * Add demo to manually test easily * Use Uncurried for tests * Migrate all React__test * Force install since we are dealing with R19 * Snapshot with lower {} * Enable ref in ppx * Add jest test for ref * Remove jest from demo/dune * Add comment on install --force * Improve test from checking ref * Bind React.act and React.actAsync * Bind React.act and React.actAsync * Migrate custom childrens test to RTL * Fix role for React__test * Rollback change on _ppx * Install latest react version with the useState fix * Use [email protected] --------- Co-authored-by: Javier Chávarri <[email protected]>
1 parent 2452ff8 commit 5ff3bf7

35 files changed

+6357
-822
lines changed

Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ jest: ## Run the jest unit tests
3131
jest-watch: ## Run the jest unit tests in watch mode
3232
@npx jest --watch
3333

34+
.PHONY: jest-devtools
35+
jest-devtools: ## Run the jest unit tests in watch mode
36+
@echo "open Chrome and go to chrome://inspect"
37+
@node --inspect-brk node_modules/.bin/jest --runInBand --detectOpenHandles
38+
3439
.PHONY: test
3540
test: ## Run the runtests from dune (snapshot)
3641
@$(DUNE) build @runtest
@@ -54,11 +59,19 @@ format-check: ## Checks if format is correct
5459
.PHONY: install
5560
install: ## Update the package dependencies when new deps are added to dune-project
5661
@opam install . --deps-only --with-test --with-dev-setup
57-
@npm install
62+
@npm install --force
5863

5964
.PHONY: init
6065
create-switch: ## Create a local opam switch
6166
@opam switch create . 5.2.0 --no-install
6267

6368
.PHONY: init
6469
init: create-switch install ## Create a local opam switch, install deps
70+
71+
.PHONY: demo-watch
72+
demo-watch: ## Build the demo in watch mode
73+
@$(DUNE) build @melange-app --watch
74+
75+
.PHONY: demo-serve
76+
demo-serve: ## Build the demo and serve it
77+
npx http-server -p 8080 _build/default/demo/

demo/dune

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(melange.emit
2+
(target demo)
3+
(alias melange-app)
4+
(module_systems
5+
(es6 mjs))
6+
(libraries reason-react melange.belt melange.dom)
7+
(runtime_deps index.html)
8+
(preprocess
9+
(pps melange.ppx reason-react-ppx)))

demo/index.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>Demo reason-react</title>
9+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.8.1.min.css" />
10+
<style>
11+
body {
12+
padding: 2rem;
13+
}
14+
15+
#root {
16+
margin-left: auto;
17+
margin-right: auto;
18+
width: 900px;
19+
}
20+
</style>
21+
<script type="importmap">
22+
{
23+
"imports": {
24+
"melange/": "./demo/node_modules/melange/",
25+
"melange.belt/": "./demo/node_modules/melange.belt/",
26+
"melange.js/": "./demo/node_modules/melange.js/",
27+
"reason-react/": "./demo/node_modules/reason-react/",
28+
"react/jsx-runtime": "https://esm.sh/[email protected]/jsx-runtime",
29+
"react": "https://esm.sh/[email protected]",
30+
"react-dom": "https://esm.sh/[email protected]",
31+
"react-dom/client": "https://esm.sh/[email protected]/client"
32+
}
33+
}
34+
</script>
35+
</head>
36+
37+
<body>
38+
<div id="root"></div>
39+
</body>
40+
<script type="module" src="./demo/demo/main.mjs"></script>
41+
42+
</html>

demo/main.re

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
module Stateful = {
2+
[@react.component]
3+
let make = (~title, ~initialValue=0, ~children=React.null) => {
4+
let (value, setValue) = React.useState(() => initialValue);
5+
let onClick = _ => setValue(value => value + 1);
6+
7+
<section>
8+
<h3> {React.string(title)} </h3>
9+
<button key="asdf" onClick> value->React.int </button>
10+
children
11+
</section>;
12+
};
13+
};
14+
15+
module Reducer = {
16+
type action =
17+
| Increment
18+
| Decrement;
19+
20+
[@react.component]
21+
let make = (~initialValue=0) => {
22+
let (state, send) =
23+
React.useReducer(
24+
(state, action) =>
25+
switch (action) {
26+
| Increment => state + 1
27+
| Decrement => state - 1
28+
},
29+
initialValue,
30+
);
31+
32+
Js.log2("Reducer state", state);
33+
34+
<section>
35+
<h3> {React.string("React.useReducer")} </h3>
36+
<main> state->React.int </main>
37+
<button onClick={_ => send(Increment)}>
38+
"Increment"->React.string
39+
</button>
40+
<button onClick={_ => send(Decrement)}>
41+
"Decrement"->React.string
42+
</button>
43+
</section>;
44+
};
45+
};
46+
47+
module ReducerWithMapState = {
48+
type action =
49+
| Increment
50+
| Decrement;
51+
52+
[@react.component]
53+
let make = (~initialValue=0) => {
54+
let (state, send) =
55+
React.useReducerWithMapState(
56+
(state, action) =>
57+
switch (action) {
58+
| Increment => state + 1
59+
| Decrement => state - 1
60+
},
61+
initialValue,
62+
initialValue => initialValue + 75,
63+
);
64+
65+
Js.log2("ReducerWithMapState state", state);
66+
67+
<section>
68+
<h3> {React.string("React.useReducerWithMapState")} </h3>
69+
<main> state->React.int </main>
70+
<button onClick={_ => send(Increment)}>
71+
"Increment"->React.string
72+
</button>
73+
<button onClick={_ => send(Decrement)}>
74+
"Decrement"->React.string
75+
</button>
76+
</section>;
77+
};
78+
};
79+
80+
module WithEffect = {
81+
[@react.component]
82+
let make = (~value) => {
83+
React.useEffect1(
84+
() => {
85+
Js.log("useEffect");
86+
None;
87+
},
88+
[|value|],
89+
);
90+
91+
React.string("React.useEffect");
92+
};
93+
};
94+
95+
module RerenderOnEachClick = {
96+
[@react.component]
97+
let make = (~value=0, ~callback as _) => {
98+
let (value, setValue) = React.useState(() => value);
99+
let onClick = _ =>
100+
if (value < 3) {
101+
Js.log2("Clicked with:", value);
102+
setValue(value => value + 1);
103+
} else {
104+
Js.log("Max value reached, not firing a rerender");
105+
setValue(value => value);
106+
};
107+
108+
<section>
109+
<h3> {React.string("RerenderOnEachClick")} </h3>
110+
<button onClick> <WithEffect value /> </button>
111+
</section>;
112+
};
113+
};
114+
115+
module WithLayoutEffect = {
116+
[@react.component]
117+
let make = (~value=0, ~callback) => {
118+
React.useLayoutEffect1(
119+
() => {
120+
callback(value);
121+
Js.log("useLayoutEffect");
122+
None;
123+
},
124+
[|value|],
125+
);
126+
127+
<section> <h3> {React.string("React.useLayoutEffect")} </h3> </section>;
128+
};
129+
};
130+
131+
module WithRefAndEffect = {
132+
[@react.component]
133+
let make = (~callback) => {
134+
let myRef = React.useRef(1);
135+
React.useEffect0(() => {
136+
myRef.current = myRef.current + 1;
137+
callback(myRef);
138+
None;
139+
});
140+
141+
<section>
142+
<h3> {React.string("React.useRef and useEffect")} </h3>
143+
<main> {React.int(myRef.current)} </main>
144+
</section>;
145+
};
146+
};
147+
148+
[@mel.module "react"]
149+
external useReducer:
150+
([@mel.uncurry] (('state, 'action) => 'state), 'state) =>
151+
('state, 'action => unit) =
152+
"useReducer";
153+
154+
module UseReducerNoProblemo = {
155+
[@react.component]
156+
let make = () => {
157+
let reducer = (v, _) => v + 1;
158+
let (state, send) = useReducer(reducer, 0);
159+
Js.log("asdfasd");
160+
<button onClick={_ => send(0)}> {React.int(state)} </button>;
161+
};
162+
};
163+
164+
module App = {
165+
[@react.component]
166+
let make = (~initialValue) => {
167+
let value = 99;
168+
let callback = _number => ();
169+
170+
<main>
171+
<Stateful title="Stateful" initialValue />
172+
<Reducer key="reducer" initialValue />
173+
<ReducerWithMapState key="reducer-with-map-state" initialValue />
174+
<RerenderOnEachClick key="rerender-on-each-click" value=0 callback />
175+
<WithLayoutEffect key="layout-effect" value callback />
176+
<WithRefAndEffect key="ref-and-effect" callback />
177+
<UseReducerNoProblemo />
178+
</main>;
179+
};
180+
};
181+
182+
switch (ReactDOM.querySelector("#root")) {
183+
| Some(el) =>
184+
let root = ReactDOM.Client.createRoot(el);
185+
ReactDOM.Client.render(root, <App initialValue=0 />);
186+
| None => Js.log("No root element found")
187+
};

dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
(dirs src test ppx)
1+
(dirs src test ppx demo)

0 commit comments

Comments
 (0)