Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove props from placeholder div #29

Open
bfin opened this issue Sep 25, 2019 · 9 comments
Open

Remove props from placeholder div #29

bfin opened this issue Sep 25, 2019 · 9 comments

Comments

@bfin
Copy link

bfin commented Sep 25, 2019

Hi!

I'm passing props to my LoadableComponent elements, but those props are also passed to the placeholder div elements so I'm getting some annoying warnings:

Warning: React does not recognize the `myCustomProp` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom attribute, 
spell it as lowercase `myCustomProp` instead. If you accidentally passed it
from a parent component, remove it from the DOM element.

Should be easy enough to remove these warnings by deleting lines 80 and 96 in createLoadableVisibilityComponent.js, but the better way would probably be to extract the className prop and continue to apply it to the placeholder div (i.e., {...props} -> className={props.className}) in case anyone is relying on that functionality (also in your tests, I believe).

Larger code block for context:

    if (LoadingComponent || props.fallback) {
      return (
        <div
          style={{
            display: "inline-block",
            minHeight: "1px",
            minWidth: "1px"
          }}
          className={props.className}  #CHANGE: previously {...props}
          ref={visibilityElementRef}
        >
          {LoadingComponent
            ? React.createElement(LoadingComponent, {
                isLoading: true,
                ...props
              })
            : props.fallback}
        </div>
      );
    }

    return (
      <div
        style={{ display: "inline-block", minHeight: "1px", minWidth: "1px" }}
        className={props.className}  #CHANGE: previously {...props}
        ref={visibilityElementRef}
      />
    );
  }

Is there another reason for applying the props to the placeholder div elements? Happy to create a PR if you're in agreement.

@tazsingh
Copy link
Member

tazsingh commented Sep 25, 2019 via email

@bfin
Copy link
Author

bfin commented Sep 26, 2019

@tazsingh Looking at this a little more, it's just a tad more complicated so I'd like your guidance before moving forward with the PR.

queryByTestId relies on the temporary div element receiving the data-testid data attribute, so we can't just pull out the className prop and apply that to both the div and the loadable component (we'd also have to pull out that data attribute). This was a common challenge with HOCs and the usual solution was to use wrapperProps to explicitly identify anything we only want the wrapper component (in this case the outer div) to receive.

Example usage:

const LoadableComponent = loadableVisibility(() => import("./my-component"), {
  fallback: <Loading />
});

export default function App() {
  return (
    <LoadableComponent
      wrapperProps={{
        htmlAttribute: 'onlyTheDivWillSeeThis',
        className: 'onlyTheDivWillSeeThis',
      }}
      className='onlyTheLoadableComponentWillSeeThis'
      myCustomProp='onlyTheLoadableComponentWillSeeThis'
    />;
}

I think wrapperProps would be the appropriate way to pass a className to the temporary div element, but we could apply it to both by joining the 2 potential values (i.e. className={[props.className, props.wrapperProps.className].join(' ')}).

Example usage:

const LoadableComponent = loadableVisibility(() => import("./my-component"), {
  fallback: <Loading />
});

export default function App() {
  return (
    <LoadableComponent
      wrapperProps={{
        htmlAttribute: 'onlyTheDivWillSeeThis',
        className: 'onlyTheDivWillSeeThis',
      }}
      className='bothWillSeeThis'
      myCustomProp='onlyTheLoadableComponentWillSeeThis'
    />;
}

What are your thoughts on this?

@tazsingh
Copy link
Member

Hmmm interesting. Thank you for laying out the problem space as such, I'm starting to see the issue.

I don't have a clear thought right now but let me sit on this until next week if it's not urgent? I need a bit of a clearer head to fully grok a recommended path forward 🙏

@BenBish
Copy link

BenBish commented Jan 8, 2020

@tazsingh we have just encountered this problem too. Have you given any thought to a solution?

@tazsingh
Copy link
Member

tazsingh commented Jan 8, 2020

@BenBish Thanks for poking me on this!

I think I'm leaning towards taking a render props approach, as I believe it to be the cleanest way to resolve particular props being passed to particular elements.

For example:

const MyComponent = () => {
  return <LoadableVisibilityComponent {...propsForTheWrappingDiv}>{
    () => {
        // This will only be executed once the component is visible
      return <MyLoadableComponent {...propsOnlyForThisElement} />
    }
  }</LoadableVisibilityComponent>
}

However, the big challenge with that approach is that this library is meant to simply wrap react-visibility and @loadable/component with an additive API (if any) on top of theirs. The above render props API is certainly a divergence from that.

This may be fine as the API would be even more flexible as you can technically put whichever lib inside the render prop (such as a React.lazy()), but it would certainly be a breaking change and a big shift for this library. I'm happy to move in that direction if we all feel that it's better? Old versions of this lib wouldn't be going anywhere.

The alternative is what @bfin has kindly detailed above - to provide another prop to just handle the wrapper props and combine both sets of props where it makes sense. I believe that to be the least invasive but comes at the cost of a larger surface area and potential ambiguity around combining props (e.g. Do we always list the wrapper className before the underlying className if both are provided? Would that cause specificity issues? Maybe it's a non-issue as someone could create 2 distinct classNames, but then we'd need to document this behaviour?).

What do you think?

@AmyScript
Copy link

Any fix for this? Running into this same issue...

@tazsingh
Copy link
Member

@AmyScript I believe the proper solution to be what I detailed in the above comment where I've also listed the considerations for such an approach. What do you think?

@AmyScript
Copy link

I like the approach @tazsingh I'm not sure what kind of props is needed to pass into the Wrapper usually? I don't have a use case for passing props into the Wrapper.

Perhaps we can start with just a PR to pass props to the original component?

@tazsingh
Copy link
Member

@AmyScript It looks like @bfin attempted that PR to pass props to the underlying component and ran into the issue described in his comment above - #29 (comment)

If you can elegantly resolve that issue in a PR, I'd be more than happy to review it!

However, I am doubtful that it can be solved elegantly without essentially doing what I recommended in my comment and by introducing those tradeoffs.... Not to say you shouldn't try!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants