How to use Nativewind in Rescript React Native

Hello, I would like to know how to use Nativewind in Rescript React Native, here is the javascript example:

import React from 'react';
import { withExpoSnack } from 'nativewind';

import { Text, View } from 'react-native';
import { styled } from 'nativewind';

const StyledView = styled(View)
const StyledText = styled(Text)

const App = () => {
  return (
    <StyledView className="flex-1 items-center justify-center">
      <StyledText className="text-slate-800">
        Try editing me! 🎉
      </StyledText>
    </StyledView>
  );
}

// This demo is using a external compiler that will only work in Expo Snacks.
// You may see flashes of unstyled content, this will not occur under normal use!
// Please see the documentation to setup your application
export default withExpoSnack(App);

I try to write a binding for Nativewind.styled:

open ReactNative

type component_type
type forward_ref
@module("nativewind") external styled: component_type => forward_ref = "styled"

let styled_text = styled(ReactNative.View)

@react.component
let app = () => {
  <View>
    <Text> {"Hello World"->React.string} </Text>
    <Expo.StatusBar style=#auto/>
  </View>
}

but it does not work:

How to pass ReactNative.View as a parameter to styled ?

It seem to have to write about deeper bindings

You need to add types for the props the component can take.

module StyledText = {
  ...
  let make = (~className: string?) => ...
}
1 Like

Creating a binding for styled is a bit more complex than that.

Here is an example on how I made a Tamagui styled binding:

// RescriptTamagui.res
type config = {acceptsClassName: bool}

@module("tamagui")
external styled: (React.component<'a>, config) => React.component<'b> = "styled"

and then, when I need to use it, I do something like this:

// StyledTh.res
module Th = {
  let make: React.component<JsxDOM.domProps> = props => {
    <th {...props} />
  }
}

type props = {
  colSpan?: int,
  w?: Style.size,
  maw?: Style.size,
  children: React.element,
}

let make: React.component<props> = RescriptTamagui.styled(Th.make, {acceptsClassName: true})

This example of mine is written before ReScript 11, so it is not as good as it could be.

A good improvement, which utilizes Record Type Spread would be something like this:

// StyledView.res
type props = {
  ...ReactNative.View.props, // ReScript 11 allowed this
  className?: string, // And you can add more props
}

let make: React.component<props> = RescriptNativewind.styled(ReactNative.View.make)

However, I think you won’t be able to spread ReactNative.View.props like that, because ReactNative bindings are using older syntax for defining components (functions with named parameters and @react.component decorator) instead of the new one like:

type props = { ... }
let make: React.component<props> = ({ ... }) => { ... }

so you would probably need to the copy the params or define minimally what you need:

// StyledView.res
type props = {
  children: React.element,
  ..., // other ReactNative.View.props
  className?: string,
}
1 Like

As for the first error…

The variant constructor ReactNative.View can't be found.

shows here because the compiler thinks you are trying to reference a variant here (See Zoo.Dog example) and not a component.
In this case, ReactNative.View is a module and modules can’t be passed to functions*. If you are looking to pass a component to styled then that would be ReactNative.View.make instead.

* - There is a concept called functor (Module Functions), which take a module as a parameter and returns a new module

2 Likes

Thank you very much for everyone’s guidance! Especially @reck753 , who guided me step by step in other groups to make it compile:

open ReactNative

type styledProps = {
  className?: string,
  tw?: string,
  baseClassName?: string,
  baseTw?: string,
  children?: React.element,
}
@module("nativewind")
external styled: React.component<'a> => React.component<styledProps> = "styled"

module StyledText = {
  let make = styled(ReactNative.Text.make)
}

@react.component
let app = () => {
  <View>
    <StyledText className="text-blue-500"> {"Hello"->React.string} </StyledText>
    <Expo.StatusBar style=#auto />
  </View>
}

Note that we must use jsx 4 in rescript.json:

{
...
    "jsx": {
        "version": 4
    },
...
}
2 Likes

Just be careful here with this definition. Whatever you pass to styled will output a component which has props as styledProps.

For example, you might try to do:

module StyledText = {
  let make = styled(ReactNative.Text.make)
}
// And use it as
<StyledText ellipsizeMode=#tail />

Just to encounter error saying that you are missing ellipsizeMode prop in styledProps which on the first thought someone might add directly to styledProps. There is nothing wrong with it, but when you create next styled component it will also have ellipsizeMode as an option compiler allows but it might not be the case:

module StyledView = {
  let make = styled(ReactNative.View.make)
}
// This will work just fine but View doesn't have this prop
<StyledView ellipsizeMode=#tail />

This is an arbitrary example, which shouldn’t break anything, but just feels wrong :smile:.

I would recommend sticking with something like I wrote, where you need to explicitly add props for every styled component.

2 Likes

Moving on, can we write a functor to quickly generate a Styled component?
Like this:

module StyledText = Styled(ReactNative.Text)

What type should Component be?

module Styled = (Component: ???) => {
    let make = styled(Component.make)
}

Well it would be ideal, but I think it is not possible (not sure)…

module Styled = (Component: {
  type params // or type params = 'a
  let make: React.component<props>
}) => {
  type props = {
    ...Component.props, // This can't work as it is not defined as record
    className?: string
  }
  @module("nativewind")
  external styled: React.component<Component.props> => React.component<props> = "styled"

  let make: React.component<props> = styled(Component.make)
}

I think the easiest solution is just to type the styled component manually. You can create a shared type for all of the styled props and just spread it wherever you need:

type styledProps = {
  className?: string,
  // ...
}

@module("nativewind")
  external styled: React.component<'a> => React.component<'b> = "styled"

module StyledText = {
  type props = {
    ...ReactNative.Text.props,
    ...styledProps
  }

  let make: React.component<props> = styled(ReactNative.Text.make)
}
1 Like

Yes, this is the best solution currently

1 Like

For this solution, ReactNative.Text.props is a record with 67 polymorphic parameters, and rescript seems to be unable to write it like this:

As explained above, this is the reason for that error.

ReactNative bindings are defined as:

module Text = {
  @react.component
  let make = (~children: React.element, ~style: Style.t=?, ...) => { ... }
  // Props defined through function named args, like above, produce that polymorphic params error
}

vs a newer, JS/TS familiar syntax:

module Text = {
  type props = {
    children: React.element,
    style?: Style.t,
    ...
  }
  let make: React.component<props> = ({ children, style, }) => { ... }
}

I don’t know if there is a solution for this error, rather than updating ReactNative bindings, but maybe someone else from rescript team could know more and explain further?

1 Like