Giter Club home page Giter Club logo

Comments (17)

nandorojo avatar nandorojo commented on September 23, 2024 1

I've tried to make some work-arounds but still haven't been able to debug this.

from dripsy.

cmaycumber avatar cmaycumber commented on September 23, 2024 1
export function createThemedComponent<P>(
	Component: ComponentType<P>,
	options: ThemedOptions = {}
): React.FC<P & Props<P>> {

...

// @ts-ignore
return React.memo(WrappedComponent)

This might be a decent workaround if the types are the only issue.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024 1

I'll test that out.

It actually is more than a TS error, for some reason the actual renders don't work properly.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024 1

Okay I think it actually is just a TS error for createThemedComponent, and that it's actually breaking for styled. I need to keep testing to be sure. If it is, @cmaycumber's work-around should do it:

export function createThemedComponent<P>(
	Component: ComponentType<P>,
	options: ThemedOptions = {}
): React.FC<P & Props<P>> {

...

// @ts-ignore
return React.memo(WrappedComponent)

In any case, I should probably re-write styled, either to 1) match createThemedComponent from scratch, or 2) to simply return createThemedComponent. Option 1 would allow us to pass a pure function to determine styles, option 2 would only allow a plain sx object.

The simplest rewrite would look like this:

export function styled<P>(
  Component: ComponentType<P>,
  {
    themeKey,
    defaultVariant,
  }: { themeKey?: string; defaultVariant?: string } = {}
) {
  return (sx: Required<SxProps>['sx']) => {
    return createThemedComponent(Component, {
      defaultStyle: sx,
      themeKey,
      defaultVariant,
    })
  }
}

Which would allow this usage:

import { View, styled } from 'dripsy'

// plain objects only
const Box = styled(View)({
  pb: 2
})

<Box sx={{ pb: 3 }} />

...but it would not allow this usage:

import { View, styled } from 'dripsy'

// pure function to determine styles
const Box = styled<{ pb: number }>(View)(props => ({
  pb: props.pb
}))

<Box pb={3} />

The passing around of props and refs gets pretty messy in React with this many HOCs. It seems like the current (and admittedly messy) implementation of styled is breaking somewhere along the way of recursively forwarding props and refs (or so I would guess).

from dripsy.

cmaycumber avatar cmaycumber commented on September 23, 2024 1

Another temporary workaround building off my previous one that fixes @expo/html-elements would add an additional optional child prop:

export function createThemedComponent<P>(
	Component: ComponentType<P>,
	options: ThemedOptions = {}
): React.FC<P & Props<P> & { children?: React.ReactNode }> {

...

// @ts-ignore
return React.memo(WrappedComponent)

Another thing to note React.FC can be replaced with ComponentType which may be more accurate because it shows a React.ForwardRefExoticComponent as the return type.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024 1

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.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024 1

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.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024

There are also issues with merging the default style and a new sx prop sometimes.

from dripsy.

cmaycumber avatar cmaycumber commented on September 23, 2024

I'll try to take a look at this as well.

from dripsy.

cmaycumber avatar cmaycumber commented on September 23, 2024

Adding this: React.FC<P & Props<P>> as an explicit return type on the HOC seems to remove the TS error from the client and shifts it to the React.memo(WrappedComponent).

Not sure how important this is yet but I thought it was an interesting observation.

from dripsy.

cmaycumber avatar cmaycumber commented on September 23, 2024

Does the styled component currently support passing props?

I can think off the top of my head plenty of situations where passing a prop might be needed for determining the style even outside of the scope of theme-related styles.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024

That usage is currently commented out. I got a lot of weird errors/edge cases importing the SxProps type from theme-ui when originally trying to make it, so I left it for later. SxProps seems like a pretty unusual type.

I came up with this:

import React, { ComponentProps, ComponentType } from 'react'
import { SxProps } from '@theme-ui/core'
import { createThemedComponent } from './create-themed-component'
import { StyledProps } from './types'

type Props<P> = Omit<StyledProps<P>, 'theme' | 'breakpoint'>
export function styled<P>(
  Component: ComponentType<P>,
  { themeKey, defaultVariant }: { themeKey?: string; defaultVariant?: string } = {}
) {
  return (sx?: ((props: P) => Required<SxProps>['sx']) | Partial<Required<Required<SxProps>['sx']>>) => {
    const Styled = React.forwardRef<typeof Component, Props<P> & ComponentProps<typeof Component>>(function Styled(
      props,
      ref
    ) {
      const Themed = createThemedComponent(Component, {
        defaultStyle: typeof sx === 'function' ? sx(props) : sx,
        themeKey,
        defaultVariant
      })
      // @ts-ignore
      return <Themed {...(props as unknown) as P} ref={ref} />
    })
    Styled.displayName = `DripsyStyled.${Component.displayName}`

    return Styled
  }
}

But that actually doesn't work perfectly either, because Component then needs to have props P declared in its prop types, when in reality we don't need it to if it's just for the sake of styling.

(For whatever reason, I wasn't getting typed suggestions until I added the many Partial and Required type helpers.)

The problems are 1) why doesn't styled allow recursive creation, and 2) how do we allow a pure function as a style argument with type suggestions.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024

Recursively calling forwardRef looks like it's causing the TS errors from createThemedComponent.

All the @expo/html-elements also forward ref and those are the only primitive components we have that show TS errors when you use them.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024

Not sure where the best place to discuss styled is, but as I mentioned on #40, I had the idea to move the pure function logic into createThemedComponent.

It seems like a refactoring like this would work (oversimplified for demonstration):

function createThemedComponent<P, T>(Component: React.ComponentType<P>, defaultStyle: SxProp | ((t: T) => SxProp)) {
  return function FinalComponent(props: P & T) {
    const style = typeof defaultStyle === "function" ? defaultStyle(props) : defaultStyle;

    return <Component {...props} style={style} />;
  };
}

I made a code sandbox to demonstrate this.

You can use it like so:

/**
 * Here's a random test component
 */
const H1 = ({ i }: { i: boolean }) => <h1>{`${i}`}</h1>;

const Created = createThemedComponent(H1, { padding: 1 });
const EnhancedCreated = createThemedComponent(H1, (props: { p: number }) => ({
  padding: props.p
}));

This gives us all the proper types:

Screen Shot 2020-09-27 at 6 37 23 PM

Screen Shot 2020-09-27 at 6 37 01 PM

Screen Shot 2020-09-27 at 6 36 56 PM

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024

The TS errors seen in the H1 and other elements is fixed in 1.2.1. I used @cmaycumber's suggestion of explicitly defining the component's return type.

Recursively using styled or createThemedComponent still seems like it isn't working. I'll be doing some extensive testing of this.

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024

I just randomly had an idea. Could solving this issue be as simple as not theming the style prop?

from dripsy.

nandorojo avatar nandorojo commented on September 23, 2024

I think I fixed this in v2.1.0.

from dripsy.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.