shadaj / slinky

Write Scala.js React apps just like you would in ES6
https://slinky.dev
MIT License
655 stars 57 forks source link

How to represent children for ExternalComponent #237

Open nadenf opened 5 years ago

nadenf commented 5 years ago

Would be useful to have documentation for handling children. For example in this component:

https://atlaskit.atlassian.com/packages/core/form/docs/form

shadaj commented 5 years ago

That's a good idea! Slinky does support the children parameter, which can be defined in the case class Props as children: ReactElement*. When combined with the @react annotation, Slinky will automatically move the children parameter into a curried parameter to match the tags API.

nadenf commented 5 years ago

If we take this example. It will not compile since you can't have default arguments with a *-parameter. However I am finding optional arguments to be very common amongst Javascript libraries.

@JSImport("@atlaskit/tag-group", "TagGroup")
@js.native
object ReactTagGroup extends js.Object

@react object TagGroup extends ExternalComponent {
  case class Props(alignment: Option[String] = None, children: ReactElement*)
  override val component = ReactTagGroup
}

Alternatively could you detect if I make it curried myself like this and not do anything. Currently I get an error, "could not find implicit value for parameter pw: slinky.core.ExternalPropsWriterProvider"

  case class Props(alignment: Option[String] = None)(children: ReactElement*)
evbo commented 3 years ago

UPDATE: this solution now permits default arguments and still curries the children

@shadaj @nadenf correct me if I'm wrong, but in the example @nadenf linked, for this to work children must be a function, with formProps as the argument.

I got a very basic example working to demonstrate, but hopefully there's a cleaner solution:

object Form {
  // here are the default args and curried children - gotta love the plain old Object approach
  def apply(onSubmit: () => Unit, disabled: Boolean = false)(children: FormState => ReactElement): ReactElement =
    AtlaskitForm(onSubmit, disabled, children)
}

@JSImport("@atlaskit/form", JSImport.Default)
@js.native
object AtlaskitFormLibrary extends js.Object

@react object AtlaskitForm extends ExternalComponent {
  // see my comment below - children must be a function and cannot be just a functional ReactElement like you might expect
  case class Props(onSubmit: () => Unit, isDisabled: Boolean, children: FormState => ReactElement)
  override val component = AtlaskitFormLibrary
}

@js.native
trait FormState extends js.Object {
  val formProps: FormProps = js.native
  val disabled: Boolean = js.native
  val dirty: Boolean = js.native
  val submitting: Boolean = js.native
  val getValues: () => js.Object = js.native
}

@js.native
trait FormProps extends js.Object {
  val ref: ReactRef[String] = js.native
  val onSubmit: js.Function1[SyntheticEvent[html.Form, Event], Unit] = js.native
}

So, for example this can be used like:

Form(
  onSubmit _,
)(
  (formState: FormState) => {
    form(
      ref := formState.formProps.ref,
      onSubmit := (formState.formProps.onSubmit(_))
    )(
      Button(Primary, Submit)("")
    )
  }
)

I borrowed heavily from here.

jedahu commented 3 years ago

This PR https://github.com/shadaj/slinky/pull/478 provides a workaround for the curried optional arguments problem @nadenf described here: https://github.com/shadaj/slinky/issues/237#issuecomment-472819612. Likely not relevant to you @evbo.

evbo commented 3 years ago

@jedahu I updated my earlier comment with an approach for currying children and supporting default args. A little extra code, but supports the general case of children being a function too

evbo commented 2 years ago

One other side effect of the children: ReactElement* argument is it forces you to specify even if you have no children:

MyComponent(id = 1)() // without "()" you will get error missing curried argument "children"

Would be nice if behavior matched that of, say, div, where the curried children are optional:

div() // no children specified and that's okay!