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

Make actions pure #35

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
17 changes: 10 additions & 7 deletions demo/src/components/Demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ class Demo extends Component {
status: 'info',
dismissible: false,
dismissAfter: 0
});
}).payload;
setTimeout(function() {
notif.message = 'Resize the window, theme is responsive!';
notif.dismissAfter = 5000;
updateNotification(notif);
const updatedNotification = Object.assign({}, notif, {
message: 'Resize the window, theme is responsive!'
});
updateNotification(updatedNotification);
}, 3000);
}

/**
* Update window height state when component will unmount
* @returns {void}
Expand Down Expand Up @@ -96,5 +97,7 @@ class Demo extends Component {
);
}
}

export default connect(null, {notify, updateNotification})(Demo);
export default connect(null, {
notify,
updateNotification
})(Demo);
2 changes: 1 addition & 1 deletion demo/src/components/NotificationExamples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class NotificationExamples extends Component {
status: 'loading',
dismissible: false,
dismissAfter: 0
});
}).payload;
setTimeout(function() {
notif.status = 'success';
notif.message = 'Your file has been successfully uploaded';
Expand Down
7 changes: 4 additions & 3 deletions demo/src/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from 'react';
import {Provider} from 'react-redux';
import {render} from 'react-dom';
import {createStore, compose, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import {createStore, compose, combineReducers} from 'redux';
import App from './components/Demo';
import {reducer as notificationsReducer} from '../../src';
import 'babel-polyfill';
import './styles/style.scss';

// store
const createStoreWithMiddleware = compose(applyMiddleware(thunk))(createStore);
const createStoreWithMiddleware = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f // add support for Redux dev tools
)(createStore);
const store = createStoreWithMiddleware(combineReducers({
notifications: notificationsReducer()
}), {});
Expand Down
25 changes: 20 additions & 5 deletions src/components/Notification.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import {Timer, mapObjectValues} from '../helpers';
import {removeNotification} from '../store/notifications';
import {Timer, mapObjectValues, preloadImage} from '../helpers';
import {removeNotification, imageLoaded} from '../store/notifications';
import {POSITIONS} from '../constants';

/**
Expand Down Expand Up @@ -45,6 +45,7 @@ export class Notification extends Component {
position: PropTypes.oneOf(mapObjectValues(POSITIONS)),
dismissAfter: PropTypes.number.isRequired,
dismissible: PropTypes.bool.isRequired,
fetchImage: PropTypes.bool,
onAdd: PropTypes.func,
onRemove: PropTypes.func,
closeButton: PropTypes.bool.isRequired,
Expand All @@ -56,7 +57,8 @@ export class Notification extends Component {
).isRequired,
allowHTML: PropTypes.bool.isRequired
}).isRequired,
removeNotification: PropTypes.func.isRequired
removeNotification: PropTypes.func.isRequired,
imageLoaded: PropTypes.func.isRequired
};

/**
Expand All @@ -77,6 +79,7 @@ export class Notification extends Component {
* @returns {void}
*/
componentDidMount() {
this._preloadImage();
const {onAdd} = this.props.notification;
if (typeof onAdd === 'function') {
onAdd();
Expand All @@ -100,12 +103,21 @@ export class Notification extends Component {
* @returns {void}
*/
componentWillReceiveProps(nextProps) {
this._preloadImage();
const {dismissAfter} = nextProps.notification;
this.setState({
timer: createTimer(dismissAfter, this._remove)
});
}

_preloadImage = () => {
const {notification, imageLoaded} = this.props;
const {fetchImage, image} = notification;
if (fetchImage && image) {
preloadImage(image, imageLoaded(notification));
}
}

/**
* Remove the notification
* @private
Expand Down Expand Up @@ -179,13 +191,17 @@ export class Notification extends Component {
message,
status,
dismissible,
fetchImage,
closeButton,
buttons,
image,
allowHTML
}
} = this.props;
const {timer} = this.state;
if (fetchImage) {
return <div/>;
}
const isDismissible = (dismissible && buttons.length === 0);
const notificationClass = [
className.main,
Expand All @@ -197,7 +213,6 @@ export class Notification extends Component {
if (timer) {
this._resumeTimer();
}

return (
<div
className={className.wrapper}
Expand Down Expand Up @@ -254,4 +269,4 @@ export class Notification extends Component {
}
}

export default connect(null, {removeNotification})(Notification);
export default connect(null, {removeNotification, imageLoaded})(Notification);
1 change: 1 addition & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function treatNotification(notification) {
}
if (notification.image) {
notification.status = STATUS.default;
notification.fetchImage = true; // directs the component to wait for image to be fetched
}
else {
notification.status = convertStatus(notification.status);
Expand Down
67 changes: 37 additions & 30 deletions src/store/notifications.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {treatNotification, preloadImage} from '../helpers';
import {treatNotification} from '../helpers';
import {DEFAULT_NOTIFICATION} from '../constants';

// An array to store notifications object
Expand All @@ -8,6 +8,7 @@ const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
const UPDATE_NOTIFICATION = 'UPDATE_NOTIFICATION';
const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION';
const REMOVE_NOTIFICATIONS = 'REMOVE_NOTIFICATIONS';
const IMAGE_LOADED = 'IMAGE_LOADED';

/**
* Add a notification (thunk action creator)
Expand All @@ -17,20 +18,13 @@ const REMOVE_NOTIFICATIONS = 'REMOVE_NOTIFICATIONS';
* @param {Object} notification
* @returns {Object} notification
*/
export const addNotification = (notification) => (dispatch) => {
export const addNotification = (notification) => {
if (!notification.id) {
notification.id = new Date().getTime();
}
notification = treatNotification(notification);
// if there is an image, we preload it
// and add notification when image is loaded
if (notification.image) {
preloadImage(notification.image, dispatch.bind(this, _addNotification(notification)));
}
else {
dispatch(_addNotification(notification));
}
return notification;

return _addNotification(notification);
};

/**
Expand All @@ -55,27 +49,13 @@ function _addNotification(notification) {
* @param {Object} notification
* @returns {Object} notification
*/
export const updateNotification = (notification) => (dispatch, getState) => {
export const updateNotification = (notification) => {
if (!notification.id) {
throw new Error('A notification must have an `id` property to be updated');
}

const notifications = getState().notifications;
const index = notifications.findIndex((oldNotification) => oldNotification.id === notification.id);
const currNotification = notifications[index];

notification = treatNotification(notification);

// if image is different, then we preload it
// and update notification when image is loaded
if (notification.image && (!currNotification.image || (currNotification.image &&
notification.image !== currNotification.image))) {
preloadImage(notification.image, dispatch.bind(this, _updateNotification(notification)));
}
else {
dispatch(_updateNotification(notification));
}
return notification;
return _updateNotification(notification);
};

/**
Expand All @@ -92,6 +72,20 @@ function _updateNotification(notification) {
};
}

export const imageLoaded = (notification) => {
if (!notification.id) {
throw new Error('A notification must have an `id` property to be updated');
}
return _imageLoaded(notification);
};

export function _imageLoaded(notification) {
return {
type: IMAGE_LOADED,
payload: notification
};
}

/**
* Remove a notification (action creator)
*
Expand Down Expand Up @@ -140,11 +134,24 @@ export default (defaultNotification = DEFAULT_NOTIFICATION) => {
const notification = Object.assign({}, defaultNotification, payload);
return [...state, notification];
case UPDATE_NOTIFICATION:
{
// get index of the notification
const index = state.findIndex((notification) => notification.id === payload.id);
const index = state.findIndex((notification) => notification.id === payload.id);
// replace the old notification by the new one
state[index] = Object.assign({}, defaultNotification, payload);
return [...state];
state[index] = Object.assign({}, defaultNotification, payload);
return [...state];
}
case IMAGE_LOADED:
{
const modifiedPayload = Object.assign({}, payload, {
fetchImage: false
});
// get index of the notification
const index = state.findIndex((notification) => notification.id === modifiedPayload.id);
// replace the old notification by the new one
state[index] = Object.assign({}, defaultNotification, modifiedPayload);
return [...state];
}
case REMOVE_NOTIFICATION:
return state.filter((notification) => notification.id !== payload);
case REMOVE_NOTIFICATIONS:
Expand Down