A React component for rendering and managing user profile forms
Through NPM:
$ npm install @vtex/profile-form
import ProfileContainer from '@vtex/profile-form/ProfileContainer'
Through vtex.io:
Add vtex.profile-form
to your manifest.json
dependencies
import { ProfileSummary } from 'vtex.profile-form'
Helper functions are properties of the modules
import
import { modules } from 'vtex.profile-form'
const { addValidation } = modules
This is the main component of profile-form
. It will render inputs based on the rules provided, and fill them with data from the profile object passed to it. It also spawns internal child components that perform validation on such inputs, also based on the rules.
ProfileContainer
keeps the current profile being managed in its internal state; the defaultProfile
prop is only accessed at mount time. If you need to update the prop at some other time, use the special key
prop to force the recreation of the whole container with the new profile. It provides an onSubmit()
function to call a function in the host component when the user submits the form.
Inputs and buttons inside ProfileContainer
can be customized to fit the style of different host applications. You can also pass in children components and they will be displayed right before the business toggle button, but they won't receive validation, submission or state management - you must do that yourself.
- rules: (default: the default rules) Set of rules for this form
- defaultProfile: Initial data for the profile object
- onSubmit: Function to be called upon form submission. Receives as argument an object containing a
valid
boolean representing the state of the profile object, and a clean profile (shape:ProfileShape
) - Input: (default:
StyleguideInput
) Component to be used as input for the form fields - ToggleBusinessButton: Component to be used as a button for toggling business fields
- SubmitButton: Component to be used as a submit button
- shouldShowExtendedGenders: (default:
false
) Whether the gender input should display a wide list of genders or just male/female - children: Custom components to be added right before the business toggle button
- intl:
react-intl
automatically injected util - blockDocument: Enables or disables editing the document field in my account page.
ProfileContainer.propTypes = {
rules: RuleShape.isRequired,
defaultProfile: ProfileShape,
onSubmit: PropTypes.func.isRequired,
Input: PropTypes.func,
ToggleBusinessButton: PropTypes.element,
SubmitButton: PropTypes.element,
shouldShowExtendedGenders: PropTypes.bool,
children: PropTypes.node,
intl: intlShape.isRequired,
}
<ProfileContainer
profile={profile}
rules={brRules}
onSubmit={this.handleSubmit}
/>
An example with customized buttons:
<ProfileContainer
profile={profile}
rules={brRules}
onSubmit={this.handleSubmit}
Input={StyleguideInput}
SubmitButton={
<Button block size="small">
Save profile
</Button>
}
ToggleBusinessButton={<Button size="small" block />}
/>
Note that the onClick
hook for both buttons is automatically injected by the container. Also, the text for the ToggleBusinessButton
is standardized; you don't need to pass any text inside your button (and if you do, it will be overwritten).
This component contains functionality for easily fetching the profile rules for any country and providing them to its direct children (usually a ProfileContainer
component).
children
: The component which will be rendered inside this component and, therefore, receive the provided rules (you probably want this to be aProfileContainer
instance)country
: The string identifier for the country which rules are to be provided, must useISO Alpha3
standard (e.g.BRA
,USA
, etc.)shouldUseIOFetching
: Whether to use built-in dynamic file fetching for the rules. Should be used if the project is an IO appfetch
: Functionality for fetching the rule files. Outside of IO, it must receive the function{country => import('@vtex/profile-form/lib/rules/' + country)}
as its value. In IO, this prop must not be set
ProfileRules.propTypes = {
children: PropTypes.element.isRequired,
locale: PropTypes.string.isRequired,
shouldUseIOFetching: PropTypes.bool,
fetch: PropTypes.func,
}
Here, ProfileContainer
will be injected with the fetched rules:
<ProfileRules
locale={'pt-BR'}
fetch={locale => import('../../src/rules/' + locale)}
>
<ProfileContainer profile={profile} onSubmit={this.handleSubmit} />
</ProfileRules>
This component that renders my account fields. It received new two more properties to make it possible to block the document field: userProfile
and blockDocument
userProfile
: User profile so that we can check if the saved document already exists in database.blockDocument
: Enables or disables editing the document field in my account page. This property comes from the my-account module
<ProfileField
key={field.name}
field={field}
data={profile[field.name]}
options={options[field.name]}
onFieldUpdate={this.handleFieldUpdate}
Input={Input}
userProfile={profile}
blockDocument={this.props.blockDocument}
/>
render() {
const { field, data, options, Input, userProfile, blockDocument } = this.props
if(blockDocument && field.name === 'document' && userProfile['document'].value !== null){
field.disabled = true
}
return (
<Input
field={field}
data={data}
options={options}
inputRef={this.inputRef}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
)
}
}
This component takes in a profile object and a set of rules and prepares the data for displaying. Its main advantages are handling the translation of the labels and informing which fields should be hidden, but it also does some parsing logic such as translating gender and masking phone.
ProfileSummary
renders nothing by itself. The data is served as a render prop, and the host app must display it as desired. An object is passed with three properties: isCorporate
, a boolean value indicating if the profile represents a corporation, and personalData
and businessData
, where each of these is an object with the keys being the field names (such as firstName
or homePhone
for personalData
) and the value being an object containing useful information for displaying each field:
label
: the field label, already translated.value
: the value obtained from theprofile
prop, masked if necessaryhidden
: whether this field should be hidden or not
profile
: The profile object whose data will be servedrules
: The set of rules to apply over the profile. Works best if injected by aProfileRules
componentchildren
: The render prop function, serving the data in the structure explained aboveintl
:react-intl
internal utility
ProfileSummary.propTypes = {
profile: ProfileShape.isRequired,
rules: RuleShape.isRequired,
children: PropTypes.func.isRequired,
intl: intlShape.isRequired,
}
<ProfileSummary profile={profile}>
{({ personalData, businessData, isCorporate }) => (
<div>
<h3>Personal Data:</h3>
{Object.keys(personalData).map(fieldName =>
<div>
{!personalData[fieldName].hidden &&
<label>{personalData[fieldName].label}</label>}
{!personalData[fieldName].hidden &&
<span>{personalData[fieldName].value}</span>}
</div>
)}
{isCorporate && (
<h3>Business Data:</h3>
{/* [...] */}
)}
</div>
)}
</ProfileSummary>
This function takes in a clean profile and a set of rules, and generates another profile object with validation metadata (following ProfileWithValidationShape
). Besides just wrapping each property, it checks each rule for a display
transform function, and applies it to the value before returning. This is specially useful when editing dates, as they usually come from the API as a YYYY-MM-DD
string and you may want to change the format to something more localized before displaying it inside the text input.
profile
: A profile in the shape ofProfileShape
rules
: A set of rules in the shape ofRuleShape
- A profile in the shape of
ProfileWithValidationShape
This is the inverse to the addValidation
function above. As such, besides stripping validation metadata it also checks the rules for submit
transformations and applies it to the proper fields. Again, this is useful to transform dates from the user's localized format to the international format required by the API.
profile
: A profile in the shape ofProfileWithValidationShape
rules
: A set of rules in the shape ofRuleShape
- A profile in the shape of
ProfileShape
This folder contains JSON localized files providing translations for every text used inside the profile-form
. The correct files must be imported and provided by the host app at their own charge.
This folder provides files for each supported country, containing the rules used to manage the form. There are also default rules, which are used when an unsupported country is provided to the ProfileRules
component.
Such rules provide information on how each field of the form should be rendered. Functionality for masking and validating inputs is also provided inside the rules and isolated by field - there are no built-in validators in profile-form
(except for checking mandatory fields).
This folder provides inputs to be used as building blocks for the profile form. Currently, only StyleguideInput
, an input which follows the VTEX Styleguide, is provided.
This is the shape of a clean profile object, as if it had just been retrieved from a server.
PropTypes.shape({
/** Date of the user's birth */
birthDate: PropTypes.string,
/** User's business phone */
businessPhone: PropTypes.string,
/** User's corporate document */
corporateDocument: PropTypes.string,
/** User's corporate name */
corporateName: PropTypes.string,
/** User's personal document */
document: PropTypes.string,
/** User's email */
email: PropTypes.string,
/** User's personal name */
firstName: PropTypes.string,
/** User's gender */
gender: PropTypes.string,
/** User's personal phone */
homePhone: PropTypes.string,
/** User's surname */
lastName: PropTypes.string,
/** User's corporate state registration */
stateRegistration: PropTypes.string,
/** User's corporate trade name */
tradeName: PropTypes.string,
/** Whether the user is a corporation or not */
isCorporate: PropTypes.bool,
})
When the profile object is passed to ProfileContainer
, validation data is added to each field. Thus, every field is now an object with the following shape:
PropTypes.shape({
/** The value for that field */
value: PropTypes.any,
/** i18n code for the error this value is presenting */
error: PropTypes.string,
/** Whether the input should receive focus */
focus: PropTypes.bool,
/** Whether the input has already been touched by the user */
touched: PropTypes.bool,
})
The whole validated profile object simply maintains the same structure as in ProfileShape
, but now each field is of type ProfileFieldShape
.
Every rule inside the rules/
folder contains the structure below. The fields are divided between personal
and business
fields, in order to be able to alternate visibility between them.
PropTypes.shape({
/** The country this rule refers to */
country: PropTypes.string,
/** Configuration for the personal fields */
personalFields: PropTypes.arrayOf(RuleFieldShape).isRequired,
/** Configuration for the business fields */
businessFields: PropTypes.arrayOf(RuleFieldShape).isRequired,
})
Inside each fields
array of a rules object, every object represents one field of profile-form
. These objects take on the shape below. Inside these rule-shaped objects, validation and masking functionality is provided.
PropTypes.shape({
/** Name for both the field and the profile object property */
name: PropTypes.string.isRequired,
/** Maximum length of the field */
maxLength: PropTypes.number,
/** i18n code for the label to be displayed over the field */
label: PropTypes.string.isRequired,
/** Whether the field is required or not */
required: PropTypes.bool,
/** Whether the field should be hidden or not */
hidden: PropTypes.bool,
/** Function returning a mask to be applied on the value */
mask: PropTypes.func,
/** A function to evaluate if the value is valid */
validate: PropTypes.func,
/** A function to transform received data before displaying */
display: PropTypes.func,
/** A function to transform input data before submitting */
submit: PropTypes.func,
})
Author: Gustavo Silva (@akaFTS)
This project is licensed under the MIT License - see the LICENSE.md file for details