├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── test
└── Main.purs
├── src
├── javascript
│ ├── Counter.js
│ └── App.js
├── index.js
└── purescript
│ ├── App.purs
│ ├── Counter
│ └── Interop.purs
│ └── Counter.purs
├── spago.dhall
├── .gitignore
├── README.md
├── package.json
└── packages.dhall
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pete-murphy/purescript-react-tutorial/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pete-murphy/purescript-react-tutorial/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pete-murphy/purescript-react-tutorial/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/test/Main.purs:
--------------------------------------------------------------------------------
1 | module Test.Main where
2 |
3 | import Prelude
4 |
5 | import Effect (Effect)
6 | import Effect.Class.Console (log)
7 |
8 | main :: Effect Unit
9 | main = do
10 | log "🍝"
11 | log "You should add some tests."
12 |
--------------------------------------------------------------------------------
/src/javascript/Counter.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | export default function Counter({ label }) {
4 | const [count, setCount] = useState(0);
5 | return (
6 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | /* Uncomment the following line to use the `./purescript/App` version of the App */
4 | // import { app as App } from "./output/App";
5 | import App from "./javascript/App";
6 |
7 | ReactDOM.render(, document.getElementById("root"));
8 |
--------------------------------------------------------------------------------
/spago.dhall:
--------------------------------------------------------------------------------
1 | {-
2 | Welcome to a Spago project!
3 | You can edit this file as you like.
4 | -}
5 | { name = "react-tutorial"
6 | , dependencies =
7 | [ "console"
8 | , "effect"
9 | , "generics-rep"
10 | , "psci-support"
11 | , "react-basic-dom"
12 | , "react-basic-hooks"
13 | , "read"
14 | ]
15 | , packages = ./packages.dhall
16 | , sources = [ "src/**/*.purs", "test/**/*.purs" ]
17 | }
18 |
--------------------------------------------------------------------------------
/src/javascript/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { jsCounter as Counter } from "../output/Counter.Interop";
3 |
4 | function App() {
5 | return (
6 |
7 |
My JavaScript App
8 | console.log("Clicked", n)} />
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default App;
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # purescript
26 | output
27 | .psc*
28 | .purs*
29 | .spago
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to Replace React Components With PureScript's React Libraries
2 |
3 | Working through this blog post by Thomas Honeyman: https://thomashoneyman.com/articles/replace-react-components-with-purescript/.
4 |
5 | ### Usage
6 |
7 | - Install dependencies with
8 |
9 | ```shell
10 | $ yarn; yarn spago build
11 | ```
12 |
13 | - Make a symbolic link for PureScript output folder so Create React App webpack server can access it.
14 |
15 | ```
16 | $ ln -s $PWD/output $PWD/src/output
17 | ```
18 |
19 | - Run the development server with `yarn start`
20 |
--------------------------------------------------------------------------------
/src/purescript/App.purs:
--------------------------------------------------------------------------------
1 | module App where
2 |
3 | import Prelude
4 | import Counter (CounterType(..), mkCounter)
5 | import React.Basic.DOM as R
6 | import React.Basic.Hooks (Component)
7 | import React.Basic.Hooks as React
8 |
9 | mkApp :: Component Unit
10 | mkApp = do
11 | counter <- mkCounter
12 | React.component "App" \_ -> React.do
13 | pure do
14 | R.div_
15 | [ R.h1_ [ R.text "My App" ]
16 | , counter
17 | { counterType: Increment
18 | , label: "Count"
19 | , onClick: mempty
20 | }
21 | ]
22 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "purecript-react-tutorial",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^17.0.1",
7 | "react-dom": "^17.0.1",
8 | "react-scripts": "4.0.2"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "eject": "react-scripts eject"
14 | },
15 | "eslintConfig": {
16 | "extends": "react-app"
17 | },
18 | "browserslist": {
19 | "production": [
20 | ">0.2%",
21 | "not dead",
22 | "not op_mini all"
23 | ],
24 | "development": [
25 | "last 1 chrome version",
26 | "last 1 firefox version",
27 | "last 1 safari version"
28 | ]
29 | },
30 | "devDependencies": {
31 | "purescript": "^0.13.8",
32 | "spago": "^0.19.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/purescript/Counter/Interop.purs:
--------------------------------------------------------------------------------
1 | module Counter.Interop where
2 |
3 | import Prelude
4 | import Counter (CounterType(..), Props, renderCounter)
5 | import Data.Maybe as Maybe
6 | import Data.Nullable (Nullable)
7 | import Data.Nullable as Nullable
8 | import Data.String.Read as Read
9 | import Effect.Uncurried (EffectFn1)
10 | import Effect.Uncurried as Uncurried
11 | import Effect.Unsafe as Unsafe
12 | import React.Basic.Hooks (JSX)
13 | import React.Basic.Hooks as React
14 |
15 | type JSProps
16 | = { label :: Nullable String
17 | , onClick :: Nullable (EffectFn1 Int Unit)
18 | , counterType :: Nullable String
19 | }
20 |
21 | jsPropsToProps :: JSProps -> Props
22 | jsPropsToProps props =
23 | { label:
24 | Maybe.fromMaybe (deriveLabelFromType props.counterType) (Nullable.toMaybe props.label)
25 | , onClick:
26 | Maybe.fromMaybe mempty (map Uncurried.runEffectFn1 (Nullable.toMaybe props.onClick))
27 | , counterType:
28 | Maybe.fromMaybe Increment (Read.read =<< Nullable.toMaybe props.counterType)
29 | }
30 | where
31 | deriveLabelFromType :: Nullable String -> String
32 | deriveLabelFromType = Maybe.fromMaybe "Count" <<< Nullable.toMaybe
33 |
34 | jsCounter :: JSProps -> JSX
35 | jsCounter =
36 | Unsafe.unsafePerformEffect do
37 | React.component "Counter" (renderCounter <<< jsPropsToProps)
38 |
--------------------------------------------------------------------------------
/src/purescript/Counter.purs:
--------------------------------------------------------------------------------
1 | module Counter where
2 |
3 | import Prelude
4 | import Data.Generic.Rep (class Generic)
5 | import Data.Generic.Rep.Show (genericShow)
6 | import Data.Maybe (Maybe(..))
7 | import Data.String.Read (class Read)
8 | import Effect (Effect)
9 | import React.Basic.DOM as R
10 | import React.Basic.Events as Events
11 | import React.Basic.Hooks (Component, JSX, Render, UseState, (/\))
12 | import React.Basic.Hooks as React
13 |
14 | type Props
15 | = { label :: String
16 | , onClick :: Int -> Effect Unit
17 | , counterType :: CounterType
18 | }
19 |
20 | data CounterType
21 | = Increment
22 | | Decrement
23 |
24 | derive instance genericCounterType :: Generic CounterType _
25 |
26 | instance showCounterType :: Show CounterType where
27 | show = genericShow
28 |
29 | instance readCounterType :: Read CounterType where
30 | read = case _ of
31 | "Increment" -> Just Increment
32 | "Decrement" -> Just Decrement
33 | _ -> Nothing
34 |
35 | mkCounter :: Component Props
36 | mkCounter = React.component "Counter" renderCounter
37 |
38 | renderCounter :: Props -> Render Unit (UseState Int Unit) JSX
39 | renderCounter props = React.do
40 | count /\ setCount <- React.useState' 0
41 | pure do
42 | R.button
43 | { onClick:
44 | Events.handler_ do
45 | let
46 | newCount = case props.counterType of
47 | Increment -> count + 1
48 | Decrement -> count - 1
49 | setCount newCount
50 | props.onClick newCount
51 | , children:
52 | [ R.text (props.label <> " " <> show count) ]
53 | }
54 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages.dhall:
--------------------------------------------------------------------------------
1 | {-
2 | Welcome to your new Dhall package-set!
3 |
4 | Below are instructions for how to edit this file for most use
5 | cases, so that you don't need to know Dhall to use it.
6 |
7 | ## Warning: Don't Move This Top-Level Comment!
8 |
9 | Due to how `dhall format` currently works, this comment's
10 | instructions cannot appear near corresponding sections below
11 | because `dhall format` will delete the comment. However,
12 | it will not delete a top-level comment like this one.
13 |
14 | ## Use Cases
15 |
16 | Most will want to do one or both of these options:
17 | 1. Override/Patch a package's dependency
18 | 2. Add a package not already in the default package set
19 |
20 | This file will continue to work whether you use one or both options.
21 | Instructions for each option are explained below.
22 |
23 | ### Overriding/Patching a package
24 |
25 | Purpose:
26 | - Change a package's dependency to a newer/older release than the
27 | default package set's release
28 | - Use your own modified version of some dependency that may
29 | include new API, changed API, removed API by
30 | using your custom git repo of the library rather than
31 | the package set's repo
32 |
33 | Syntax:
34 | where `entityName` is one of the following:
35 | - dependencies
36 | - repo
37 | - version
38 | -------------------------------
39 | let upstream = --
40 | in upstream
41 | with packageName.entityName = "new value"
42 | -------------------------------
43 |
44 | Example:
45 | -------------------------------
46 | let upstream = --
47 | in upstream
48 | with halogen.version = "master"
49 | with halogen.repo = "https://example.com/path/to/git/repo.git"
50 |
51 | with halogen-vdom.version = "v4.0.0"
52 | -------------------------------
53 |
54 | ### Additions
55 |
56 | Purpose:
57 | - Add packages that aren't already included in the default package set
58 |
59 | Syntax:
60 | where `` is:
61 | - a tag (i.e. "v4.0.0")
62 | - a branch (i.e. "master")
63 | - commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977")
64 | -------------------------------
65 | let upstream = --
66 | in upstream
67 | with new-package-name =
68 | { dependencies =
69 | [ "dependency1"
70 | , "dependency2"
71 | ]
72 | , repo =
73 | "https://example.com/path/to/git/repo.git"
74 | , version =
75 | ""
76 | }
77 | -------------------------------
78 |
79 | Example:
80 | -------------------------------
81 | let upstream = --
82 | in upstream
83 | with benchotron =
84 | { dependencies =
85 | [ "arrays"
86 | , "exists"
87 | , "profunctor"
88 | , "strings"
89 | , "quickcheck"
90 | , "lcg"
91 | , "transformers"
92 | , "foldable-traversable"
93 | , "exceptions"
94 | , "node-fs"
95 | , "node-buffer"
96 | , "node-readline"
97 | , "datetime"
98 | , "now"
99 | ]
100 | , repo =
101 | "https://github.com/hdgarrood/purescript-benchotron.git"
102 | , version =
103 | "v7.0.0"
104 | }
105 | -------------------------------
106 | -}
107 | let upstream =
108 | https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20210118/packages.dhall sha256:a59c5c93a68d5d066f3815a89f398bcf00e130a51cb185b2da29b20e2d8ae115
109 |
110 | in upstream
111 |
--------------------------------------------------------------------------------