Skip to content

Commit 764a299

Browse files
authored
Add rescript example (#442) (#552)
* Add rescript example (#442) * Add rescript build configurations * Create rescript page components * Add rescript deps * Solve render comments issue * Refactor css transaction components React Hooks shouldn't be used in loops or if statement. * Ignore the rescript output js files from lint The rescript output js files from lint because it doesn't follow the lint rules. * Apply review requested changes * Remove unused tag * Add ROR binding * Apply review requested changes * remove action js file * Remove .mjs files * Compile rescript files before running js tests * Build recript files before test * minor change * remove react-on-rails binding * migrate to tailwind * remove bs.mjs files * remove unused scss files * fix rescript spec tests * build rescript before test * refactor rescript src * fix add commit fail bug * refactor the types * format rescript files * remove the types files * update error rescript page state design * refactor switch statements * move store comment state to the form component * format rescript * rename the saving state * empty commit
1 parent 5762d08 commit 764a299

31 files changed

+740
-4
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ public/
44
client/app/libs/i18n/translations.js
55
client/app/libs/i18n/default.js
66
postcss.config.js
7+
client/app/bundles/comments/rescript/

.github/workflows/rspec_test.yml

+3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ jobs:
7373
- name: Build i18n libraries
7474
run: bundle exec rake react_on_rails:locale
7575

76+
- name: Build Rescript components
77+
run: yarn res:build
78+
7679
- name: Build shakapacker chunks
7780
run: NODE_ENV=development bundle exec bin/shakapacker
7881

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,8 @@ client/app/libs/i18n/default.js
4747
/yarn-error.log
4848
yarn-debug.log*
4949
.yarn-integrity
50+
51+
lib/bs
52+
/lib/ocaml
53+
54+
client/app/bundles/comments/rescript/**/*.bs.js

Procfile.dev

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Procfile for development using HMR
22
# You can run these commands in separate shells
3+
rescript: yarn res:dev
34
redis: redis-server
45
rails: bundle exec rails s -p 3000
56
wp-client: HMR=true RAILS_ENV=development NODE_ENV=development bin/shakapacker-dev-server

app/controllers/pages_controller.rb

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def no_router
3636

3737
def simple; end
3838

39+
def rescript; end
40+
3941
private
4042

4143
def set_comments

app/views/pages/rescript.html.erb

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= react_component "RescriptShow", prerender: true %>

bsconfig.json

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "react-webpack-rails-tutorial",
3+
"sources": [
4+
{
5+
"dir": "client/app/bundles/comments/rescript",
6+
"subdirs": true
7+
}
8+
],
9+
"package-specs": [
10+
{
11+
"module": "es6",
12+
"in-source": true
13+
}
14+
],
15+
"bsc-flags": ["-open JsonCombinators", "-open Belt"],
16+
"suffix": ".bs.js",
17+
"bs-dependencies": [
18+
"@rescript/react",
19+
"@rescript/core",
20+
"@glennsl/rescript-fetch",
21+
"@glennsl/rescript-json-combinators",
22+
"rescript-react-on-rails"
23+
],
24+
"jsx": {
25+
"version": 4,
26+
"mode": "automatic"
27+
}
28+
}

client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx

+8
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ function NavigationBar(props) {
9797
Classic Rails
9898
</a>
9999
</li>
100+
<li className={classNames({ 'bg-yellow-100': pathname === paths.RESCRIPT_PATH })}>
101+
<a
102+
className="px-2 py-4 w-full inline-block text-gray-500 hover:text-gray-700"
103+
href={paths.RESCRIPT_PATH}
104+
>
105+
Rescript
106+
</a>
107+
</li>
100108
<li>
101109
<a
102110
className="px-2 py-4 w-full inline-block text-gray-500 hover:text-gray-700"
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const ROUTER_PATH = '/';
22
export const REACT_ROUTER_PATH = '/react-router';
33
export const NO_ROUTER_PATH = '/no-router';
4+
export const RESCRIPT_PATH = '/rescript';
45
export const SIMPLE_REACT_PATH = '/simple';
56
export const STIMULUS_PATH = '/stimulus';
67
export const RAILS_PATH = '/comments';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// TODO : use only one way to make http requests either Axios or Fetch
2+
module Create = {
3+
type t = {
4+
author: string,
5+
text: string,
6+
}
7+
8+
let storeComment = async (comment: t) => {
9+
let _ = await Axios.post(
10+
"comments.json",
11+
{
12+
"author": comment.author,
13+
"text": comment.text,
14+
},
15+
{
16+
"responseType": "json",
17+
"headers": {
18+
// see https://github.com/shakacode/react_on_rails/blob/249c69812474e0f532df432581bf5e618df0e1ec/node_package/src/Authenticity.ts#L13C1-L18C1
19+
"X-CSRF-Token": ReactOnRails.authenticityToken(),
20+
"X-Requested-With": "XMLHttpRequest",
21+
},
22+
},
23+
)
24+
}
25+
}
26+
27+
module Fetch = {
28+
type t = {
29+
author: string,
30+
text: string,
31+
id: int,
32+
}
33+
34+
type comments = array<t>
35+
36+
type commentsRes = {comments: comments}
37+
38+
let fetchComments = async (): result<comments, string> => {
39+
open Json.Decode
40+
41+
let response = await Fetch.get("comments.json")
42+
let jsonRes = await response->Fetch.Response.json
43+
44+
let jsonComment = Json.Decode.object(field => {
45+
author: field.required(. "author", string),
46+
text: field.required(. "text", string),
47+
id: field.required(. "id", int),
48+
})
49+
50+
let jsonComments = Json.Decode.object(field => {
51+
comments: field.required(. "comments", array(jsonComment)),
52+
})
53+
54+
switch jsonRes->Json.decode(jsonComments) {
55+
| Ok(decodedRes) => Ok(decodedRes.comments)
56+
| Error(e) => Error(e)
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
type formDisplay =
2+
| Horizontal
3+
| Inline
4+
| Stacked
5+
6+
type savingStatus =
7+
| Idle
8+
| Saving
9+
| Error
10+
11+
type formData = {
12+
formName: string,
13+
formType: formDisplay,
14+
}
15+
16+
type state = {
17+
author: string,
18+
text: string,
19+
form: formDisplay,
20+
savingStatus: savingStatus,
21+
}
22+
23+
type action =
24+
| SetAuthor(string)
25+
| SetText(string)
26+
| SetFormType(formDisplay)
27+
| SetSavingError
28+
| ClearSavingError
29+
| SetStoreStatusSaving
30+
31+
let reducer = (state: state, action: action): state => {
32+
switch action {
33+
| SetAuthor(author) => {...state, author}
34+
| SetText(text) => {...state, text}
35+
| SetFormType(form) => {...state, form}
36+
| SetSavingError => {...state, savingStatus: Error}
37+
| ClearSavingError => {...state, savingStatus: Idle}
38+
| SetStoreStatusSaving => {...state, savingStatus: Saving}
39+
}
40+
}
41+
42+
@react.component
43+
let make = (~fetchData) => {
44+
let (state, dispatch) = React.useReducer(
45+
reducer,
46+
{
47+
author: "",
48+
text: "",
49+
form: Horizontal,
50+
savingStatus: Idle,
51+
},
52+
)
53+
54+
let disabled = React.useMemo1(() => {
55+
switch state.savingStatus {
56+
| Saving => true
57+
| Idle
58+
| Error => false
59+
}
60+
}, [state.savingStatus])
61+
62+
let storeComment = (newComment: Actions.Create.t) => {
63+
SetStoreStatusSaving->dispatch
64+
let saveAndFetchComments = async () => {
65+
try {
66+
let _ = await Actions.Create.storeComment(newComment)
67+
ClearSavingError->dispatch
68+
69+
await fetchData()
70+
} catch {
71+
| _ => SetSavingError->dispatch
72+
}
73+
}
74+
saveAndFetchComments()->ignore
75+
}
76+
77+
let handleAuthorChange = event => {
78+
let value = ReactEvent.Form.currentTarget(event)["value"]
79+
SetAuthor(value)->dispatch
80+
}
81+
82+
let handleTextChange = event => {
83+
let value = ReactEvent.Form.currentTarget(event)["value"]
84+
SetText(value)->dispatch
85+
}
86+
87+
let handleSubmit = event => {
88+
ReactEvent.Form.preventDefault(event)
89+
storeComment({author: state.author, text: state.text})
90+
}
91+
92+
let forms: array<formData> = [
93+
{formName: "Horizontal Form", formType: Horizontal},
94+
{formName: "Inline Form", formType: Inline},
95+
{formName: "Stacked Form", formType: Stacked},
96+
]
97+
98+
<div>
99+
<div className="flex gap-1 not-prose">
100+
{forms
101+
->Array.map(form =>
102+
<button
103+
key={`form_${form.formName}`}
104+
className={`px-6 py-2 font-semibold border-0 rounded ${state.form == form.formType
105+
? "text-sky-50 bg-sky-600"
106+
: "text-sky-600 hover:bg-gray-100"}`}
107+
onClick={event => SetFormType(form.formType)->dispatch}>
108+
{form.formName->React.string}
109+
</button>
110+
)
111+
->React.array}
112+
</div>
113+
<hr />
114+
{switch state.form {
115+
| Horizontal =>
116+
<HorizontalForm
117+
author={state.author}
118+
handleAuthorChange
119+
text={state.text}
120+
handleTextChange
121+
handleSubmit
122+
disabled
123+
/>
124+
| Stacked =>
125+
<StackedFrom
126+
author={state.author}
127+
handleAuthorChange
128+
text={state.text}
129+
handleTextChange
130+
handleSubmit
131+
disabled
132+
/>
133+
| Inline =>
134+
<InlineForm
135+
author={state.author}
136+
handleAuthorChange
137+
text={state.text}
138+
handleTextChange
139+
handleSubmit
140+
disabled
141+
/>
142+
}}
143+
{switch state.savingStatus {
144+
| Error => <AlertError errorMsg="Can't save the comment!" />
145+
| Idle
146+
| Saving => React.null
147+
}}
148+
</div>
149+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@react.component
2+
let make = (~author, ~handleAuthorChange, ~text, ~handleTextChange, ~handleSubmit, ~disabled) => {
3+
<form className="form-horizontal flex flex-col gap-4" onSubmit=handleSubmit disabled>
4+
<div className="flex flex-col gap-0 items-center lg:gap-4 lg:flex-row">
5+
<label className="w-full lg:w-2/12 lg:text-end shrink-0"> {"Name"->React.string} </label>
6+
<input
7+
type_="text"
8+
className="px-3 py-1 leading-4 border border-gray-300 rounded w-full"
9+
placeholder="Your Name"
10+
name="comment_author"
11+
id="comment_author"
12+
onChange=handleAuthorChange
13+
value={author}
14+
/>
15+
</div>
16+
<div className="flex flex-col gap-0 items-center lg:gap-4 lg:flex-row">
17+
<label className="w-full lg:w-2/12 lg:text-end shrink-0"> {"Text"->React.string} </label>
18+
<input
19+
type_="text"
20+
className="px-3 py-1 leading-4 border border-gray-300 rounded w-full"
21+
placeholder="Say something using markdown..."
22+
name="comment_text"
23+
id="comment_text"
24+
onChange=handleTextChange
25+
value={text}
26+
/>
27+
</div>
28+
<div className="flex flex-col gap-0 lg:gap-4 lg:flex-row">
29+
<div className="hidden lg:block lg:w-2/12 grow-0" />
30+
<input
31+
type_="submit"
32+
className="self-start px-3 py-1 font-semibold border-0 rounded text-sky-50 bg-sky-600 hover:bg-sky-800"
33+
value="Post"
34+
/>
35+
</div>
36+
</form>
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@react.component
2+
let make = (~author, ~handleAuthorChange, ~text, ~handleTextChange, ~handleSubmit, ~disabled) => {
3+
<form
4+
className="form-inline flex flex-col lg:flex-row flex-wrap gap-4"
5+
onSubmit=handleSubmit
6+
disabled>
7+
<div className="flex gap-2 items-center">
8+
<label> {"Name"->React.string} </label>
9+
<input
10+
type_="text"
11+
className="px-3 py-1 leading-4 border border-gray-300 rounded"
12+
placeholder="Your Name"
13+
name="comment_author"
14+
id="comment_author"
15+
value={author}
16+
onChange=handleAuthorChange
17+
/>
18+
</div>
19+
<div className="flex gap-2 items-center">
20+
<label> {"Text"->React.string} </label>
21+
<input
22+
type_="text"
23+
className="px-3 py-1 leading-4 border border-gray-300 rounded"
24+
placeholder="Say something using markdown..."
25+
name="comment_text"
26+
id="comment_text"
27+
onChange=handleTextChange
28+
value={text}
29+
/>
30+
</div>
31+
<div className="flex gap-2">
32+
<input
33+
type_="submit"
34+
className="self-start px-3 py-1 font-semibold border-0 rounded text-sky-50 bg-sky-600 hover:bg-sky-800"
35+
onSubmit=handleSubmit
36+
value="Post"
37+
/>
38+
</div>
39+
</form>
40+
}

0 commit comments

Comments
 (0)