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

Remove FlashMessage dependency on redux-flash #446

Merged
merged 6 commits into from
Sep 30, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Documentation and usage info can be found in [docs.md](docs.md).
- [v2.0.0](migration-guides/v2.0.0.md)
- [v3.0.0](migration-guides/v3.0.0.md)
- [v4.0.0](migration-guides/v4.0.0.md)
- [v5.0.0](migration-guides/v5.0.0.md)

## Contribution

Expand Down
17 changes: 17 additions & 0 deletions migration-guides/v5.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# v5.0.0 Migration Guide

`FlashMessage` now expects separate `children` and `isError` props, rather than a single `message` prop. This allows the component to be used without needing to use `redux-flash`.

You should update any instances of `FlashMessage` accordingly.

(Note: this change is not necessary if you are using `FlashMessageContainer` instead of `FlashMessage` directly.)

```jsx

// Before, where "message" was a redux-flash message object:
<FlashMessage message={message} />

// After:
<FlashMessage isError={message.isError} {message.props}>{message.message}</FlashMessage>
chawes13 marked this conversation as resolved.
Show resolved Hide resolved

```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@launchpadlab/lp-components",
"version": "4.1.0",
"version": "5.0.0",
"engines": {
"node": "^8.0.0 || ^10.13.0 || ^12.0.0"
},
Expand Down
21 changes: 13 additions & 8 deletions src/indicators/flash-message-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import FlashMessage from './flash-message'
* A component that displays multiple flash messages generated by [redux-flash](https://github.com/LaunchPadLab/redux-flash).
* Most apps will need only one of these containers at the top level.
* Will pass down any additional props to the inner `FlashMessage` components.
*
*
* @name FlashMessageContainer
* @type Function
* @param {Object} messages - The flash messages that will be displayed.
* @param {Number} [limit] - Maximum number of concurrent messages to display
* @example
*
*
* function MyApp ({ messages }) {
* return (
* <div>
Expand All @@ -35,15 +35,20 @@ const defaultProps = {
limit: 5,
}

function FlashMessageContainer ({ messages, limit, ...rest }) {
function FlashMessageContainer({ messages, limit, ...rest }) {
const messagesToDisplay = messages.slice(0, limit)
return (
<div className="flash-message-container" role="alert">
{
messagesToDisplay.map(message =>
<FlashMessage key={ message.id } message={ message } { ...rest } />
)
}
{messagesToDisplay.map((message) => (
<FlashMessage
key={message.id}
isError={message.isError}
{...message.props}
{...rest}
>
{message.message}
</FlashMessage>
))}
</div>
)
}
Expand Down
46 changes: 27 additions & 19 deletions src/indicators/flash-message.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,59 @@
import React from 'react'
import PropTypes from 'prop-types'
import { flashMessageType } from 'redux-flash'
import classnames from 'classnames'

/**
*
* A component that displays a flash message generated by [redux-flash](https://github.com/LaunchPadLab/redux-flash).
* Any message props will be passed through to this component.
*
* A component that displays a flash message.
*
* @name FlashMessage
* @type Function
* @param {Object} message - The flash message that will be displayed.
* @param {String} children - The flash message that will be displayed.
* @param {Boolean} [isError] - A flag to indicate whether the message is an error message.
chawes13 marked this conversation as resolved.
Show resolved Hide resolved
* @param {Function} [onDismiss] - A callback for dismissing the flash message. The dismiss button will only be shown if this callback is provided.
* @example
*
* function ManyMessages ({ messages }) {
*
* function MyView () {
* const [message, setMessage] = useState(null)
* return (
* <div>
* {
* messages.map(message => <FlashMessage key={ message.id } message={ message } />)
* }
* {
* message &&
* <FlashMessage>{message}</FlashMessage>)
* }
* <button onClick={() => setMessage('Hi!')}> Show message </button>
* </div>
* )
* }
*
*/

const propTypes = {
message: flashMessageType.isRequired,
children: PropTypes.node.isRequired,
isError: PropTypes.bool,
onDismiss: PropTypes.func,
className: PropTypes.string,
}

const defaultProps = {
isError: false,
onDismiss: null,
className: '',
}

function FlashMessage ({ message, onDismiss }) {
const statusClass = message.isError ? 'failure' : 'success'
function FlashMessage({ children, isError, onDismiss, className, ...rest }) {
const statusClass = isError ? 'failure' : 'success'
return (
<div { ...message.props } className={ classnames('flash-message', statusClass, message.props.className) }>
{
onDismiss &&
<button type="button" className="dismiss" onClick={ () => onDismiss(message) }>
<div
className={classnames('flash-message', statusClass, className)}
{...rest}
>
{onDismiss && (
<button type="button" className="dismiss" onClick={() => onDismiss()}>
×
</button>
}
<p> { message.message } </p>
)}
<p> {children} </p>
chawes13 marked this conversation as resolved.
Show resolved Hide resolved
</div>
)
}
Expand Down
16 changes: 3 additions & 13 deletions stories/indicators/flash-message.story.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,9 @@ import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { FlashMessage } from 'src'

const successMessage = { id: '0', message: 'Success!', isError: false, props: {} }
const failureMessage = { id: '1', message: 'Failure!', isError: true, props: {} }

storiesOf('FlashMessage', module)
.add('success', () => (
<FlashMessage message={ successMessage } />
))
.add('failure', () => (
<FlashMessage message={ failureMessage } />
))
.add('success', () => <FlashMessage>Success!</FlashMessage>)
.add('failure', () => <FlashMessage isError>Failure!</FlashMessage>)
.add('dismissable', () => (
<FlashMessage message={ successMessage } onDismiss={ action('Dismiss') } />
<FlashMessage onDismiss={action('Dismiss')}>Success!</FlashMessage>
))
.add('with custom prop ("hidden")', () => (
<FlashMessage message={ { ...successMessage, props: { hidden: true } }} />
))
36 changes: 36 additions & 0 deletions test/indicators/flash-message-container.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import { mount } from 'enzyme'
import { FlashMessageContainer } from '../../src'

const successMessage = {
id: '0',
message: 'Success!',
isError: false,
props: {},
}
const failureMessage = {
id: '1',
message: 'Failure!',
isError: true,
props: {},
}

test('FlashMessageContainer displays all provided redux-flash messages', () => {
const wrapper = mount(
<FlashMessageContainer messages={[successMessage, failureMessage]} />
)
expect(wrapper.find('div.flash-message.success').exists()).toBe(true)
expect(
wrapper
.find('div.flash-message.success > p')
.first()
.contains('Success!')
).toBe(true)
expect(wrapper.find('div.flash-message.failure').exists()).toBe(true)
expect(
wrapper
.find('div.flash-message.failure > p')
.first()
.contains('Failure!')
).toBe(true)
})
28 changes: 15 additions & 13 deletions test/indicators/flash-message.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ import React from 'react'
import { mount } from 'enzyme'
import { FlashMessage } from '../../src/'

const successMessage = { id: '0', message: 'Success!', isError: false, props: {} }
const customMessage = { id: '0', message: 'Success!', isError: false, props: { className: 'foo', hidden: true } }

test('FlashMessage only shows dismiss button when callback is provided', () => {
const wrapper = mount(
<FlashMessage message={ successMessage } />
)
const wrapper = mount(<FlashMessage>Success!</FlashMessage>)
expect(wrapper.find('button.dismiss').exists()).toBe(false)
const dismissWrapper = mount(
<FlashMessage message={ successMessage } onDismiss={ () => { /* do something */ } } />
<FlashMessage
onDismiss={() => {
/* do something */
}}
>
Success!
</FlashMessage>
)
expect(dismissWrapper.find('button.dismiss').exists()).toBe(true)
})

test('FlashMessage accepts props from message object', () => {
const wrapper = mount(
<FlashMessage message={ customMessage } />
)
expect(wrapper.find('div.foo').exists()).toBe(true)
expect(wrapper.find('div').prop('hidden')).toBe(true)
test('FlashMessage sets class based on isError prop', () => {
const wrapper = mount(<FlashMessage>Success!</FlashMessage>)
expect(wrapper.find('div.success').exists()).toBe(true)
expect(wrapper.find('div.failure').exists()).toBe(false)
const errorWrapper = mount(<FlashMessage isError>Failure!</FlashMessage>)
expect(errorWrapper.find('div.success').exists()).toBe(false)
expect(errorWrapper.find('div.failure').exists()).toBe(true)
})