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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Form.Check.Label spacing #5983

Merged
merged 9 commits into from Nov 29, 2021

Conversation

TyMick
Copy link
Contributor

@TyMick TyMick commented Aug 19, 2021

Fixes #5938. Currently, when using a Form.Check.Label component to customize Form.Check rendering, there will be no space between the checkbox and the label. This is because Form.Check is currently relying on the presence of a label prop to apply the form-check class name to the wrapping <div>, because checkboxes without labels don't need the wrapping element to have the form-check class name.

This PR adds a utility to check whether an element has a child of a certain type. It then uses that utility to check if a Form.Check element has a Form.Check.Label child and takes that into account when determining whether the checkbox has a label.

Adding a special property (currently called typeName, but that can certainly change) to components for this utility is necessary because React minifies the displayName property in production.

Here's a side-by-side screenshot of the current bug as shown in the documentation site, compared to the fixed version when I run the docs locally after this fix:

Screen Shot 2021-08-19 at 7 58 58 AM


EDIT: Only the changes from the first two commits are required to fix this spacing. The third commit adds a related feature that I maybe should've saved for a separate PR. 馃檲

@TyMick
Copy link
Contributor Author

TyMick commented Aug 19, 2021

Another thing I just thought of is that Form.Check currently doesn't allow users to use a label prop and a custom Form.Check.Input at the same time...

{children || (
<>
{input}
{hasLabel && (
<FormCheckLabel title={title}>{label}</FormCheckLabel>
)}

...since the presence of any children will prevent a prop-generated label from rendering. Is that something we want to support?

@kyletsang
Copy link
Member

Wonder if we should allow bools for label here to control the .form-check class in the custom rendering scenario.

@jquense this might be a cleaner approach?

@TyMick
Copy link
Contributor Author

TyMick commented Aug 20, 2021

Do you mean requiring the user to pass label={true} to Form.Check when using a custom Form.Check.Label? Just checking if I'm interpreting correctly. 馃憤馃徏

@TyMick
Copy link
Contributor Author

TyMick commented Aug 20, 2021

Also I just thought of one way to allow users to use a label prop and a custom Form.Check.Input at the same time (or a custom Form.Check.Label without having to include a custom Form.Check.Input): 5822d94. I can revert if it's too much, though.

@kyletsang
Copy link
Member

Do you mean requiring the user to pass label={true} to Form.Check when using a custom Form.Check.Label? Just checking if I'm interpreting correctly. 馃憤馃徏

Yeah, so when using custom rendering, setting label={true} would toggle the class

<FormCheck label={true}>
  <FormCheckLabel>Custom</FormCheckLabel>
  ...
</FormCheck>

@TyMick
Copy link
Contributor Author

TyMick commented Aug 23, 2021

Right on. As a user, I'd prefer FormCheck to be able to detect a FormCheckLabel child element, but if there's a consensus that that feature isn't worth the overhead of adding a typeName property and a getChildOfType (or includesType) function, I can get behind that.

@kyletsang
Copy link
Member

@jquense your thoughts on this?

@TyMick
Copy link
Contributor Author

TyMick commented Aug 23, 2021

And FWIW, only the changes from the first two commits are required to fix the Form.Check.Label spacing. The third commit adds a related feature that I maybe should've saved for a separate PR. 馃檲

Currently, when using a `Form.Check.Label` component to customize
`Form.Check` rendering, there will be no space between the checkbox and
the label. This is because `Form.Check` is currently relying on the
presence of a `label` prop to apply the `form-check` class name to the
wrapping `<div>`, because checkboxes without labels [don't need the
wrapping element to have the `form-check` class name][1].

This commit adds a utility to check whether an element has a child of a
certain type. It then uses that utility to check if a `Form.Check`
element has a `Form.Check.Label` child and takes that into account when
determining whether the checkbox has a label.

Adding a special property (currently called `typeName`, but that can
certainly change) to components for this utility is necessary because
React minifies the `displayName` property in production.

[1]: react-bootstrap#5938 (comment)
Currently, `Form.Check` doesn't allow users to use a custom
`Form.Check.Input` with a label generated by the `label` prop (and
requires that a custom `Form.Check.Input` be used if a custom
`Form.Check.Label` is also used) because the presence of _any_ children
in `Form.Check` prevents automatic inputs and labels (and feedback) from
generating. This approach allows mixing and matching by extracting any
custom input, label, and feedback components from `Form.Check`'s
`children` prop, then separately for each, using the custom component if
provided or an automatic component otherwise.
@TyMick TyMick force-pushed the fix-form-check-label-spacing branch from 5822d94 to 3b7a225 Compare October 22, 2021 15:16
@TyMick
Copy link
Contributor Author

TyMick commented Oct 22, 2021

I see a stable v2.0.0 was released with Form.Check.Label spacing still broken. 馃様 Here's the broken example on the current docs site.

@kyletsang, @jquense, is there anything I can do to help get this fix out the door? I'm willing to refactor to make label={true} add a "form-check" class name to the wrapper if that's what it takes. I just think that Form.Check being able to detect the presence of Form.Check.Label child would be a better developer experience. But this bug is what's keeping my projects on React Bootstrap v1 for the time being.

I've rebased this PR onto the latest master to fix the conflict with #6015.

@kyletsang
Copy link
Member

@jquense, could we check something like child.type here?

The `type` property of React elements have reference equality with
matching imported component variables (`JSXElementConstructor` objects,
to be precise)! For some reason I was not expecting that, but it makes
sense to me now, since these constructor objects are being imported from
a singular source, so there's no reason React would create multiple
instances of them. So `child.type === type` all we need to check in
`getChildOfType`; the `typeName` prop I added earlier is not at all
needed.
Just as a convenience. I can revert this commit if these scripts aren't
desired.
@TyMick TyMick force-pushed the fix-form-check-label-spacing branch from ce25d71 to 5343b0a Compare October 23, 2021 16:39
@TyMick
Copy link
Contributor Author

TyMick commented Oct 23, 2021

@kyletsang You know what, I just learned to my surprise that the type property of a React element has reference equality with a matching imported component variable (JSXElementConstructor object, to be precise)! For some reason I was not expecting that, but it makes sense to me now, since these constructor objects are being imported from a singular source, so there's no reason React would create multiple instances of them. So child.type === type is indeed all we need to check in getChildOfType; the typeName property I added earlier is not at all necessary.

I've pushed a commit greatly simplifying getChildOfType and removing the typeName property, and I've tested it manually with a local development server of the docs site, with a local production server of the docs site (using the scripts in 5343b0a), and with a personal project, and it still fixes the Form.Check.Label spacing issues! And with a much cleaner solution now.

)}
</>
)}
{inputElement}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would force the elements to render in a specific order even if the user specified otherwise. For ex:

