Skip to content

Commit

Permalink
Add rescript example (#442) (#552)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Yassa-hue authored Nov 17, 2023
1 parent 5762d08 commit 764a299
Show file tree
Hide file tree
Showing 31 changed files with 740 additions and 4 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ public/
client/app/libs/i18n/translations.js
client/app/libs/i18n/default.js
postcss.config.js
client/app/bundles/comments/rescript/
3 changes: 3 additions & 0 deletions .github/workflows/rspec_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ jobs:
- name: Build i18n libraries
run: bundle exec rake react_on_rails:locale

- name: Build Rescript components
run: yarn res:build

- name: Build shakapacker chunks
run: NODE_ENV=development bundle exec bin/shakapacker

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ client/app/libs/i18n/default.js
/yarn-error.log
yarn-debug.log*
.yarn-integrity

lib/bs
/lib/ocaml

client/app/bundles/comments/rescript/**/*.bs.js
1 change: 1 addition & 0 deletions Procfile.dev
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Procfile for development using HMR
# You can run these commands in separate shells
rescript: yarn res:dev
redis: redis-server
rails: bundle exec rails s -p 3000
wp-client: HMR=true RAILS_ENV=development NODE_ENV=development bin/shakapacker-dev-server
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def no_router

def simple; end

def rescript; end

private

def set_comments
Expand Down
1 change: 1 addition & 0 deletions app/views/pages/rescript.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= react_component "RescriptShow", prerender: true %>
28 changes: 28 additions & 0 deletions bsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "react-webpack-rails-tutorial",
"sources": [
{
"dir": "client/app/bundles/comments/rescript",
"subdirs": true
}
],
"package-specs": [
{
"module": "es6",
"in-source": true
}
],
"bsc-flags": ["-open JsonCombinators", "-open Belt"],
"suffix": ".bs.js",
"bs-dependencies": [
"@rescript/react",
"@rescript/core",
"@glennsl/rescript-fetch",
"@glennsl/rescript-json-combinators",
"rescript-react-on-rails"
],
"jsx": {
"version": 4,
"mode": "automatic"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ function NavigationBar(props) {
Classic Rails
</a>
</li>
<li className={classNames({ 'bg-yellow-100': pathname === paths.RESCRIPT_PATH })}>
<a
className="px-2 py-4 w-full inline-block text-gray-500 hover:text-gray-700"
href={paths.RESCRIPT_PATH}
>
Rescript
</a>
</li>
<li>
<a
className="px-2 py-4 w-full inline-block text-gray-500 hover:text-gray-700"
Expand Down
1 change: 1 addition & 0 deletions client/app/bundles/comments/constants/paths.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const ROUTER_PATH = '/';
export const REACT_ROUTER_PATH = '/react-router';
export const NO_ROUTER_PATH = '/no-router';
export const RESCRIPT_PATH = '/rescript';
export const SIMPLE_REACT_PATH = '/simple';
export const STIMULUS_PATH = '/stimulus';
export const RAILS_PATH = '/comments';
59 changes: 59 additions & 0 deletions client/app/bundles/comments/rescript/Actions/Actions.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// TODO : use only one way to make http requests either Axios or Fetch
module Create = {
type t = {
author: string,
text: string,
}

let storeComment = async (comment: t) => {
let _ = await Axios.post(
"comments.json",
{
"author": comment.author,
"text": comment.text,
},
{
"responseType": "json",
"headers": {
// see https://github.com/shakacode/react_on_rails/blob/249c69812474e0f532df432581bf5e618df0e1ec/node_package/src/Authenticity.ts#L13C1-L18C1
"X-CSRF-Token": ReactOnRails.authenticityToken(),
"X-Requested-With": "XMLHttpRequest",
},
},
)
}
}

module Fetch = {
type t = {
author: string,
text: string,
id: int,
}

type comments = array<t>

type commentsRes = {comments: comments}

let fetchComments = async (): result<comments, string> => {
open Json.Decode

let response = await Fetch.get("comments.json")
let jsonRes = await response->Fetch.Response.json

let jsonComment = Json.Decode.object(field => {
author: field.required(. "author", string),
text: field.required(. "text", string),
id: field.required(. "id", int),
})

let jsonComments = Json.Decode.object(field => {
comments: field.required(. "comments", array(jsonComment)),
})

switch jsonRes->Json.decode(jsonComments) {
| Ok(decodedRes) => Ok(decodedRes.comments)
| Error(e) => Error(e)
}
}
}
149 changes: 149 additions & 0 deletions client/app/bundles/comments/rescript/CommentForm/CommentForm.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
type formDisplay =
| Horizontal
| Inline
| Stacked

type savingStatus =
| Idle
| Saving
| Error

type formData = {
formName: string,
formType: formDisplay,
}

type state = {
author: string,
text: string,
form: formDisplay,
savingStatus: savingStatus,
}

type action =
| SetAuthor(string)
| SetText(string)
| SetFormType(formDisplay)
| SetSavingError
| ClearSavingError
| SetStoreStatusSaving

let reducer = (state: state, action: action): state => {
switch action {
| SetAuthor(author) => {...state, author}
| SetText(text) => {...state, text}
| SetFormType(form) => {...state, form}
| SetSavingError => {...state, savingStatus: Error}
| ClearSavingError => {...state, savingStatus: Idle}
| SetStoreStatusSaving => {...state, savingStatus: Saving}
}
}

@react.component
let make = (~fetchData) => {
let (state, dispatch) = React.useReducer(
reducer,
{
author: "",
text: "",
form: Horizontal,
savingStatus: Idle,
},
)

let disabled = React.useMemo1(() => {
switch state.savingStatus {
| Saving => true
| Idle
| Error => false
}
}, [state.savingStatus])

let storeComment = (newComment: Actions.Create.t) => {
SetStoreStatusSaving->dispatch
let saveAndFetchComments = async () => {
try {
let _ = await Actions.Create.storeComment(newComment)
ClearSavingError->dispatch

await fetchData()
} catch {
| _ => SetSavingError->dispatch
}
}
saveAndFetchComments()->ignore
}

let handleAuthorChange = event => {
let value = ReactEvent.Form.currentTarget(event)["value"]
SetAuthor(value)->dispatch
}

let handleTextChange = event => {
let value = ReactEvent.Form.currentTarget(event)["value"]
SetText(value)->dispatch
}

let handleSubmit = event => {
ReactEvent.Form.preventDefault(event)
storeComment({author: state.author, text: state.text})
}

let forms: array<formData> = [
{formName: "Horizontal Form", formType: Horizontal},
{formName: "Inline Form", formType: Inline},
{formName: "Stacked Form", formType: Stacked},
]

<div>
<div className="flex gap-1 not-prose">
{forms
->Array.map(form =>
<button
key={`form_${form.formName}`}
className={`px-6 py-2 font-semibold border-0 rounded ${state.form == form.formType
? "text-sky-50 bg-sky-600"
: "text-sky-600 hover:bg-gray-100"}`}
onClick={event => SetFormType(form.formType)->dispatch}>
{form.formName->React.string}
</button>
)
->React.array}
</div>
<hr />
{switch state.form {
| Horizontal =>
<HorizontalForm
author={state.author}
handleAuthorChange
text={state.text}
handleTextChange
handleSubmit
disabled
/>
| Stacked =>
<StackedFrom
author={state.author}
handleAuthorChange
text={state.text}
handleTextChange
handleSubmit
disabled
/>
| Inline =>
<InlineForm
author={state.author}
handleAuthorChange
text={state.text}
handleTextChange
handleSubmit
disabled
/>
}}
{switch state.savingStatus {
| Error => <AlertError errorMsg="Can't save the comment!" />
| Idle
| Saving => React.null
}}
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@react.component
let make = (~author, ~handleAuthorChange, ~text, ~handleTextChange, ~handleSubmit, ~disabled) => {
<form className="form-horizontal flex flex-col gap-4" onSubmit=handleSubmit disabled>
<div className="flex flex-col gap-0 items-center lg:gap-4 lg:flex-row">
<label className="w-full lg:w-2/12 lg:text-end shrink-0"> {"Name"->React.string} </label>
<input
type_="text"
className="px-3 py-1 leading-4 border border-gray-300 rounded w-full"
placeholder="Your Name"
name="comment_author"
id="comment_author"
onChange=handleAuthorChange
value={author}
/>
</div>
<div className="flex flex-col gap-0 items-center lg:gap-4 lg:flex-row">
<label className="w-full lg:w-2/12 lg:text-end shrink-0"> {"Text"->React.string} </label>
<input
type_="text"
className="px-3 py-1 leading-4 border border-gray-300 rounded w-full"
placeholder="Say something using markdown..."
name="comment_text"
id="comment_text"
onChange=handleTextChange
value={text}
/>
</div>
<div className="flex flex-col gap-0 lg:gap-4 lg:flex-row">
<div className="hidden lg:block lg:w-2/12 grow-0" />
<input
type_="submit"
className="self-start px-3 py-1 font-semibold border-0 rounded text-sky-50 bg-sky-600 hover:bg-sky-800"
value="Post"
/>
</div>
</form>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@react.component
let make = (~author, ~handleAuthorChange, ~text, ~handleTextChange, ~handleSubmit, ~disabled) => {
<form
className="form-inline flex flex-col lg:flex-row flex-wrap gap-4"
onSubmit=handleSubmit
disabled>
<div className="flex gap-2 items-center">
<label> {"Name"->React.string} </label>
<input
type_="text"
className="px-3 py-1 leading-4 border border-gray-300 rounded"
placeholder="Your Name"
name="comment_author"
id="comment_author"
value={author}
onChange=handleAuthorChange
/>
</div>
<div className="flex gap-2 items-center">
<label> {"Text"->React.string} </label>
<input
type_="text"
className="px-3 py-1 leading-4 border border-gray-300 rounded"
placeholder="Say something using markdown..."
name="comment_text"
id="comment_text"
onChange=handleTextChange
value={text}
/>
</div>
<div className="flex gap-2">
<input
type_="submit"
className="self-start px-3 py-1 font-semibold border-0 rounded text-sky-50 bg-sky-600 hover:bg-sky-800"
onSubmit=handleSubmit
value="Post"
/>
</div>
</form>
}
Loading

0 comments on commit 764a299

Please sign in to comment.