Skip to content

Instantly share code, notes, and snippets.

@soegaard
Created November 9, 2021 21:23
Show Gist options
  • Save soegaard/1a12a9c3eccd295f1d91e030f9f5ae6a to your computer and use it in GitHub Desktop.
Save soegaard/1a12a9c3eccd295f1d91e030f9f5ae6a to your computer and use it in GitHub Desktop.
Urlang Counter Example using React
#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