Skip to content

Commit d2fbf81

Browse files
Added support for plain JS props (#9)
1 parent 305a2e2 commit d2fbf81

File tree

5 files changed

+106
-16
lines changed

5 files changed

+106
-16
lines changed

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ val ReactRouterVersion = "^5.2.1"
55
val ReduxVersion = "^3.6.0"
66
val ReduxDevToolsVersion = "^2.13.0"
77

8-
val StaticTagsVersion = "2.7.0"
8+
val StaticTagsVersion = "2.7.1-SNAPSHOT"
99

1010
val commonSettings = Seq(
1111
organization := "org.scommons.shogowada",

core/src/main/scala/io/github/shogowada/scalajs/reactjs/React.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ object React {
2626

2727
def stateFromNative[State](nativeState: js.Dynamic): State = unwrap[State](nativeState)
2828

29-
case class Props[Wrapped](native: js.Dynamic) {
30-
def wrapped: Wrapped = native.wrapped.asInstanceOf[Wrapped]
29+
case class Props[T](native: js.Dynamic) {
30+
def plain: T = native.asInstanceOf[T]
31+
def wrapped: T = native.wrapped.asInstanceOf[T]
3132
def children: ReactElement = native.children.asInstanceOf[ReactElement]
3233
}
3334

core/src/main/scala/io/github/shogowada/scalajs/reactjs/VirtualDOM.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ trait VirtualDOM extends StaticTags {
5555
lazy val key = StringAttributeSpec("key")
5656
lazy val ref = RefAttributeSpec("ref")
5757
lazy val wrapped = WrappedPropsAttributeSpec("wrapped")
58+
lazy val plain = PlainPropsAttributeSpec("plainProps")
5859
}
5960

6061
object VirtualDOMAttributes {
@@ -63,6 +64,11 @@ trait VirtualDOM extends StaticTags {
6364
Attribute(name, wrappedProps.asInstanceOf[js.Any], AS_IS)
6465
}
6566

67+
case class PlainPropsAttributeSpec(name: String) extends AttributeSpec {
68+
def :=(props: js.Object): Attribute[js.Object] =
69+
Attribute(name, props, AS_IS)
70+
}
71+
6672
case class ReactClassAttributeSpec(name: String) extends AttributeSpec {
6773
def :=(value: ReactClass): Attribute[ReactClass] = Attribute(name, value, AS_IS)
6874
}
@@ -124,11 +130,17 @@ trait VirtualDOM extends StaticTags {
124130
htmlNameToReactNameMap.getOrElse(name, name)
125131
}
126132

127-
def toReactAttributes(attributes: Iterable[Attribute[_]]): js.Dictionary[js.Any] =
128-
attributes
129-
.map(attributeToReactAttribute)
130-
.toMap
131-
.toJSDictionary
133+
def toReactAttributes(attributes: Iterable[Attribute[_]]): js.Dictionary[js.Any] = {
134+
val maybeProps = attributes.find(_.name == "plainProps")
135+
maybeProps match {
136+
case Some(propsAttr) => propsAttr.value.asInstanceOf[js.Dictionary[js.Any]]
137+
case None =>
138+
attributes
139+
.map(attributeToReactAttribute)
140+
.toMap
141+
.toJSDictionary
142+
}
143+
}
132144

133145
private def attributeToReactAttribute(attribute: Attribute[_]): (String, js.Any) =
134146
toReactAttributeName(attribute.name) -> attributeValueToReactAttributeValue(attribute)

example/README.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This is just a facade for React, so if you are not already familiar with React,
55
- [How does it replace JSX in Scala?](#how-does-it-replace-jsx-in-scala)
66
- [How to create React classes?](#how-to-create-react-classes)
77
- [What's WrappedProps?](#whats-wrappedprops)
8+
- [How about plain JS props?](#how-about-plain-js-props)
89
- [How about states?](#how-about-states)
910
- [Can I see fully working examples?](#can-i-see-fully-working-examples)
1011

@@ -109,11 +110,56 @@ ReactDOM.render(
109110
)
110111
```
111112

113+
### How about plain JS props?
114+
115+
Alternatively to case classes and `Wrapped` props you
116+
can also use plain JavaScript object as props directly.
117+
118+
However, you have to define it as
119+
[non-native JS trait](http://www.scala-js.org/doc/interoperability/sjs-defined-js-classes.html):
120+
121+
```scala
122+
import io.github.shogowada.scalajs.reactjs.ReactDom
123+
import io.github.shogowada.scalajs.reactjs.VirtualDOM._
124+
125+
import scala.scalajs.js
126+
127+
trait PlainJSProps extends js.Object {
128+
val foo: String
129+
val bar: Int
130+
}
131+
132+
object PlainJSProps {
133+
134+
def apply(foo: String, bar: Int): PlainJSProps = {
135+
js.Dynamic.literal(
136+
foo = foo,
137+
bar = bar
138+
).asInstanceOf[PlainJSProps]
139+
}
140+
}
141+
142+
val reactClass = React.createClass[PlainJSProps, Unit](
143+
(self) =>
144+
<.div()(
145+
s"foo: ${self.props.plain.foo}",
146+
<.br.empty,
147+
s"bar: ${self.props.plain.bar}"
148+
)
149+
)
150+
151+
ReactDOM.render(
152+
<(reactClass)(^.plain := PlainJSProps("foo", 123))(),
153+
mountNode
154+
)
155+
```
156+
112157
Props looks like the following:
113158

114159
```scala
115-
case class Props[Wrapped](native: js.Dynamic) {
116-
def wrapped: Wrapped = native.wrapped.asInstanceOf[Wrapped]
160+
case class Props[T](native: js.Dynamic) {
161+
def plain: T = native.asInstanceOf[T]
162+
def wrapped: T = native.wrapped.asInstanceOf[T]
117163
def children: ReactElement = native.children.asInstanceOf[ReactElement]
118164
}
119165
```

example/helloworld/src/main/scala/io/github/shogowada/scalajs/reactjs/example/helloworld/Main.scala

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,50 @@ import io.github.shogowada.scalajs.reactjs.VirtualDOM._
44
import io.github.shogowada.scalajs.reactjs.{React, ReactDOM}
55
import org.scalajs.dom
66

7+
import scala.scalajs.js
78
import scala.scalajs.js.annotation.JSExport
89

910
object Main {
1011

1112
@JSExport
1213
def main(args: Array[String]): Unit = {
13-
case class WrappedProps(name: String)
1414

15-
val reactClass = React.createClass[WrappedProps, Unit](
16-
(self) => <.div(^.id := "hello-world")(s"Hello, ${self.props.wrapped.name}!")
17-
)
15+
def renderUsingWrappedProps(): Unit = {
1816

19-
val mountNode = dom.document.getElementById("mount-node")
20-
ReactDOM.render(<(reactClass)(^.wrapped := WrappedProps("World"))(), mountNode)
17+
case class WrappedProps(name: String)
18+
19+
val reactClass = React.createClass[WrappedProps, Unit] { self =>
20+
<.div(^.id := "hello-world")(s"Hello, ${self.props.wrapped.name}!")
21+
}
22+
23+
val mountNode = dom.document.getElementById("mount-node")
24+
ReactDOM.render(<(reactClass)(^.wrapped := WrappedProps("World"))(), mountNode)
25+
}
26+
27+
def renderUsingPlainJSProps(): Unit = {
28+
29+
trait PlainJSProps extends js.Object {
30+
val name: String
31+
}
32+
33+
object PlainJSProps {
34+
35+
def apply(name: String): PlainJSProps = {
36+
js.Dynamic.literal(
37+
name = name
38+
).asInstanceOf[PlainJSProps]
39+
}
40+
}
41+
42+
val reactClass = React.createClass[PlainJSProps, Unit] { self =>
43+
<.div(^.id := "hello-world")(s"Hello, ${self.props.plain.name}!")
44+
}
45+
46+
val mountNode = dom.document.getElementById("mount-node")
47+
ReactDOM.render(<(reactClass)(^.plain := PlainJSProps("World"))(), mountNode)
48+
}
49+
50+
renderUsingWrappedProps()
51+
renderUsingPlainJSProps()
2152
}
2253
}

0 commit comments

Comments
 (0)