Created
November 9, 2021 21:23
-
-
Save soegaard/1a12a9c3eccd295f1d91e030f9f5ae6a to your computer and use it in GitHub Desktop.
Urlang Counter Example using React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang at-exp racket | |
(require urlang urlang/html urlang/react/urx) | |
(require net/sendurl syntax/parse) | |
;;; | |
;;; Urlang Configuration | |
;;; | |
(current-urlang-run? #f) ; run using Node? No, use browser | |
(current-urlang-echo? #t) ; print generated JavaScript? | |
(current-urlang-console.log-module-level-expr? #f) ; print top-level expression? | |
(current-urlang-beautify? #t) ; invoke js-beautify | |
;;; | |
;;; Utilities | |
;;; | |
(define-urlang-macro def | |
(λ (stx) | |
(syntax-parse stx | |
[(_def (name arg ...) . body) | |
(syntax/loc stx | |
(var [name (λ (arg ...) . body)]))] | |
[(_def name expr) | |
(syntax/loc stx | |
(var [name expr]))]))) | |
;;; | |
;;; REACT HOOKS | |
;;; | |
; SYNTAX | |
; (use-state id set-id initial-expression) | |
; is equivalent to the following JavaScript: | |
; const [id, set-id] = useState(initial-expression); | |
; in other words: | |
; a state variable is initialized with the value of initial-expresion, | |
; id can be used to reference the value, and set-id to set it. | |
(define-urlang-macro use-state | |
(λ (stx) | |
(syntax-parse stx | |
[(_ state-id set-state-id initial-expr) | |
(syntax/loc stx | |
(var [state-temp (React.useState initial-expr)] | |
[state-id (ref state-temp 0)] | |
[set-state-id (ref state-temp 1)]))]))) | |
;;; Urls for JavaScript and CSS libraries used in this example. | |
(define (script url) @~a{<script src=@url ></script>}) | |
(define (link-css url) @~a{<link href=@url rel="stylesheet">}) | |
(define (generate-html body urlang-js-file) | |
;; Given a body (a string) wrap it in a basic html template. | |
@~a{ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Testing: Skypack and React</title> | |
</head> | |
<body> | |
@body | |
<script type="module">@file->string[urlang-js-file]</script> | |
</body> | |
</html>}) | |
;;; Generate JavaScript | |
;; The urlang form will generate a JavaScript file. | |
(require urlang/extra ; make cond available in urlang | |
urlang/for) ; make "for" available in urlang | |
;; Our html will contains an element named "app-goes-here". | |
;; We simply replace that element with our React app. | |
(urlang | |
(urmodule counter-app ; output in "counter-app.js" | |
; We import ES6 modules via Skypack. | |
(import-from "https://cdn.skypack.dev/jquery" (default $)) | |
(import-from "https://cdn.skypack.dev/react" (default React)) | |
(import-from "https://cdn.skypack.dev/react-dom" (default ReactDOM)) | |
(import window) ; only available in the browser | |
;; Our "app" consists of two buttons and a counter. | |
;; When clicked the buttons will increment and decrement the counter. | |
(define (OurApp) | |
; The state is a single counter. | |
(use-state counter set-counter 0) | |
; The button component. | |
(def (Button props) | |
(def text props.text) ; is displayed on the button | |
(def delta props.delta) ; is added to the counter, when the button is clicked | |
(def (on-click) (set-counter (+ counter delta))) | |
@urx[@button[className: "button" ; the css class | |
type: "button" ; clickable button | |
onClick: on-click | |
text]]) | |
@urx[@div[@div[className: "counter" @ur[(+ "Value:" counter)]] | |
@Button[text: "Decrement" delta: -1] | |
@Button[text: "Increment" delta: +1]]]) | |
; Given a css selector as a string, find the corresponding element. | |
(define ($0 selector) (ref ($ selector) 0)) | |
; Replace the "app-goes-here" element with our app. | |
(let ([elm ($0 "#app-goes-here")]) | |
(console.log "here") | |
(console.log elm) | |
(unless (= elm undefined) | |
(def props (object [tag (elm.getAttribute "tag")])) | |
(ReactDOM.render (React.createElement OurApp props) elm))))) | |
;;; Test it in the browser | |
(send-url/contents | |
(generate-html | |
@~xs{ @h1{Testing: Skypack and react-select} | |
@div[id: "app-goes-here"]} | |
"counter-app.js")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment