Skip to content

Commit

Permalink
Update documentations
Browse files Browse the repository at this point in the history
Close #26
  • Loading branch information
shogowada committed Apr 15, 2017
1 parent ed04e67 commit 7cb4159
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 348 deletions.
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Develop React applications with Scala. It is compatible with Scala 2.12 and Scala.js 0.6.14.

Facade for react-router and react-redux are optionally available too.

## Quick Look

```scala
Expand Down Expand Up @@ -50,15 +52,8 @@ ReactDOM.render(HelloWorld("World"), mountNode)

## Examples

- [TODO App](./example/todo-app)
- [Routing](./router)
- [Redux](./redux)
- [Basics](./example)
- [TODO App](./example/todo-app/src/main/scala/io/github/shogowada/scalajs/reactjs/example/todoapp/Main.scala)
- [Routing](./example/routing/src/main/scala/io/github/shogowada/scalajs/reactjs/example/routing/Main.scala)
- [Redux](./example/todo-app-redux/src/main/scala/io/github/shogowada/scalajs/reactjs/example/todoappredux)
- [I don't like `<` and `^`. How can I change them?](./example/custom-virtual-dom)
- [All Examples](./example)

## API References

- [react facade](./core)
- [react-router facade](./router)
- [react-router-dom facade](./router-dom)
- [react-redux facade](./redux)
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ publishArtifact := false
val commonSettings = Seq(
organization := "io.github.shogowada",
name := "scalajs-reactjs",
version := "0.7.2-SNAPSHOT",
version := "0.8.0",
licenses := Seq("MIT" -> url("https://opensource.org/licenses/MIT")),
homepage := Some(url("https://github.com/shogowada/scalajs-reactjs")),
scalaVersion := "2.12.1",
Expand Down
109 changes: 106 additions & 3 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,108 @@
# Examples
# Basics

All of those examples are tested by Selenium. You can find Selenium test cases under [test](./test) folder.
## How to replace JSX in Scala?

scalajs-reactjs solely relies on those test cases to test its behaviors. In other words, the library should have example for all the behaviors it provides, and they all should be tested.
To create elements in Scala, import `VirtualDOM._`. `VirtualDOM` is an extended [Static Tags](https://github.com/shogowada/statictags).

`VirtualDOM` is made of three parts: tag, attributes (a.k.a. props), and children.

For example, this code

```scala
<.div(^.id := "hello-world")("Hello, World!")
```

is equivalent of the following:

```html
<div id="hello-world">Hello, World!</div>
```

You can use as many as attributes and children you want.

## How to create React components?

To create React components, extend `ReactClassSpec[WrappedProps, State]` or one of its sub classes.

You have four options:

- `ReactClassSpec[WrappedProps, State]`
- This is the parent of them all. You can have both custom props and state.
- `StatelessReactClassSpec[WrappedProps]`
- You cannot have state.
- `PropslessReactClassSpec[State]`
- You cannot have custom props.
- `StaticReactClassSpec`
- You cannot have both custom props and state.

To render React components, use `<(/* React component */)` to make it an element. You can pass attributes and children like regular elements.

```scala
import io.github.shogowada.scalajs.reactjs.VirtualDOM._

class MyComponent extends ReactClassSpec[WrappedProps, State] {
// ...
}

ReactDOM.render(
<(new MyComponent())(/* attributes (a.k.a. props) */)(/* children */),
mountNode
)
```

### What's WrappedProps?

While many want to use case classes as props, React requires props to be a plain JavaScript object. So, to use case classes, we need to wrap the case class in another property. In this facade, we wrap it in "wrapped" property.

```scala
case class WrappedProps(foo: String, bar: Int)

class MyComponent extends StatelessReactClassSpec[WrappedProps] {
override def render(): ReactElement =
<.div()(
s"foo: ${props.wrapped.foo}",
<.br.empty,
s"bar: ${props.wrapped.bar}"
)
}

ReactDOM.render(
<(new MyComponent())(^.wrapped := WrappedProps("foo", 123))(),
mountNode
)
```

Props looks like the following:

```scala
case class Props[WrappedProps](native: js.Dynamic) {
def wrapped: WrappedProps = // ...
def children: ReactElement = // ...
}
```

### How about states?

States are wrapped and unwrapped automatically, so you don't need to do `state.wrapped`.

```scala
case class State(text: String)

