Writing Components
Writing React components in Slinky is just like writing them with the native ES6 API.
Slinky components must define the type of their React props and implement a render method. Within the component, you can access the props using the props
variable.
@react class HelloName extends StatelessComponent {
case class Props(name: String)
def render = {
h1(s"Hello ${props.name}")
}
}
To use a component, call the generated companion object's apply method with the parameters defined in props.
div(
HelloName(name = "World")
)
To pass in keys and refs, use withKey
and withRef
:
div(
HelloName(name = "World").withKey("my-hello-name-key").withRef(componentInstance => {
println(componentInstance.state)
})
)
Adding State
To make your component stateful, extend the Component
class (instead of StatelessComponent
) and define your state type and initial state
case class State(buttonPresses: Int)
def initialState = State(0)
Then you can access the state with state
and use setState
to update your component's state. Slinky supports all variants of setState
, such as the one that takes the previous state as a function parameter.
So a full component would look something like this:
@react class MyComponent extends Component {
type Props = Unit // no props
case class State(buttonPresses: Int)
def initialState = State(0)
def render = {
div(
h1(s"Clicked ${state.buttonPresses} times!"),
button(onClick := (_ => {
setState(State(state.buttonPresses + 1))
}))(
"Click Me!"
)
)
}
}
Props and State Type Definitions
When defining the Props
and State
types, Slinky accepts any type definition, so you can define these types as type aliases, case classes, or even regular classes. For example, if we had a component that takes a single String
as its props, we could define it as:
@react class MyComponent extends StatelessComponent {
type Props = String
def render = props
}
where props
is the String
value passed in from a parent.
Typed Props
If you need props with typed parameters, Slinky requires the typed props to be declared as a separate case class with the props specified as a type alias. Currently, this approach does not work with the @react
style, so you will need to declare a ComponentWrapper
instead. For example, a simple component with type parameters for both Props
and State
would look like:
object MyComponent extends ComponentWrapper {
case class TypedProps[T](value: T)
case class TypedState[T](value: T)
type Props = TypedProps[_]
type State = TypedState[_]
class Def(jsProps: js.Object) extends Definition(jsProps) {
override def initialState = TypedState(props.value)
override def render(): ReactElement = {
state.value.toString
}
}
}
// ...
MyComponent(MyComponent.TypedProps(123))
Component Styles
With Slinky 0.2.0, the @react
macro annotation was introduced to reduce the boilerplate involved with creating components. Most examples in the documentation will use the macro annotation, but it is always possible to use the no-annotation API with just a few changes.
If you have a component that looks like this:
@react class MyComponent extends Component {
case class Props(...)
case class State(...)
def initialState = ...
def render = {
...
}
}
The macro annotation converts this code into something like this:
object MyComponent extends ComponentWrapper {
case class Props(...)
case class State(...)
class Def(jsProps: js.Object) extends Definition(jsProps) {
def initialState = ...
def render = {
...
}
}
}
And in the case of a StatelessComponent
something like this:
object MyComponent extends StatelessComponentWrapper {
case class Props(...)
class Def(jsProps: js.Object) extends Definition(jsProps) {
def render = {
...
}
}
}
Lifecycle Methods
Slinky supports all of the React component lifecycle methods, including the next-generation ones from React 16.
class Def(jsProps: js.Object) extends Definition(jsProps) {
override def componentWillMount() = { ... }
override def componentDidMount() = { ... }
override def componentWillReceiveProps(nextProps: Props) = { ... }
override def shouldComponentUpdate(nextProps: Props, nextState: State): Boolean = { ... }
override def componentWillUpdate(nextProps: Props, nextState: State) = { ... }
override def componentDidUpdate(prevProps: Props, prevState: State) = { ... }
override def componentWillUnount() = { ... }
override def componentDidCatch(error: js.Error, info: ErrorBoundaryInfo) = { ... }
}
Static Lifecycle Methods
To use lifecycle methods defined in a static context, override the lifecycle function inside the companion object if using @react
style or directly inside ComponentWrapper
if using the non-annotation style.
Note: for the static lifecycle methods, you must override with a val
, not with a def
.
object MyComponent extends ComponentWrapper {
case class Props(...)
case class State(...)
override val getDerivedStateFromProps = (nextProps: Props, prevState: State) => { ... }
override val getDerivedStateFromError = (error: js.Error) => { ... }
class Def(jsProps: js.Object) extends Definition(jsProps) {
def render = { ... }
}
}
Or with the @react
API:
@react class MyComponent extends Component {
case class Props(...)
case class State(...)
def render = { ... }
}
object MyComponent {
override val getDerivedStateFromProps = (nextProps: Props, prevState: State) => { ... }
override val getDerivedStateFromError = (error: js.Error) => { ... }
}
Snapshot-based Lifecycle
To use the new snapshot based lifecycle from React 16.3, define the Snapshot
type and implement getSnapshotBeforeUpdate
and the variant of componentDidUpdate
.
object MyComponent extends ComponentWrapper {
case class Props(...)
case class State(...)
case class Snapshot(...)
class Def(jsProps: js.Object) extends Definition(jsProps) {
override def getSnapshotBeforeUpdate(prevProps: Props, prevState: State): Snapshot = { ... }
override def componentDidUpdate(prevProps: Props, prevState: State, snapshot: Snapshot) = { ... }
def render = { ... }
}
}
Or with the @react
API:
@react class MyComponent extends Component {
case class Props(...)
case class State(...)
case class Snapshot(...)
override def getSnapshotBeforeUpdate(prevProps: Props, prevState: State): Snapshot = { ... }
override def componentDidUpdate(prevProps: Props, prevState: State, snapshot: Snapshot) = { ... }
def render = { ... }
}