Docs

Write React apps in Scala just like you would in ES6

Get Started

Just like ES6

Slinky has a strong focus on mirroring the ES6 API. This means that any documentation or examples for ES6 React can be easily applied to your Scala code.

There are no new patterns involved with using Slinky. Just write React apps like you would in any other language!

Complete Interop

Slinky provides straightforward APIs for using external components. Simply define the component's properties using standard Scala types and you're good to go!

In addition, Slinky components can be used from JavaScript code, thanks to a built in Scala to JS mappings. This means that your favorite libraries like React Router work out of the box with Slinky!

First-Class Dev Experience

Writing web applications with Scala doesn't have to feel like a degraded development experience. Slinky comes ready with full integration with familiar tools like Webpack and React DevTools.

Slinky also comes with built-in support for hot-loading via Webpack, allowing you to make your code-test-repeat flow even faster!


A Simple Component

Just like React, Slinky components implement a render()method that returns what to display based on the input data, but also define a Props type that defines the input data shape. Slinky comes with a tags API for constructing HTML trees that gives a similar experience to other Scala libraries like ScalaTags but also includes additional type-safety requirements. Input data that is passed into the component can be accessed by render() via props

SCALA CODE
@react class HelloMessage extends StatelessComponent {
  case class Props(name: String)

  override def render(): ReactElement = {
    div("Hello ", props.name)
  }
}

ReactDOM.render(
  HelloMessage(name = "Taylor"),
  mountNode
)
RESULT
Hello Taylor

A Stateful Component

Slinky components, just like React components, can maintain internal state data (accessed with state). When a component's state data changes after an invocation of setState, the rendered markup will be update by re-invoking render().

SCALA CODE
@react class Timer extends Component {
  type Props = Unit
  case class State(seconds: Int)

  override def initialState = State(seconds = 0)

  def tick(): Unit = {
    setState(prevState =>
      State(prevState.seconds + 1))
  }

  private var interval = -1

  override def componentDidMount(): Unit = {
    interval = setInterval(() => tick(), 1000)
  }

  override def componentWillUnmount(): Unit = {
    clearInterval(interval)
  }

  override def render(): ReactElement = {
    div(
      "Seconds: ", state.seconds.toString
    )
  }
}

ReactDOM.render(Timer(), mountNode)
RESULT
Seconds: 0

An Application

Using props and state, we can put together a small Todo application. This example uses state to track the current list of items as well as the text that the user has entered.

SCALA CODE
case class TodoItem(text: String, id: Long)

@react class TodoApp extends Component {
  type Props = Unit
  case class State(items: Seq[TodoItem], text: String)

  override def initialState = State(Seq.empty, "")

  def handleChange(e: SyntheticEvent[html.Input, Event]): Unit = {
    val eventValue = e.target.value
    setState(_.copy(text = eventValue))
  }

  def handleSubmit(e: SyntheticEvent[html.Form, Event]): Unit = {
    e.preventDefault()

    if (state.text.nonEmpty) {
      val newItem = TodoItem(
        text = state.text,
        id = Date.now().toLong
      )

      setState(prevState => {
        State(
          items = prevState.items :+ newItem,
          text = ""
        )
      })
    }
  }

  override def render() = {
    div(
      h3("TODO"),
      TodoList(items = state.items),
      form(onSubmit := (handleSubmit(_)))(
        input(
          onChange := (handleChange(_)),
          value := state.text
        ),
        button(s"Add #${state.items.size + 1}")
      )
    )
  }
}

@react class TodoList extends StatelessComponent {
  case class Props(items: Seq[TodoItem])

  override def render() = {
    ul(
      props.items.map { item =>
        li(key := item.id.toString)(item.text)
      }
    )
  }
}

ReactDOM.render(TodoApp(), mountNode)
RESULT

TODO