Skip to content

Commit

Permalink
Remove FlashMessage component dependency on redux-flash
Browse files Browse the repository at this point in the history
  • Loading branch information
dpikt committed Sep 28, 2020
1 parent a49be1e commit 1d5f081
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 53 deletions.
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.
* @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>
</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)
})

0 comments on commit 1d5f081

Please sign in to comment.