question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Recursively creating themed component doesn't work

See original GitHub issue

What

There’s a bug with createThemedComponent (and thus styled), where passing a component made by dripsy to it doesn’t work.

import { styled, View } from 'dripsy'
import * as Native from 'react-native'

const Container = styled(View) // 🚨error
const Wrapper = styled(Native.View) // 👍 works

Same goes for createThemedComponent:

import { createThemedComponent, View } from 'dripsy'
import * as Native from 'react-native'

const Container = createThemedComponent(View) // 🚨error
const Wrapper = createThemedComponent(Native.View) // 👍 works

Why

First, this bug needs to be fixed for the custom pragma from #31 to get merged.

Second, I’ve really enjoyed using the styled function from the fresnel-2 branch in my real-world app. styled is a wrapper around createThemedComponent with a cooler name and syntax.

While using the sx prop is the central API, I would like to treat styled syntax as a first-class citizen, too.

import { styled } from 'dripsy'

const Container = styled(View)({
  maxWidth: 10
})

I’ve enjoyed the API of naming UI elements outside of the component scope. It prevents cluttering the return statement with styles.

With styled-components, this was pretty annoying, because I had to pass a function to my styles, redeclare prop types, and it was just a mess. But with dripsy, it’s easy; I can still pass an sx prop to the component returned by styled if it needs render-specific variables in its styles, like so:

const Container = styled(View)({
  maxWidth: 10
})

const Box = ({ height }) => <Container sx={{ height }} />

How

I’m not fully sure why this bug is exists, but I could see issues with recursively wrapping components in HOCs.

I think an easy solution would be to add something like a static isDripsyComponent field to any component returned by createThemedComponent. Then, in createThemedComponent, we check if it already is a dripsy component. If it is, we just return the component itself and forward the props down – nothing special.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:17 (17 by maintainers)

github_iconTop GitHub Comments

1reaction
nandorojocommented, Sep 28, 2020

For whatever reason, I cannot get the TS suggestions for SxProps['sx'] type to work when it’s a return type of a function.

// types.ts
export type ThemedOptions<T> = {
  defaultStyle?:
    | SxProps['sx'] // this works fine with intellisense 
    | ((props: T) => SxProps['sx']) // no intellisense here...
  themeKey?: string
  defaultVariant?: string
}

I’ve also tried using SxStyleProp and ThemeUIObject, but no luck yet. When I use my own custom type, it works fine.

Here’s the updated createThemedComponent file:

/* eslint-disable @typescript-eslint/ban-ts-ignore */
import React, { ComponentType, ComponentProps, useMemo } from 'react'
import { ThemedOptions, StyledProps } from './types'
import { useThemeUI } from '@theme-ui/core'
import { useBreakpointIndex, mapPropsToStyledComponent } from '.'
import { SSRComponent } from './ssr-component'
import { Platform } from 'react-native'

type Props<P> = Omit<StyledProps<P>, 'theme' | 'breakpoint'>

export function createThemedComponent<P, T>(
  Component: ComponentType<P>,
  { defaultStyle: baseStyle, ...options }: ThemedOptions<T> = {}
) {
  // without styled-components...
  const WrappedComponent = React.forwardRef<
    typeof Component,
    Props<P> & ComponentProps<typeof Component> & T
  >(function Wrapped(prop, ref) {
    const {
      sx,
      as: SuperComponent,
      variant,
      style,
      webContainerSx,
      themeKey = options.themeKey,
      ...props
    } = prop
    const defaultStyle =
      typeof baseStyle === 'function' ? baseStyle(prop) : baseStyle

    const { theme } = useThemeUI()
    const breakpoint = useBreakpointIndex({
      __shouldDisableListenerOnWeb: true,
    })
    // const ssr = useIsSSR()
    // change/remove this later maybe
    const ssr = Platform.OS === 'web'

    const { responsiveSSRStyles, ...styles } = useMemo(
      () =>
        mapPropsToStyledComponent(
          {
            theme,
            breakpoint: Platform.OS === 'web' && ssr ? undefined : breakpoint,
            variant,
            sx,
            style,
          },
          {
            ...options,
            themeKey,
            defaultStyle,
          }
        )(),
      [breakpoint, defaultStyle, ssr, style, sx, theme, themeKey, variant]
    )

    const TheComponent = SuperComponent || Component

    if (Platform.OS === 'web' && ssr && !!responsiveSSRStyles?.length) {
      return (
        <SSRComponent
          {...props}
          Component={TheComponent as React.ComponentType<unknown>}
          responsiveStyles={responsiveSSRStyles}
          style={styles}
          ref={ref}
          containerStyles={
            webContainerSx as ComponentProps<
              typeof SSRComponent
            >['containerStyles']
          }
        />
      )
    }

    return (
      <TheComponent {...((props as unknown) as P)} ref={ref} style={styles} />
    )
  })

  WrappedComponent.displayName = `Themed.${Component.displayName ??
    'NoNameComponent'}`

  return React.memo(WrappedComponent)
}

Once I get this fixed I’ll clean up the code and remove old stuff that went unused. The naming is a bit confusing in a number of places, too.

@cmaycumber I haven’t updated to put your explicit return type changes yet, I can add that once I figure this out.

1reaction
nandorojocommented, Sep 27, 2020

Got it, I’ll try this out. ComponentType probably makes more sense. I see that @expo/html-elements is using React.PropsWithChildren, I haven’t used that type before.

I’m also working on figuring out a way to get React.memo to work without using @ts-ignore, such that it forwards the correct details. Something like this might work to forward the props generic:

const typedMemo: <T>(c: T) => T = React.memo
return typedMemo(WrappedComponent)

But I’m not sure if it works with forwarding refs too yet.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Calling a component recursively in React.js
The call to a component recursively must be driven by recursive data, which will define the tree of nested calls that will be...
Read more >
connect not working in recursive components - Stack Overflow
1 Answer 1 ... You are re-using the Base component, not the connected one. ... const ConnectedChild = connect(mapStateToProps, mapDispatchToProps)( ...
Read more >
Recursive Component In Angular - Medium
This blog will explain to you how to utilize Angular Component in itself recursively to make a tree-structure that will render a nested...
Read more >
Playing With Recursive Components In Angular 6.1.10
First, let's create a TreeComponent. This component will represent the ingress to the Tree widget and will provide Input and Output hooks for ......
Read more >
InternalError: too much recursion - JavaScript - MDN Web Docs
To avoid this problem, make sure that the property being assigned to inside the setter function is different from the one that initially...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found