<Form.Check type={type} id={`check-api-${type}`}>     
  <Form.Control.Feedback type="valid">You did it!</Form.Control.Feedback>
  <Form.Check.Label>{`Custom api ${type}`}</Form.Check.Label>
  <Form.Check.Input type={type} isValid />
</Form.Check>

would render

<div class="form-check">
  <input type="radio" id="check-api-radio" class="form-check-input is-valid">
  <label for="check-api-radio" class="form-check-label">Custom api radio</label>
  <div class="valid-feedback">You did it!</div>
</div>

@kyletsang
Copy link
Member

You could probably simplify this further to just check the for the label node if children is specified.

Maybe like this?

 const hasLabel =
        (label != null && label !== false && !children) ||
        (children !== undefined &&
          React.Children.toArray(children).find(
            (child) =>
              React.isValidElement(child) && child.type === FormCheckLabel,
          ) !== undefined);

@jquense
Copy link
Member

jquense commented Oct 26, 2021

Checking type on an element is a bit fraught. It doesn't end up working as nicely as you'd expect. We actually used to do this in a number of components. The problem is that lots of things wrap components.

  • hot reloading makes this check not work (or used to)
  • If someone makes their own wrapper around FormCheckLabel it misses
  • if children contains a wrapping element, or fragment, toArray won't find it.

As a "best effort" its ok but it's not consistent

@TyMick
Copy link
Contributor Author

TyMick commented Nov 6, 2021

Thanks for the great feedback, @kyletsang and @jquense! How do things look after this latest commit? I've stopped trying to force the custom render components into a certain order, and now the only thing this PR adds to FormCheck is it checks to see if a direct child is a FormCheckLabel and takes that into account when applying the correct Bootstrap class. Then if someone decides to wrap FormCheckLabel, the official solution can be passing label={true} to FormCheck? I think checking for FormCheckLabel will help people out in the majority of cases, though.

Copy link
Member

@kyletsang kyletsang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be ok since it covers the most general case?

Your call @jquense 馃槃

src/FormCheck.tsx Outdated Show resolved Hide resolved
@kyletsang
Copy link
Member

@TyMick can you remove the changes in both the package.json files? We don't need those.

I'll merge after

@jquense
Copy link
Member

jquense commented Nov 29, 2021

sorry i've been so absent. seems ok with me, certainly don't block on me

@TyMick
Copy link
Contributor Author

TyMick commented Nov 29, 2021

@kyletsang Sounds good! I found them useful for testing the production build, but they certainly aren't necessary. 馃憤馃徏

@kyletsang kyletsang merged commit 250655c into react-bootstrap:master Nov 29, 2021
@kyletsang
Copy link
Member

Thanks! And sorry for the delays 馃槄

@TyMick
Copy link
Contributor Author

TyMick commented Nov 29, 2021

No worries; plenty of those delays were on my end anyway. 馃槉

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

Successfully merging this pull request may close these issues.

<Form.Check> and <Form.Check.Label> should add form-check and form-label class automatically.
3 participants