This version contains the following breaking changes:
<Select />
now has aplaceholder
prop by default ("Select")<Button />
and<SubmitButton />
now usearia-disabled
instead ofdisabled
- The
style
prop on<Button />
and<SubmitButton />
has been renamed tovariant
- Labels for
<CheckboxGroup />
and<RadioGroup />
now use a<legend>
html tag rather than a<label>
- The
hideLabel
prop on<RangeInput />
has been renamed tohideRangeValue
<CloudinaryFileInput />
and<FileInput />
components now store an array of objects representing the file data as opposed to a string<CloudinaryFileInput />
and<FileInput />
components no longer accept anonLoad
prop<CloudinaryFileInput />
and<FileInput />
components now default to allowing the user to remove a selected file- The
previewComponent
for a file input no longer receives avalue
prop andfile
is a file object (with the url) - The tag on
<Spinner />
now uses the classspinner
in place of an id and supports additional classes <TabBar />
now expects bothoptions
andvalue
as required props<Modal />
no longer accepts thehideCloseButton
prop<FlashMessageContainer />
component invokesonDismiss
with redux-flash message object and allows message-specific overrides<Button />
and<SubmitButton />
now accept a forwarded ref<Modal />
no longer overwrites the default modal or overlay class<Modal />
targets #root instead of the body<DateInput />
s dependency on react-datepicker was upgraded from v1 to v4<RadioGroup />
and<CheckboxGroup />
components no longer pass className prop to inputs@launchpadlab/lp-hoc
was removed as a dependency
The required changes for each item are detailed below.
It's likely that you are already providing placeholders to each instance of <Select />
in your application. However, if there are instances that must not have a placeholder, you can replicate this behavior in v4
by passing a false placeholder
prop:
<Field name="foo" component={Select} options={[]} placeholder={false} />
This change requires an update to stylesheets in your application to target aria-disabled
rather than disabled
:
// Before
button:disabled {
// ...disabled styles
}
// After
button[aria-disabled='true'] {
// ...disabled styles
}
Note: this update likely won't be necessary in apps that use the LaunchPad Lab client-template
since those styles target a common .is-disabled
class.
Any <Button />
and <SubmitButton />
with a style
prop should be updated to use variant
instead.
// Before
<Button style="secondary" />
// After
<Button variant="secondary" />
Hint: a global text search for style="
should point you to where this update needs to happen.
4. Labels for <CheckboxGroup />
and <RadioGroup />
now use a <legend>
html tag rather than a <label>
To avoid visual regressions, make sure any global styles applied to <label>
elements in your stylesheets also apply to <legend>
elements.
// Before
label {
// ...global label styles
}
// After
label, legend {
// ...global label styles
}
Any <RangeInput />
with a hideLabel
prop should be updated to use hideRangeValue
instead.
// Before
<RangeInput input={rangeInputProps} hideLabel={true} />
// After
<RangeInput input={rangeInputProps} hideRangeValue={true} />
6. <CloudinaryFileInput />
and <FileInput />
components now store an array of objects representing the file data as opposed to a string
This will affect how the value is stored in redux and could subsequently also affect the shape of the API request body (if not transformed beforehand). You may also need to update your API endpoint to accept additional file information (e.g., name, lastModified) if you plan on showing this information at a later point in time (e.g., on edit of an existing profile).
// Before
const Form = () => (
<form onSubmit={handleSubmit}>
<Field
name="profilePhoto"
component={FileInput}
/>
</form>
)
<Form
onSubmit={({ profilePhoto }) => {
return updateProfilePhotoUrl(profilePhoto)
}}
/>
// After
const Form = () => (
<form onSubmit={handleSubmit}>
<Field
name="profilePhoto"
component={FileInput}
/>
</form>
)
<Form
onSubmit={({ profilePhoto }) => {
return updateProfilePhotoUrl(profilePhoto[0].url)
}}
/>
Additionally, if you only have access to the url of the image, then you'll need to transform the data in the initialValues
prop. Remember to pass in an array!
// Before
<Form
initialValues={{
profilePhoto: profile.imageUrl,
}}
/>
// After
<Form
initialValues={{
profilePhoto: [{
url: profile.imageUrl,
}]
}}
/>
If you're passing in onLoad
to perform something external to the component, you can use onChange
instead. onChange
will only accept one argument (file object or objects) as opposed to onLoad
accepting the url string and the FileList item separately.
import { Field } from 'redux-form'
// Before
<Field
name="profilePhoto"
component={FileInput}
onLoad={(url, fileItem) => {
console.log(fileItem.name, url)
}}
/>
// After
<Field
name="profilePhoto"
component={FileInput}
onChange={(file) => {
console.log(file.name, file.url)
}}
/>
8. <CloudinaryFileInput />
and <FileInput />
components now default to allowing the user to remove a selected file
You may need to add styling to account for the new button that is available next to file previews. If you want to keep your application as-is (i.e., not showing a remove button), then you can specify a custom removeComponent
that returns null
.
// Before
<Field
name="profilePhoto"
component={FileInput}
/>
// After
function EmptyRemoveComponent() {
return null
}
<Field
name="profilePhoto"
component={FileInput}
removeComponent={EmptyRemoveComponent}
/>
9. The previewComponent
for a file input no longer receives a value
prop and file
is a file object (with the url)
If you're passing in a custom previewComponent
, you must modify your logic to only access the value
object.
// Before
const PreviewComponent = ({ file, value }) => {
return (
<div>
<p>Name: {file.name}</p>
<p>URL: {value}</p>
</div>
)
}
<FileInput previewComponent={PreviewComponent} />
// After
const PreviewComponent = ({ file }) => {
return (
<div>
<p>Name: {file.name}</p>
<p>URL: {file.url}</p>
</div>
)
}
<FileInput previewComponent={PreviewComponent} />
Replace any styling rules that target #spinner
with .spinner
.
// Before
#spinner {
text-align: center;
}
// After
.spinner {
text-align: center;
&.custom-spinner {
margin-bottom: 10px;
}
}
Make sure that any instances of <TabBar />
in your application are already sending these two props.
<TabBar
options={['Tab 1', 'Tab 2']}
value={currentTab}
/>
Replace hideCloseButton
with preventClose
. If you still need the modal to close on escape and/or by clicking the overlay, you can manually set those props. By default, shouldCloseOnEsc
and shouldCloseOnOverlayClick
will be set to the opposite of preventClose
(default false
).
// Before
<Modal hideCloseButton>
Modal content
</Modal>
// After
<Modal preventClose>
Modal content
</Modal>
// With prop override
<Modal preventClose shouldCloseOnEsc={true}>
Modal content
</Modal>
13. <FlashMessageContainer />
component invokes onDismiss
with redux-flash message object and allows message-specific overrides
If you are relying on props passed in to FlashMessageContainer
to override message props, then you need to remove the message props instead.
// Before
<FlashMessageContainer onDismiss={null} />
flashSuccessMessage('Success!', { props: { onDismiss: () => console.log('Dismiss!') } })
// After
flashSuccessMessage('Success!')
If you previously created a custom FlashMessageContainer
to be able to reference the message's id when removing the message, you no longer need that component.
// Before
function CustomFlashMessageContainer({ messages, limit, onDismiss, ...rest }) {
const messagesToDisplay = messages.slice(0, limit)
return (
<div className="flash-message-container" role="alert">
{messagesToDisplay.map((message) => {
const customOnDismiss = onDismiss
? () => onDismiss(message)
: null
const iconSrc = message.isError ? ErrorIcon : SuccessIcon
return (
<FlashMessage
key={message.id}
isError={message.isError}
onDismiss={customOnDismiss}
{...message.props}
{...rest}
>
<p>{message}</p>
</FlashMessage>
)
})}
</div>
)
}
// After
<FlashMessageContainer onDismiss={({ id }) => dispatch(removeMessage(id))} /> // or
flashSuccessMessage('Success!', { props: { onDismiss: ({ id }) => dispatch(removeMessage(id)) }})
If you need to directly access the underlying button HTML element, you can now pass a ref to the component
function MyExample() {
const ref = React.createRef()
return (
<Button ref={ref}>Click me!</Button>
)
}
Prior to v7, passing in a custom class name to the modal would override the default class, which would often strip away all of the styling. Now, that class will be added to the default class without the user having to include that class as well.
// Before
<Modal className="custom-modal">
.custom-modal {
// copy over all styles from .modal-inner
// add additional custom styles
}
or
// Before
<Modal className="modal-inner custom-modal">
.custom-modal {
// add additional custom styles
}
<Modal className="custom-modal">
.custom-modal {
// add additional custom styles
}
This logic applies to both the className
and overlayClassName
props, whether the use passes in a string or an object.
To make the Modal component more accessible, content needs to be hidden while the modal is open. react-modal
accomplishes this by applying this attribute to the appElement
, defined by the user. Previously, this library was relying on body
to serve as that element. However, Firefox will ignore all content when the body has that applied. Now, the #root
selector is used.
This should work seamlessly with any project that started from the LPL client template. If for whatever reason your application does not have an element with an id of root
, then you can either add that id to one of the top-most elements or you can override the appElement
prop with a target that's appropriate for your application. E.g.,
function LocalModal(props) {
const el = document.querySelector('#main')
return <Modal appElement={el} {...props} />
}
This introduces the following breaking changes:
- 2.0.0
moment
is deprecated bydate-fns
- 3.0.0
- Replace inlineFocusSelectedMonth with focusSelectedMonth
- 4.0.0
- Upgraded
react-popper
dependency from v1 to v2
- Upgraded
More details: https://github.com/Hacker0x01/react-datepicker/releases.
Of these breaking changes, the deprecation of moment
has the highest likelihood of affecting your application. This will only impact your application if you referenced the moment
library elsewhere in the codebase without installing the package directly as a dependency.
This change might affect the styles in your application. If you are passing in the className
prop and your styles rely on targeting both the outer-most fieldset and the inputs, you can either update your scss or you can pass in the same class name via <type>InputProps
.
<RadioGroup className="custom-radio-group" />
.custom-radio-group {
margin-bottom: 2px;
}
.custom-radio-group {
margin-bottom: 2px;
input {
margin-bottom: 2px;
}
}
Or,
<RadioGroup
className="custom-radio-group"
radioInputProps={{ className: 'custom-radio-group' }}
/>
With the introduction of hooks, lp-hoc
is no longer necessary and will soon be archived. To accommodate this, the dependency was removed from this library. This change only introduced one breaking change, which affected the CloudinaryFileInput
. All other components that relied on lp-hoc
utilities did not have any changes in interface or functionality but the utilities that these components relied on were removed as exports from this library, which projects may currently rely on.
If you're using <CloudinaryFileInput>
, you will need to pass in an additional required prop: apiAdapter
. This adapter needs to respond to the post
method and return a Promise. For typical LPL projects, this will be the api
adapter which can be imported from services/api.js
.
If your project relies on any of the following utils imported from @launchpad/lp-components
, you should look for supported alternatives on npm. If for whatever reason that's not possible, then you can import them directly from @launchpadlab/lp-hoc
in your project.
- modifyProps
- sortable
- sortablePropTypes
- toggle
- togglePropTypes
- onOutsideClick