class MyComponent extends PropslessReactClassSpec[State] {
override def getInitialState(): State = State(text = "")

override def render(): ReactElement =
<.div()(
<.input(
^.placeholder := "Type something here",
^.value := state.text,
^.onChange := onChange
)()
)

val onChange = (event: InputFormSyntheticEvent) => {
val newText = event.target.value
setState(_.copy(text = newText))
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ import org.scalajs.dom

import scala.scalajs.js.JSApp

/*
* If you are not yet familiar with react-router, check it our first:
*
* - https://reacttraining.com/react-router/web/guides/quick-start
*
* This is just a facade for the react-router, so if you know how to use it already,
* you should be able to understand how to use this facade.
*
* Import the following to access router components (e.g. Route):
*
* - import io.github.shogowada.scalajs.reactjs.VirtualDOM._
* - import io.github.shogowada.scalajs.reactjs.router.dom.RouterDOM._
* */

object Main extends JSApp {
override def main(): Unit = {
/* Import the following to access router components:
*
* - import io.github.shogowada.scalajs.reactjs.VirtualDOM._
* - import io.github.shogowada.scalajs.reactjs.router.dom.RouterDOM._
*
* */
val mountNode = dom.document.getElementById("mount-node")
ReactDOM.render(
<.HashRouter()(
Expand All @@ -33,8 +41,7 @@ object App {
def apply() = WithRouter(new App())
}

class App extends StaticReactClassSpec
with RouterProps {
class App extends StaticReactClassSpec {
override def render() =
<.div()(
<.h1()("React Router Tutorial"),
Expand All @@ -48,13 +55,14 @@ class App extends StaticReactClassSpec
).asReactElement
}

object Links extends RouterProps {
object Links {
def apply(): ReactElement = <.nav()(
<.li()(<.Link(^.to := "/about")("About")),
<.li()(<.Link(^.to := "/repos")("Repos"))
)
}

// Extend RouterProps or import RouterProps._ to access router specific props like props.match or props.history.
object RouterApiButtons extends RouterProps {
def apply(props: Props[_]): ReactElement = <.div()(
<.button(
Expand Down Expand Up @@ -100,6 +108,7 @@ object Repo {

class Repo extends StaticReactClassSpec
with RouterProps {
// Params has type of js.Dictionary[String].
private def id: String = props.`match`.params("id")

override def render() = <.div(^.id := s"repo-${id}")(s"Repo ${id}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object VisibilityFilters {
* You can define actions as case classes.
* You don't need to define "type" field; it will automatically use the class name as its "type".
* For example, the AddTodo action will have "AddTodo" as a type.
* Make sure your actions extends "Action" trait.
* Make sure actions extends "Action" trait.
* */

case class AddTodo(id: Int = AddTodo.nextId, text: String) extends Action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,14 @@ import io.github.shogowada.scalajs.reactjs.redux.ReactRedux
import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch

/*
* You can create container components by using ReactRedux.connect method.
* The method has three signatures:
* You can create container components by using ReactRedux.connectAdvanced method.
*
* - ReactRedux.connect(dispatch, state, ownProps)
* - ReactRedux.connect(dispatch, state)
* - ReactRedux.connect(dispatch)
*
* It shows example for all of them.
*
* Container components are higher-order components.
* This means you need to pass another component to it to create a real component.
*
* You can pass either one of the following to create a real component:
* You can pass either one of the following to create a component:
*
* - Render function of type (props: Props[WrappedProps]) => ReactElement
* - Presentational component of type ReactClassSpec
*
* It shows example for all of them.
* It shows example for both.
* */

object ContainerComponents {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,26 @@ import org.scalajs.dom

import scala.scalajs.js.JSApp

/*
* If you are not familiar with react-redux yet, please check it out first:
*
* - http://redux.js.org/docs/basics/UsageWithReact.html
* */

object Main extends JSApp {
override def main(): Unit = {
val mountNode = dom.document.getElementById("mount-node")

// Use Redux.createStore(reducer) to create a store.
val store = Redux.createStore(Reducer.reduce)

/*
* Use <.Provider(store)(children) to create a virtual DOM for your Redux containers.
* Note that you need to import the following to access the Provider:
*
* - import io.github.shogowada.scalajs.reactjs.VirtualDOM._
* - import io.github.shogowada.scalajs.reactjs.redux.ReactRedux._
* */
* Import the following to access the Provider:
*
* - import io.github.shogowada.scalajs.reactjs.VirtualDOM._
* - import io.github.shogowada.scalajs.reactjs.redux.ReactRedux._
* */
ReactDOM.render(
<.Provider(store = store)(
<.Provider(^.store := store)(
App()
),
mountNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ object Footer {
<.p()(
"Show: ",
<.LinkContainerComponent(
// Make sure to wrap own props with "wrapped" property
^.wrapped := LinkContainerComponentOwnProps("SHOW_ALL")
)(
"All"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.github.shogowada.scalajs.reactjs.redux.Action

/*
* Reducer function has a signature of (Option[State], Action) => State.
* If the state is absent, you need to return an initial state.
* If the state is absent, return an initial state.
* */
object Reducer {
def reduce(maybeState: Option[State], action: Action): State =
Expand All @@ -13,32 +13,30 @@ object Reducer {
visibilityFilter = reduceVisibilityFilter(maybeState.map(_.visibilityFilter), action)
)

private def reduceTodos(maybeTodos: Option[Seq[TodoItem]], action: Action): Seq[TodoItem] =
maybeTodos.map(todos =>
action match {
case action: AddTodo => {
todos :+ TodoItem(
id = action.id,
text = action.text,
completed = false
)
}
case action: ToggleTodo => {
todos.map(todo => if (todo.id == action.id) {
todo.copy(completed = !todo.completed)
} else {
todo
})
}
case _ => todos
private def reduceTodos(maybeTodos: Option[Seq[TodoItem]], action: Action): Seq[TodoItem] = {
val todos = maybeTodos.getOrElse(Seq.empty)
action match {
case action: AddTodo => {
todos :+ TodoItem(
id = action.id,
text = action.text,
completed = false
)
}
).getOrElse(Seq())
case action: ToggleTodo => {
todos.map(todo => if (todo.id == action.id) {
todo.copy(completed = !todo.completed)
} else {
todo
})
}
case _ => todos
}
}

private def reduceVisibilityFilter(maybeVisibilityFilter: Option[String], action: Action): String =
maybeVisibilityFilter.map(visibilityFilter =>
action match {
case action: SetVisibilityFilter => action.filter
case _ => visibilityFilter
}
).getOrElse(VisibilityFilters.ShowAll)
action match {
case action: SetVisibilityFilter => action.filter
case _ => maybeVisibilityFilter.getOrElse(VisibilityFilters.ShowAll)
}
}
Loading

0 comments on commit 7cb4159

Please sign in to comment.