React allows you to obtain references to real DOM objects that your component creates in its VDOM. See Refs and the DOM for details.
This document describes how to do the same within scalajs-react.
- Refs to VDOM tags
- Refs to Scala components
- Refs to JS components
- Refs to functional components
- Forwarding refs
There are two different types of refs to VDOM. The names evolved incrementally and are now a bit misleading. The types are named "typed" and "untyped". What are the differences?
- "Typed" refs can only be used with vdom of the same type:
if you try to associate a typed ref to an
<input>
with a<select>
you'll get a compilation error.
"Untyped" refs on the other hand can be associated with any vdom type:
if you try to associate an untyped ref to an <input>
with a <select>
you won't experience any compilation
or runtime errors, but the association will be silently ignored.
If the provided vdom is the ref type (or a subtype), then the ref will work as expected.
- Decoupling of control. With "typed" refs, you create the ref and apply it in the same class. With "untyped" refs, you can pass the ref around and have a completely different part of the code wire it up to vdom.
- Create a
Ref
in which to store the DOM reference. Its type should be whatever the DOM's type. Store this inside a component Backend, as aprivate val
. - On your
VdomTag
, call.withRef(ref)
and pass it your reference. - To access the DOM from callbacks, call
.get
or.foreach
.
Example (excerpts from CallbackOption online demo):
object CallbackOptionExample {
class Backend($: BackendScope[Unit, State]) {
// Create the reference
private val outerRef = Ref[html.Element]
// Wire it up from VDOM
def render(s: State) =
OuterDiv.withRef(outerRef)(...)
// Use it
def init: Callback =
outerRef.foreach(_.focus())
}
val Main = ScalaComponent.builder[Unit]("CallbackOption example")
.initialState(initState)
.renderBackend[Backend]
.componentDidMount(_.backend.init)
.build
}
Here is another example: Refs online demo.
There are two ways to use an untyped ref.
The first is to just pass in a Dom => Unit
function:
var refOrNull: html.Input = null
def render =
<.input(^.untypedRef(refOrNull = _))
The second is to create a Ref
object and wire it up like a normal vdom attribute:
val ref = Ref.toVdom[html.Input]
def render =
<.input(^.untypedRef := ref)
- Create a ref. You've got two ways to do this:
- Call
Ref.toScalaComponent(C)
whereC
is the target Scala component. - Call
Ref.toScalaComponent[P, S, B]
and explicitly specify the expected types. SoP
= props,S
= state,B
= backend.
- Store the ref inside a component Backend, as a
private val
. - Use the ref in your render function.
C.withRef(ref)(props)
whereC
is the Scala component.- Alternatively, if you specified the component to create the ref, you can avoid respecifying it again and use
ref.component(props)
- To access the DOM from callbacks, call
.value
on the ref.
Example:
val DoubleComp = ScalaComponent.builder[Int]("Doubler")
.render_P(i => <.p(s"$i + $i = ${i << 1}"))
.build
class Backend {
// Create a mutable reference
private val ref = Ref.toScalaComponent(DoubleComp)
// Wire it up from VDOM
def render = <.div(DoubleComp.withRef(ref)(123))
// Alternatively: <.div(ref.component(123))
// Use it from a lifecycle callback
def onMount: Callback =
ref
.get
.map(_.getDOMNode.asElement.outerHTML)
.flatMapCB(Callback.log("DOM HTML: ", _))
}
val Exapmle = ScalaComponent.builder[Unit]("Example")
.renderBackend[Backend]
.componentDidMount(_.backend.onMount)
.build
Same as with Scala components; except instead of Ref.toScalaComponent
use one of these...
Ref.toJsComponent(C)
whereC
is aJsComponent
instanceRef.toJsComponent[P,S]
whereP
= props,S
= stateRef.toJsComponentWithMountedFacade[P,S,F]
whereP
= props,S
= state,F
= facade with additional methods on mounted component
This is not supported by React. As such, there is no mechanism by which to do so from scalajs-react.
Create a ref-forwarding component by calling React.forwardRef
,
or wrap a JS one as described in the interoperability doc.
Next, use it as a normal component,
specifying .withRef
before providing the props to forward a ref,
or just by providing props if you don't want to specify a ref.
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
object Foo {
val Component = ScalaComponent.builder[Int]("Foo")
.render_P(p => <.h1(s"$p = $p !"))
.build
}
object Bar {
val Component = React.forwardRef.toScalaComponent(Foo.Component)[String](
(label, ref) =>
<.div(label, Foo.Component.withRef(ref)(123))
)
}