Skip to content

Commit

Permalink
doc updates
Browse files Browse the repository at this point in the history
  • Loading branch information
aappddeevv committed Feb 8, 2019
1 parent c5f69a3 commit d51b913
Show file tree
Hide file tree
Showing 24 changed files with 480 additions and 146 deletions.
4 changes: 3 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"sourceMaps": true,
"presets": [
"@babel/preset-react",
"@babel/preset-react",
"@babel/preset-typescript",
[
"@babel/preset-env",
{
Expand All @@ -24,6 +25,7 @@
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-transform-modules-commonjs",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
"@babel/plugin-proposal-function-sent",
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
.cache
.history
.lib/
blah/
.metals/

target/
lib_managed/
Expand All @@ -23,4 +25,4 @@ node_modules/*
.\#*
# do not include assets that are generated during the build
docs/src/main/resources/microsite/static
dist/*
dist/*
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ create a new project.

It's easy to create a component and render it:

```scala
val HelloWorld: SFC0 = () => div("hello world")
reactdom.createAndRenderWithId(HelloWorld, "container")
```

Of course, if every component was so simple you would not need a library. You
can use hooks in your function to add state, effects and other important
facets. The long hand way to create a Component that has mimics the ReasonReact
API for a stateless component is:

```scala
object HelloWorld {
val c = statelessComponent("HelloWorld")
Expand All @@ -58,12 +68,12 @@ object HelloWorld {
reactdom.createAndRenderWithId(Helloworld(), "container")
```

Of course, if every component was so simple, you would just write some js/ts
functions and skip this facade. This facade shines when you introduce
state-holding components since every component has a builtin reducer to manage
state and you use your favorite effects library. See the
The ReasonReact facade is more valuable when you introduce state-holding
components since every component has a builtin reducer to manage state and you
use your favorite effects library. See the
[documentation](http://aappddeevv.github.io/scalajs-reaction) for more details.

You have choices and they are all straightforward.

## Usage
Include the library in your build:
Expand Down
46 changes: 45 additions & 1 deletion docs/src/main/tut/docs/creating.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,46 @@ state. There is no `setState` in this facade to manage state, a stateful
component has a buit-in reducer which is essentially a Finite State Machine
(FSM).

## Stateless Component Approach #1 (Easiest)

## Stateless Component
You can use a simple function definition to define your SFC. There are two type
aliases defined, SFC0 (no args) and SFC1 (1 arg), to create components.

```scala
val component1 = SFC0{ () => div("hello world")
// or val comonent1: SFC0 = () => div("hello world")

trait Props extends js.Object {
var p: js.UndefOr[String] = js.undefined
}

val component2 = SFC1[Prop]{ props =>
div(s"""hello world ${p.getOrElse("no-arg")""")
}
```
You can use a scala object as the parameter as well you will probably want to
memoize the function to ensure that it is only run when the input parameter
changes.
To use these function based components, they must be converted to a
ReactElement. For SFC1, you can use `.toEl(arg)` to explicitly convert it to a
Component for rendering in other Component objects. You can also automatically
convert a tuple:
```scala
(component2, new Props {...})
```
using some automatic conversions (evil!). The reason that SFC0 and SFC1 have a
different approach to creating the final ReactElement is that a raw js function
(which SFC0 and SFC1 are) must go through `ReactJS.createElement(func object,
arg)` and so we need the "component" and the argument separated to plug into he
API.
## Stateless Component Approach #2
This approach uses the full machinery of the component model in this facade.
When we say "defining" a component, we mean that you creating a "template" for a
component instance. When you create a component instance through make or apply,
Expand Down Expand Up @@ -123,6 +161,9 @@ This shortcut works because `c.ops._` imports a method that attaches itself to
want to use any other methods though you'll have to default back to the more
verbose copy approach.
## Stateful Components
There are a few different types of components:
* stateless
Expand Down Expand Up @@ -175,6 +216,9 @@ object MyComponent {
}
```
You can also use SFC components with hooks. See the React Interop section for
information on hooks.
## Attributes
You can define your make function to take either a list of attributes or an
Expand Down
4 changes: 3 additions & 1 deletion docs/src/main/tut/docs/exporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ create your own "mapping" framework if you want to make your mapping process
easier. For example, you could create a set of "Write" typeclasses.

Here's the API. This is all you need to do:

```scala
// ToDoProp is a non-native JS trait
@JSExportTopLevel("ToDo")
Expand All @@ -24,7 +25,8 @@ We mentioned in the previous section that if your make/apply parameter object is
javascript friendly, such as ToDoProps, it's easier to interface. In fact, if
you do it this waya and there is no other processing you need to perform on the properties, you can shrink the above to:

```scala// ToDoProp is a non-native JS trait
```scala
// ToDoProp is a non-native JS trait
@JSExportTopLevel("ToDo")
val exportedToDo = ToDo.wrapScalaForJs(make(_))
```
Expand Down
108 changes: 105 additions & 3 deletions docs/src/main/tut/docs/reactinterop.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,46 @@ def make(...) = c.copy(new methods {

```

## Ref and Key

Ref and key are special properties used by react to assist in accessing the
component instance or managing arrays of values. These properties are ripped out
of props and processed special by reactjs.

In this facade, the scala "props" are appended to the real props under a
specially named key. If you were to add key and prop to your scala prop,s they
would not be seen. So...we need a way to create a scala Component with key and
ref.

To add ref and key you can add them using `createElement` directly or using the
implicit syntax support, `yourComponent.toEl(Some("key"), Some(yourReactRef))`.

The entire ref and key issue can be confusing because in reactjs libraries
imported into scala.js, you can typically define the key and prop in the
imported "props" type definition and reactjs will find them. Since these
elements are created differently than scala components, it just works that way.

## Plain javascript

You can write your components using raw javascript content. For example, as long as you use js semantics you could do the following:

```scala
val jscomponent: js.Function0[ReactNode] = () => {
div("some content")
}
```

But you'll need to create an instance using the raw `ReactJS.createElement` function:

```scala
...some othercomponent render method...
ReactJS.createElement[Null](jscomponent, null)
...more othercomonent render method...
```

This a good alternative to declaring `statelessComponent` objects and their
related render method.

## Latest reactjs features

### Fragment
Expand Down Expand Up @@ -127,7 +167,7 @@ implemented.

You can write your own hooks by defining the dependent hooks just like in the js
examples. Your authored hooks can be defined in a scala function as long as they
are *used* in a function component as descried above:
are *used* in a function component as described above:

```scala
def myHook(key: String): Boolean = {
Expand All @@ -147,8 +187,70 @@ rendering completes. While you can do some of this today, without using
suspense, the user-experience is bit jerky. You throw the promise in the
rendering function.

Note that lazy is not currently supported.
There are a couple of complications.
* You cannot throw a raw promise in scala.js. It's always wrapped up before it
hits the js side.
* You cannot call `import` as a function as its really a keyword in javascript
now.

To use Suspense and React.lazy, you'll need to do one of a few different
approaches:
* Create a .ts/.js file that exports a component that is created using
`React.lazy`. The component needs to be non-scalajs component since scalajs
bundles *everything* together. Import that component as a ReactLazyComponent
and then call `wrapJsForScala` to create a scala component.
* Create a .ts/.js file and include only exports of the form `() =>
import("amodule")`. The import type is DynamicImportThunk and that can used as
an argument to scala's `React.lazy`. You need to define it as a function so
the ts/js Promise is not started immediately. Since you are created deferred
computations, you can define as many of these as you want in a single file and
import them all at once.
* Externalize the entire Suspense and React.lazy machine and just import a
component from a js/ts module in its entirety.

In react 16.8, only lazy loading is supported but any component that throws a
javascript Promise can be used which is actually how React.lazy and dynamic
imports work. Creating a dynamic import creates as javascript Promise that
resolves to the module content.

Since scala cannot throw a raw javascript Promise, you can create a lazy
component by throwing inside a ts/js defined component. The demo shows how this
can be done in ts/js.

As a trick, you can define an scala import module that imports a single function
that throws an object for you.

```javascript
export default throwit(e) { throw e }
```

then import it

```scala
@js.native
@JSImport("throwit", JSImport.Default)
object throwit {
def apply(t: js.Any): Unit = js.native
}
```

Then throw it in your function, such as a SFC

```scala
val sfc = SFC[js.Object]{ _ =>
val x = throwit(..code that creates a promise...)
div("My nodes to display")
}
```

The Suspense element is not currently supported.
But the trick is to have a js.Promise that is smart enough to resolve once the
content has been loaded. This typically uses a backing store of some sort
e.g. cache. If you naviavely try this and its a new promise that never completes
(resolve or fail), you may hang the UI at some point since the promise never
completes.

It's not clear how useful this all is although the lazy loading use case is
interesting in a mixed project.

Because of the Suspense feature, `didCatch` has been removed from scala
Components until the interaction between them is better understood.
1 change: 1 addition & 0 deletions docs/src/main/tut/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
layout: home
---

# What is scalajs-reaction?

scalajs-reaction is a a scala.js facade over reactjs that provides a functional
Expand Down
4 changes: 2 additions & 2 deletions examples/src/main/assets/AddressSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react"
import React from "react"
import { Address } from "./datamodel"
import cx = require("classnames")
import cx from "classnames"

export interface Props {
className?: string | null
Expand Down
4 changes: 2 additions & 2 deletions examples/src/main/assets/LabelAndChild.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react"
import cx = require("classnames")
import React from "react"
import cx from "classnames"

export interface Props {
className?: string | null
Expand Down
33 changes: 33 additions & 0 deletions examples/src/main/assets/SuspenseChild.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react"
import cx from "classnames"

export interface Props {
className?: string | null
label?: string | null
doit?: boolean | null
delay?: number | null
key?: string | null
}

const cache: Record<string, any> = {}

export const SuspenseChild: React.SFC<Props> =
({ key, className, label, children, doit, delay }) => {

if (!cache[key || "fetched"])
throw new Promise((resolve, reject) => {
setTimeout(() => {
cache[key || "fetched"] = true
resolve(true)
}, delay || 7000)
})

return (
<div className={cx("ttg-SuspensChild", className)}>
<div>{label || "SuspenseChild (throws a js Promise explicitly): no label arg provided"}</div>
{children}
</div>
)
}

export default SuspenseChild
29 changes: 29 additions & 0 deletions examples/src/main/assets/SuspenseParent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { lazy, Suspense } from "react"
import cx from "classnames"

export const SuspenseChild = React.lazy(() => import("./SuspenseChild"))
export const X = () => () => import("./SuspenseChild")

console.log("SuspenseChild via ts React.lazy", SuspenseChild)
console.log("X", X)
//console.log("X called", X())

export interface Props {
className?: string | null
label?: string | null
doit?: boolean | null
delay?: number | null
key?: string | null
}

export const SuspenseParent: React.SFC<Props> =
({ className, label, children, doit, delay }) => {
return (
<Suspense fallback={<div>Loading...</div>}>
<div className={cx("ttg-SuspenseParent", className)}>
<div>{label || "SuspenseParent: no label arg provided"}</div>
{children}
</div>
</Suspense>
)
}
2 changes: 1 addition & 1 deletion examples/src/main/assets/addressmanager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Action, ActionCreator } from "redux"
import update = require("immutability-helper")
import update from "immutability-helper"
import { Address } from "./datamodel"

type Id = string
Expand Down
Loading

0 comments on commit d51b913

Please sign in to comment.