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

feat: new callback prop (renderComponent) to customize rendering #395

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

andrwo
Copy link

@andrwo andrwo commented Oct 15, 2021

Adds a new renderProp to allow user to customize how to render the component.

Description

Adds a new prop to IntlTelInput:

{
  renderComponent? : (wrapperProps, flagDropDownProps, inputProps) => React.ReactNode
}

Currently, the rendering of the component is fixed with a <div> around <FlagDropDown ...> and <input>. If this callback is provided, instead of rendering the fixed default, will call the callback with the relevant props for user to customize their own rendering.

If callback not provided, will use the existing default rendering (<div ...> <FlagDropDown ...> <input ... >), so it does not affect any existing usages.

This allows users to customize using whatever Input component from any UI libraries (bootstrap, Material UI, etc.),. This enables this component to work smoothly with material UI (and other UI libraries) without messing around with the CSS to reproduce the UI's behaviour.

  • Also exposed the FlagDropDown component so that it can be used in the rendering.
  • Add new prop flagContainerProps to inner component FlagDropDown to allow user to customize the flag container's properties.

Will post CodeSandbox when I figure out how to link the module dependency to my fork...

Example usage (Material UI):

import InputAdornment from "@mui/material/InputAdornment";
import TextField from "@mui/material/TextField";
import IntlTelInput, { FlagDropDown } from "react-intl-tel-input";

<IntlTelInput
	value={phoneNumber}
	fieldName="phone"
	fieldId="phone_id"
	formatOnInit={true}
	autoHideDialCode={false}
	renderComponent={(wrapperProps, flagDropDownProps, inputProps) => {
	  flagDropDownProps.flagContainerProps = { style: { position: "relative" } };
          inputProps.inputRef = inputProps.ref;
          inputProps.ref = undefined;
	  inputProps.InputProps = {
		startAdornment: (
		  <InputAdornment position="start">
			<div {...wrapperProps}>
			  <FlagDropDown {...flagDropDownProps} />
			</div>
		  </InputAdornment>
		),
	  };
	  return (
		<TextField
		  variant="filled"
		  margin="dense"
		  size="small"
		  fullWidth
		  {...inputProps}
		/>
	  );
	}}
	onPhoneNumberChange={(v, nn) => setPhoneNumber(nn)}
  />

Example usage (reactstrap (bootstrap React)):

import { Input, InputGroup, InputGroupAddon, InputGroupText } from "reactstrap";
import IntlTelInput, { FlagDropDown } from "react-intl-tel-input";

<IntlTelInput
	value={phoneNumber}
	fieldName="phone"
	fieldId="phone_id"
	formatOnInit={true}
	autoHideDialCode={false}
        renderComponent={(wrapperProps, flagDropDownProps, inputProps) => {
          flagDropDownProps.flagContainerProps = { style: { position: "relative" } };
          inputProps.innerRef = inputProps.ref;
          inputProps.ref = undefined;
          return (
            <InputGroup>
              <InputGroupAddon addonType="prepend">
                <InputGroupText>
                  <div {...wrapperProps}>
                    <FlagDropDown {...flagDropDownProps} />
                  </div>
                </InputGroupText>
              </InputGroupAddon>
              <Input {...inputProps} />
            </InputGroup>
          );
        }}
	onPhoneNumberChange={(v, nn) => setPhoneNumber(nn)}
  />

Screenshots:

Below are actual working implementation of above code:

Material UI:

image

Reactstrap (Bootstrap React):

image

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have used ESLint & Prettier to follow the code style of this project.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Closes #391

@andrwo andrwo mentioned this pull request Oct 18, 2021
@andrewsantarin
Copy link
Contributor

andrewsantarin commented Oct 20, 2021

UPDATE: I see it's applied. Thank you.

@andrwo Could you insert this to the description? GitHub will automatically close the issue if this gets merged.

Closes #391

Comment on lines +1328 to +1334
if (this.props.renderComponent) {
return this.props.renderComponent(
wrapperProps,
flagDropDownProps,
inputProps,
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You're offloading the entire rendering work to the consumer. Correct? Have you considered a much lighter API that doesn't completely overtake the built-in rendering? Is there something that stops you from doing so?

Disclaimers:

  • I have yet to test this locally.
  • I can totally see where you're coming from with this sort of approach. It's a valid use case. All I'm concerned about is the complexity of the usage. The component is pretty complex as it is.

@mcataford Any opinions?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for taking a look at this.

This is a purely optional additional API for more advanced users who say: yes, I know what I am doing and would like to take control of how the component should be rendered. If renderComponent() prop is not provided, then it will still fallback on the original default rendering method, so non-expert users need not take heed of this.

The upside is that this enables the sub-components to be used in many different UI frameworks. As long as someone has figured out how to rewire this renderComponent() adapter for use in Material UI, Reactstrap etc. they can just copy the renderComponent() method from these examples and it is ready to use. We can put in the examples a new section on how to make this component work with some popular UI libraries to help future users so they don't have to figure it out themselves -- just copy and use.

I am not sure how I would be able to make the API lighter and still maintain the flexibility for it to potentially work with any UI library (and also not introduce any further code bloat -- the changes here are pretty minimal).

Choose a reason for hiding this comment

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

@andrewsantarin is this PR ok for merge? It would solve my development issues.

@mcataford
Copy link
Collaborator

Late to the ball on this one -- I'll read through this in a couple hours to get a proper review in. 🚀

@mcataford mcataford self-requested a review December 13, 2021 18:40
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.

Using with Material UI
4 participants