Skip to content

Commit

Permalink
Fix derivation crash to enable components with type parameters (#287)
Browse files Browse the repository at this point in the history
* Fix derivation crash to enable components with type parameters

* Add docs for typed props
  • Loading branch information
shadaj committed Jul 31, 2019
1 parent 2b9e952 commit 72111eb
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 1 deletion.
25 changes: 25 additions & 0 deletions docs/public/docs/writing-components.md
Expand Up @@ -75,6 +75,31 @@ When defining the `Props` and `State` types, Slinky accepts **any type definitio

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:

```scala
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.

Expand Down
Expand Up @@ -107,7 +107,7 @@ abstract class GenericDeriveImpl(val c: whitebox.Context) { self =>
c.abort(c.enclosingPosition, "Cannot derive a typeclass for a type parameter")
} else if (symbol.isModuleClass && c.typecheck(c.parse(symbol.asClass.module.fullName), silent = true).nonEmpty) {
createModuleTypeclass(tTag.tpe, c.parse(symbol.asClass.module.fullName))
} else if (symbol.isClass && symbol.asClass.isCaseClass) {
} else if (symbol.isClass && symbol.asClass.isCaseClass && symbol.asType.typeParams.size == tTag.tpe.typeArgs.size) {
val constructor = symbol.asClass.primaryConstructor
val companion = symbol.asClass.companion
if (companion != NoSymbol) {
Expand Down
26 changes: 26 additions & 0 deletions tests/src/test/scala/slinky/core/ComponentTest.scala
Expand Up @@ -269,6 +269,22 @@ object DefaultStateParamsComponent extends ComponentWrapper {
}
}

object TypeParamsComponent extends ComponentWrapper {
case class TypedProps[T](abc: T)
case class TypedState[T](abc: T)

type Props = TypedProps[_]
type State = TypedState[_]

class Def(jsProps: js.Object) extends Definition(jsProps) {
override def initialState = TypedState(props.abc)

override def render(): ReactElement = {
state.abc.toString
}
}
}

class ComponentTest extends AsyncFunSuite {
test("setState given function is applied") {
val promise: Promise[Assertion] = Promise()
Expand Down Expand Up @@ -444,4 +460,14 @@ class ComponentTest extends AsyncFunSuite {

promise.future
}

test("Can render a component with type parameters") {
val container = dom.document.createElement("div")
ReactDOM.render(
TypeParamsComponent(TypeParamsComponent.TypedProps(123)),
container
)

assert(container.innerHTML == "123")
}
}

0 comments on commit 72111eb

Please sign in to comment.