Skip to content

Latest commit

 

History

History
442 lines (360 loc) · 14.4 KB

v7.0.0.md

File metadata and controls

442 lines (360 loc) · 14.4 KB

v7.0.0 Migration Guide

This version contains the following breaking changes:

  1. <Select /> now has a placeholder prop by default ("Select")
  2. <Button /> and <SubmitButton /> now use aria-disabled instead of disabled
  3. The style prop on <Button /> and <SubmitButton /> has been renamed to variant
  4. Labels for <CheckboxGroup /> and <RadioGroup /> now use a <legend> html tag rather than a <label>
  5. The hideLabel prop on <RangeInput /> has been renamed to hideRangeValue
  6. <CloudinaryFileInput /> and <FileInput /> components now store an array of objects representing the file data as opposed to a string
  7. <CloudinaryFileInput /> and <FileInput /> components no longer accept an onLoad prop
  8. <CloudinaryFileInput /> and <FileInput /> components now default to allowing the user to remove a selected file
  9. The previewComponent for a file input no longer receives a value prop and file is a file object (with the url)
  10. The tag on <Spinner /> now uses the class spinner in place of an id and supports additional classes
  11. <TabBar /> now expects both options and value as required props
  12. <Modal /> no longer accepts the hideCloseButton prop
  13. <FlashMessageContainer /> component invokes onDismiss with redux-flash message object and allows message-specific overrides
  14. <Button /> and <SubmitButton /> now accept a forwarded ref
  15. <Modal /> no longer overwrites the default modal or overlay class
  16. <Modal /> targets #root instead of the body
  17. <DateInput />s dependency on react-datepicker was upgraded from v1 to v4
  18. <RadioGroup /> and <CheckboxGroup /> components no longer pass className prop to inputs
  19. @launchpadlab/lp-hoc was removed as a dependency

The required changes for each item are detailed below.


1. <Select /> now has a placeholder prop by default ("Select")

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} />

2. <Button /> and <SubmitButton /> now use aria-disabled instead of disabled

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.

3. The style prop on <Button /> and <SubmitButton /> has been renamed to variant

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
}

5. The hideLabel prop on <RangeInput /> has been renamed to hideRangeValue

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,
    }]
  }}
/>

7. <CloudinaryFileInput /> and <FileInput /> components no longer accept an onLoad prop

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} />

10. The tag on <Spinner /> now uses the spinner class in place of an id

Replace any styling rules that target #spinner with .spinner.

// Before
#spinner {
  text-align: center;
}

// After
.spinner {
  text-align: center;

  &.custom-spinner {
    margin-bottom: 10px;
  }
}

11. <TabBar /> now expects both options and value as required props

Make sure that any instances of <TabBar /> in your application are already sending these two props.

  <TabBar
    options={['Tab 1', 'Tab 2']}
    value={currentTab}
  />

12. <Modal /> no longer accepts the hideCloseButton prop

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)) }})

14. <Button /> and <SubmitButton /> now accept a forwarded ref

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>
  )
}

15. <Modal /> no longer overwrites the default modal or overlay class

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

// 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
}

After

<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.

16. <Modal /> targets #root instead of the body

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} />
}

17. <DateInput />s dependency on react-datepicker was upgraded from v1 to v4

This introduces the following breaking changes:

  • 2.0.0
    • moment is deprecated by date-fns
  • 3.0.0
    • Replace inlineFocusSelectedMonth with focusSelectedMonth
  • 4.0.0
    • Upgraded react-popper dependency from v1 to v2

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.

18. <RadioGroup /> and <CheckboxGroup /> components no longer pass className prop to inputs

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.

Before

<RadioGroup className="custom-radio-group" />
.custom-radio-group {
  margin-bottom: 2px;
}

After

.custom-radio-group {
  margin-bottom: 2px;
  input {
    margin-bottom: 2px;
  }
}

Or,

<RadioGroup
  className="custom-radio-group"
  radioInputProps={{ className: 'custom-radio-group' }}
/>

19. @launchpadlab/lp-hoc was removed as a dependency

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