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
Refin 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
.getor.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)
.backend(new Backend(_))
.renderS(_.backend.render(_))
.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 = _))Or you can pass in a Dom => (() => Unit) which returns a cleanup function.
var refOrNull: html.Input = null
def render =
<.input(^.untypedRef.withCleanup { n =>
refOrNull = n
() => {
println("Reference is being cleaned up...")
}
})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)whereCis 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)whereCis 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
.valueon 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")
.backend(new Backend(_))
.render(_.backend.render)
.componentDidMount(_.backend.onMount)
.buildSame as with Scala components; except instead of Ref.toScalaComponent use one of these...
Ref.toJsComponent(C)whereCis aJsComponentinstanceRef.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))
)
}