Лекция 28
const FileConstants = {
FILE_ADD: 'FILE_ADD',
FILE_UPDATE_CONTENT: 'FILE_UPDATE_CONTENT',
COMPILE_AUTO: 'COMPILE_AUTO'
};
export default FileConstants;
import Dispatcher from '../dispatcher';
import FileConstants from '../constants/file';
import Api from '../api';
const FileActions = {
add: function (name) {
Dispatcher.dispatch({
actionType: FileConstants.FILE_ADD,
name: name
});
},
...
};
export default FileActions;
const FileActions = {
...
compile: function (files, resultId) {
Api.compile(files).then((result) => {
Dispatcher.dispatch({
actionType: FileConstants.FILE_UPDATE_CONTENT,
id: resultId,
content: result
});
});
}
};
export default FileActions;
const Dispatcher = {
subscribers: [],
subscribe: function (handler) {
this.subscribers.push(handler);
},
dispatch: function (action) {
for (let i = 0; i < this.subscribers.length; i++)
this.subscribers[i](action);
}
};
export default Dispatcher;
class Store {
constructor() {
this.seed = 0;
this.data = [];
this.subscribers = [];
}
getAll() {
return this.data;
}
...
}
export default Store;
class Store {
...
getOne(id) {
return this.data.find(x => x.id);
}
add(item) {
item.id = Date.now() + this.seed++;
this.data.push(item);
}
...
}
class Store {
...
update(item) {
const index = this.data
.findIndex(x => x.id === item.id);
this.data[index] = Object.assign({}, this.data[index], item);
}
remove(id) {
const index = this.data
.findIndex(x => x.id === id);
this.data.splice(index, 1);
}
...
}
class Store {
...
emit() {
for (let i = 0; i < this.subscribers.length; i++){
this.subscribers[i]();
}
}
subscribe(handler) {
this.subscribers.push(handler);
}
unsubscribe(handler) {
const index = this.subscribers.indexOf(handler);
this.subscribers.splice(index, 1);
}
}
class FileStore extends Store {
find(name) {
return this.data.find(x => x.name === name);
}
}
const fileStore = new FileStore();
...
export default fileStore;
Dispatcher.subscribe((action) => {
switch (action.actionType) {
case FileConstants.FILE_ADD: {
let file = fileStore.find(action.name);
if (!file) return;
file = { name: action.name, content: '' };
fileStore.add(file);
break;
}
...
}
fileStore.emit();
});
Dispatcher.subscribe((action) => {
switch (action.actionType) {
...
case FileConstants.FILE_UPDATE_CONTENT: {
fileStore.update({
id: action.id,
content: action.content
});
break;
}
default: return;
}
fileStore.emit();
});
import React from 'react';
import FileActions from './actions/file';
import FileStore from './stores/file';
class App extends React.Component {
render() {
return (<div className="app-root">
<Tabs tabs={this.state.tabs}/>
<Textbox file={this.state.currentFile}
compile={this._compile}/>
</div>);
}
...
});
class App extends React.Component {
...
constructor(props) {
super(props);
this._onChange = ::this._onChange;
this._compile = ::this._onChange;
this.state = App.getState();
}
...
});
class App extends React.Component {
...
componentDidMount() {
TabStore.subscribe(this._onChange);
FileStore.subscribe(this._onChange);
}
componentWillUnmount() {
TabStore.unsubscribe(this._onChange);
FileStore.unsubscribe(this._onChange);
}
...
});
class App extends React.Component {
...
_onChange() {
this.setState(
App.getState()
);
}
_compile() {
FileActions.compile(
this.state.files
);
}
...
});
class App extends React.Component {
...
static getState() {
return {
tabs: TabStore.getAll(),
files: FileStore.getAll(),
currentFile: FileStore.getOne(
TabStore.getCurrent().id
)
};
}
});
const TodoConstants = {
TODO_INIT: 'TODO_INIT',
TODO_ADD: 'TODO_ADD',
TODO_REMOVE: 'TODO_REMOVE',
TODO_UPDATE: 'TODO_UPDATE',
TODO_TOGGLE: 'TODO_TOGGLE',
TODO_TOGGLE_ALL: 'TODO_TOGGLE_ALL',
TODO_CLEAR: 'TODO_CLEAR'
};
const NavConstants = {
NAV_INIT: 'NAV_INIT',
NAV_ACTIVATE: 'NAV_ACTIVATE'
};
const TodoActions = {
init: function () {
TodoActions.add('Sleep');
TodoActions.add('Eat');
TodoActions.add('Code');
TodoActions.add('Repeat');
},
add: function (text) {
Dispatcher.dispatch({
actionType: TodoConstants.TODO_ADD,
text: text
});
},
...
};
const TodoActions = {
...
remove: function (id) {
Dispatcher.dispatch({
actionType: TodoConstants.TODO_REMOVE,
id: id
});
},
update: function (id, text) {
Dispatcher.dispatch({
actionType: TodoConstants.TODO_UPDATE,
id: id,
text: text
});
},
...
};
const TodoActions = {
...
toggle: function (id) {
Dispatcher.dispatch({
actionType: TodoConstants.TODO_TOGGLE,
id: id
});
},
toggleAll: function () {
Dispatcher.dispatch({
actionType: TodoConstants.TODO_TOGGLE_ALL
});
},
...
};
const TodoActions = {
...
clear: function () {
Dispatcher.dispatch({
actionType: TodoConstants.TODO_CLEAR
});
}
};
const NavActions = {
init: function () {
Dispatcher.dispatch({
actionType: NavConstants.NAV_INIT
});
},
activate: function (link) {
Dispatcher.dispatch({
actionType: NavConstants.NAV_ACTIVATE,
link: link
});
}
};
class TodoStore {
constructor() {...}
getActiveCount() {...}
getCompletedCount() {...}
areAllCompleted() {...}
addItem(text) {...}
removeItem(id) {...}
removeCompleted() {...}
updateItem(id, text) {...}
toggleItem(id) {...}
switchAllTo(completed) {...}
emit() {...}
subscribe(handler) {...}
unsubscribe(handler) {...}
...
}
class TodoStore {
...
getItems() {
if (!this.activeLink
|| this.activeLink.title === 'All'
) {
return this.list;
} else if (
this.activeLink.title === 'Completed'
) {
return this.list.filter(x => x.completed);
} else {
return this.list.filter(x => !x.completed);
}
}
}
const todoStore = new TodoStore();
Dispatcher.subscribe((action) => {
const type = action.actionType;
if (type === TodoConstants.TODO_ADD) {
todoStore.addItem(action.text);
} else if (type === TodoConstants.TODO_REMOVE) {
todoStore.removeItem(action.id);
} else if (type === TodoConstants.TODO_UPDATE) {
todoStore.updateItem(action.id, action.text);
} else if (type === TodoConstants.TODO_TOGGLE) {
todoStore.toggleItem(action.id);
} else if ...
});
Dispatcher.subscribe((action) => {
... else if (type
=== TodoConstants.TODO_TOGGLE_ALL) {
todoStore.toggleAll(
!todoStore.areAllCompleted()
);
} else if (type === TodoConstants.TODO_CLEAR) {
todoStore.removeCompleted();
} else if (type === NavConstants.NAV_ACTIVATE) {
todoStore.activeLink = action.link;
return;
} else return;
todoStore.emit();
});
class NavStore {
constructor() {...}
getLinks() {...}
getActive() {...}
setActive(link) {...}
emit() {...}
subscribe(handler) {...}
unsubscribe(handler) {...}
}
const navStore = new NavStore();
Dispatcher.subscribe((action) => {
const type = action.actionType;
if (type === NavConstants.NAV_INIT){
navStore.setActive(navStore.links[0]);
} else if (type === NavConstants.NAV_ACTIVATE) {
navStore.setActive(action.link);
} else return;
navStore.emit();
});
class ToDo extends React.Component {
render() {...}
...
}
constructor(props) {
super(props);
TodoActions.init();
NavActions.init();
this.state = ToDo.getState();
}
componentDidMount() {
todoStore.subscribe(this._rerender);
navStore.subscribe(this._rerender);
}
componentWillUnmount() {
todoStore.unsubscribe(this._rerender);
navStore.unsubscribe(this._rerender);
}
static getState() {
return {
remains: todoStore.getActiveCount(),
completed: todoStore.getCompletedCount(),
areAllCompleted: todoStore.areAllCompleted(),
tasks: todoStore.getItems(),
links: navStore.getLinks(),
activeLink: navStore.getActive()
};
}
_rerender = () => {
this.setState(ToDo.getState());
}
_toggleItem(id) {
TodoActions.toggle(id);
}
_toogleAll() {
TodoActions.toggleAll();
}
_removeItem(id) {
TodoActions.remove(id);
}
_addItem(text) {
TodoActions.add(text);
}
_updateItem(id, text) {
TodoActions.update(id, text);
}
_removeCompleted() {
TodoActions.clear();
}
_navigate(link) {
NavActions.activate(link);
}
npm install --save flux
<script src=
"https://unpkg.com/flux@3.1.2/dist/Flux.js"
></script>
<script src=
"https://unpkg.com/flux@3.1.2/dist/FluxUtils.js"
></script>
// or use Flux object if in sandbox
import { Dispatcher } from 'flux';
const dispatcher = new Dispatcher();
register(function callback): string
unregister(string id): void
waitFor(array<string> ids): void
dispatch(object payload): void
isDispatching(): boolean
const flightDispatcher = new Dispatcher();
const CountryStore = {country: null};
const CityStore = {city: null};
const capitals = {
'france': 'paris',
'usa': 'washington',
'italy': 'rome'
};
...
...
CityStore.dispatchToken
= flightDispatcher.register((payload) => {
const type = payload.actionType;
if (type === 'city-update') {
CityStore.city = payload.selectedCity;
} else if (type === 'country-update') {
flightDispatcher
.waitFor([CountryStore.dispatchToken]);
CityStore.city
= capitals[CountryStore.country];
}
});
...
...
CountryStore.dispatchToken
= flightDispatcher.register((payload) => {
if (payload.actionType === 'country-update') {
CountryStore.country
= payload.selectedCountry;
}
});
...
...
flightDispatcher.dispatch({
actionType: 'country-update',
selectedCountry: 'usa'
});
flightDispatcher.dispatch({
actionType: 'city-update',
selectedCity: 'new york'
});
// or use FluxUtils object if in sandbox
import { Store } from 'flux/utils';
class MyStore extends Store {...}
constructor(dispatcher: Dispatcher)
addListener(callback: Function)
: {remove: Function}
getDispatcher(): Dispatcher
getDispatchToken(): DispatchToken
hasChanged(): boolean
__emitChange(): void
__onDispatch(payload: Object): void
const flightDispatcher = new Flux.Dispatcher();
const capitals = {
'france': 'paris',
'usa': 'washington',
'italy': 'rome'
};
...
...
class CountryStore extends FluxUtils.Store {
constructor(dispatcher) {
super(dispatcher);
this.country = null;
}
__onDispatch(payload) {
if (payload.actionType === 'country-update') {
this.country = payload.selectedCountry;
}
}
getCountry() {
return this.country;
}
}
...
...
class CityStore extends FluxUtils.Store {
constructor(dispatcher, countryStore, capitals) {
super(dispatcher);
this.city = null;
this.countryStore = countryStore;
this.capitals = capitals;
}
getCity() {
return this.city;
}
...
}
...
...
__onDispatch(payload) {
const type = payload.actionType;
if (type === 'city-update') {
this.city = payload.selectedCity;
} else if (type === 'country-update') {
this.getDispatcher().waitFor([
this.countryStore.getDispatchToken()
]);
this.city = this.capitals[
this.countryStore.getCountry()
];
}
}
...
...
const countryStore = new CountryStore(
flightDispatcher
);
const cityStore = new CityStore(
flightDispatcher,
countryStore,
capitals
);
...
...
flightDispatcher.dispatch({
actionType: 'country-update',
selectedCountry: 'usa'
});
flightDispatcher.dispatch({
actionType: 'city-update',
selectedCity: 'new york'
});
// or use FluxUtils object if in sandbox
import { ReduceStore } from 'flux/utils';
class MyStore extends ReduceStore {...}
extends Store
getState(): T
getInitialState(): T
reduce(state: T, action: Object): T
areEqual(one: T, two: T): boolean
// or use FluxUtils object if in sandbox
import {Component} from 'react';
import {Container} from 'flux/utils';
class CounterContainer extends Component {
...
}
const container = Container.create(
CounterContainer
);
class CounterContainer extends Component {
static getStores() {
return [CounterStore];
}
static calculateState(prevState) {
return {
counter: CounterStore.getState(),
};
}
render() {
return <CounterUI counter={this.state.counter} />;
}
}
npm install --save redux
<script src=
"https://unpkg.com/redux@3.7.2/dist/redux.js"
></script>
// or use 'Redux' object if in sandbox
import { createStore } from 'redux';
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
...
...
let store = createStore(counter);
store.subscribe(() =>
console.log(store.getState())
);
// 0
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
function humans(state = [], action) {
switch (action.type) {
case 'HUMANS_ADD':
return [
...state,
{ name: action.name }
];
}
return state;
}
...
...
function robots(state = [], action) {
switch (action.type) {
case 'ROBOTS_ADD':
return [
...state,
{
name: action.name,
manufacturer: action.manufacturer
}
];
}
return state;
}
...
...
const reducer = Redux.combineReducers({
humans,
robots
});
const store = Redux.createStore(reducer);
store.subscribe(() =>
console.log(store.getState())
);
...
...
store.dispatch({
type: 'HUMANS_ADD',
name: 'Fry' });
store.dispatch({
type: 'HUMANS_ADD',
name: 'Leela' });
store.dispatch({
type: 'ROBOTS_ADD',
name: 'Bender',
manufacturer: 'MomCorp' });
const store = Redux.createStore(reducer, {
humans: [{ name: 'Pr. Farnsworth' }]
});
const TodoConstants = {
TODO_ADD: 'TODO_ADD',
TODO_REMOVE: 'TODO_REMOVE',
TODO_UPDATE: 'TODO_UPDATE',
TODO_TOGGLE: 'TODO_TOGGLE',
TODO_TOGGLE_ALL: 'TODO_TOGGLE_ALL',
TODO_CLEAR: 'TODO_CLEAR'
};
const NavConstants = {
NAV_ACTIVATE: 'NAV_ACTIVATE'
};
const TodoActions = {
add: function (text) {
return { type: TodoConstants.TODO_ADD, text };
},
remove: function (id) {
return { type: TodoConstants.TODO_REMOVE, id };
},
update: function (id, text) {
return { type: TodoConstants.TODO_UPDATE,
id, text};
},
...
};
const TodoActions = {
...
toggle: function (id) {
return { type: TodoConstants.TODO_TOGGLE, id };
},
toggleAll: function (completed) {
return { type: TodoConstants.TODO_TOGGLE_ALL,
completed };
},
clear: function () {
return { type: TodoConstants.TODO_CLEAR };
}
};
const NavActions = {
activate: function (link) {
return {
type: NavConstants.NAV_ACTIVATE,
link: link
};
}
};
function tasks(state = [], action) {
switch (action.type) {
...
default:
return state;
}
}
case TodoConstants.TODO_ADD:
return [
...state,
{
id: Date.now() + state.length,
text: action.text,
completed: false
}
];
case TodoConstants.TODO_REMOVE:
return state.filter(
item => item.id !== action.id
);
case TodoConstants.TODO_UPDATE:
return state.map(item => {
if (item.id !== action.id) return item;
return {
...item,
text: action.text
};
});
case TodoConstants.TODO_TOGGLE:
return state.map(item => {
if (item.id !== action.id) return item;
return {
...item,
completed: !item.completed
};
});
case TodoConstants.TODO_TOGGLE_ALL:
return state.map(item => {
return {
...item,
completed: action.completed
};
});
case TodoConstants.TODO_CLEAR:
return state.filter(item => !item.completed);
function activeLink(state = {}, action) {
switch (action.type) {
case NavConstants.NAV_ACTIVATE:
return action.link;
default:
return state;
}
}
function links(state = []) {
return state;
}
const reducer = Redux.combineReducers({
tasks,
activeLink,
links
});
const store = Redux.createStore(reducer, {
tasks: [
{ id: 1, text: 'Sleep', completed: true },
{ id: 2, text: 'Eat', completed: false },
{ id: 3, text: 'Code', completed: false },
{ id: 4, text: 'Repeat', completed: false }
],
links: [
{ title: 'All' },
{ title: 'Active' },
{ title: 'Completed' }
],
activeLink: { title: 'All' }
});
class ToDo extends React.Component {
constructor(props) {
super(props);
this.state = ToDo.getState();
this.state.subscription = store.subscribe(
this._rerender
);
}
render: function () {...}
...
}
componentWillUnmount() {
this.state.subscription.unsubscribe();
}
static getState() {
const state = { ...store.getState() };
state.completed = state.tasks.filter(x => x.completed).length;
state.remains = state.tasks.length - state.completed;
state.areAllCompleted = state.remains === 0;
if (state.activeLink.title === 'Completed') {
state.tasks = state.tasks.filter(x => x.completed);
} else if (state.activeLink.title === 'Active') {
state.tasks = state.tasks.filter(x => !x.completed);
}
return state;
}
_rerender = () => {
this.setState(ToDo.getState());
}
_toggleItem(id) {
store.dispatch(
TodoActions.toggle(id)
);
}
_toogleAll = () => {
store.dispatch(
TodoActions.toggleAll(!this.state.areAllCompleted)
);
}
_removeItem(id) {
store.dispatch(
TodoActions.remove(id)
);
}
_addItem(text) {
store.dispatch(
TodoActions.add(text)
);
}
_updateItem(id, text) {
store.dispatch(
TodoActions.update(id, text)
);
}
_removeCompleted() {
store.dispatch(
TodoActions.clear()
);
}
_navigate(link) {
store.dispatch(
NavActions.activate(link)
);
}
npm install --save react-redux
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.js"
></script>
function getVisibleTasks(tasks, link) {
if (link.title === 'All') {
return tasks;
} else if (link.title === 'Completed') {
return tasks.filter(x => x.completed);
} else {
return tasks.filter(x => !x.completed);
}
}
function getCompletedCount(tasks) {
return tasks.filter(x => x.completed).length;
}
function getRemainsCount(tasks) {
return tasks.filter(x => !x.completed).length;
}
function areAllCompleted(tasks) {
return getRemainsCount(tasks) === 0;
}
const mapStateToProps = (state) => {
return {
tasks: getVisibleTasks(
state.tasks,
state.activeLink
),
completed: getCompletedCount(state.tasks),
remains: getRemainsCount(state.tasks),
areAllCompleted: areAllCompleted(state.tasks),
activeLink: state.activeLink,
links: state.links
}
};
const mapDispatchToProps = (dispatch) => {
return {
toggleItem: (id) => {
dispatch(
TodoActions.toggle(id)
);
},
toggleAll: (status) => {
dispatch(
TodoActions.toggleAll(status)
);
},
...
};
};
const mapDispatchToProps = (dispatch) => {
return {
...
removeItem: (id) => {
dispatch(
TodoActions.remove(id)
);
},
addItem: (text) => {
dispatch(
TodoActions.add(text)
);
},
...
};
};
const mapDispatchToProps = (dispatch) => {
return {
...
updateItem: (id, text) => {
dispatch(
TodoActions.update(id, text)
);
},
removeCompleted: () => {
dispatch(
TodoActions.clear()
);
},
...
};
};
const mapDispatchToProps = (dispatch) => {
return {
...
navigate: (link) => {
dispatch(
NavActions.activate(link)
);
}
};
};
class ToDo extends React.Component {
render() {
return (...);
},
_toggleAll = () => {
this.props.toggleAll(
!this.props.areAllCompleted
);
}
});
<div className="todo">
<div className="todo__title">
React Redux ToDo
</div>
<Nav
links={this.props.links}
activeLink={this.props.activeLink}
navigate={this.props.navigate}
/>
...
</div>
<div className="todo">
...
<ToDoSummary
remains={this.props.remains}
completed={this.props.completed}
/>
...
</div>
<div className="todo">
...
<ToDoList
tasks={this.props.tasks}
areAllComplete={this.props.areAllCompleted}
toggleItem={this.props.toggleItem}
toggleAll={this._toggleAll}
removeItem={this.props.removeItem}
updateItem={this.props.updateItem}
/>
...
</div>
<div className="todo">
...
<ToDoForm
addItem={this.props.addItem}
/>
<ToDoClear
removeCompleted={this.props.removeCompleted}
/>
</div>
const ToDoContainer = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(ToDo);
ReactDOM.render(
<ReactRedux.Provider store={store}>
<ToDoContainer/>
</ReactRedux.Provider>,
document.getElementById('app')
);
export function fetchPosts(subreddit) {
return function (dispatch) {
dispatch(requestPosts(subreddit));
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
error => console.log('An error occurred.', error)
)
.then(json =>
dispatch(receivePosts(subreddit, json))
)
}
}
// logging, reports, side-effects, ...
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
...
const crashReporter = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
// TODO: report error
throw err
}
}
import {
createStore,
combineReducers,
applyMiddleware
} from 'redux'
const todoApp = combineReducers(reducers)
const store = createStore(
todoApp,
applyMiddleware(logger, crashReporter)
)
// simple state
{
counter: 10
}
// state for undo/redo feature
{
counter: {
past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
present: 10,
future: []
}
}
// UNDO X 1
{
counter: {
past: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ],
present: 9,
future: [ 10 ]
}
}
// UNDO X 2
{
counter: {
past: [ 0, 1, 2, 3, 4, 5, 6, 7 ],
present: 8,
future: [ 9, 10 ]
}
}
{
past: Array<T>,
present: T,
future: Array<T>
}
// Global Undo/Redo
{
past: [
{ counterA: 1, counterB: 1 },
{ counterA: 1, counterB: 0 },
{ counterA: 0, counterB: 0 }
],
present: { counterA: 2, counterB: 1 },
future: []
}
// Separate Undo/Redo
{
counterA: {
past: [ 1, 0 ],
present: 2,
future: []
},
counterB: {
past: [ 0 ],
present: 1,
future: []
}
}
npm install --save redux-undo
<script src=
"https://unpkg.com/redux-undo@1.0.0-beta7/dist/redux-undo.js"
></script>
import undoable from 'redux-undo';
undoable(reducer)
undoable(reducer, config)
import { ActionCreators } from 'redux-undo';
store.dispatch(ActionCreators.undo());
ActionCreators.undo()
ActionCreators.redo()
ActionCreators.jump(-2) // undo 2 steps
ActionCreators.jump(5) // redo 5 steps
ActionCreators.jumpToPast(index)
ActionCreators.jumpToFuture(index)
ActionCreators.clearHistory()
undoable(reducer, {
limit: false,
filter: () => true,
debug: false,
neverSkipReducer: false,
undoType: ActionTypes.UNDO,
redoType: ActionTypes.REDO,
jumpType: ActionTypes.JUMP,
jumpToPastType: ActionTypes.JUMP_TO_PAST,
jumpToFutureType: ActionTypes.JUMP_TO_FUTURE,
clearHistoryType: ActionTypes.CLEAR_HISTORY
})
const reducer = Redux.combineReducers({
tasks: ReduxUndo.default(tasks),
activeLink,
links
});
const store = Redux.createStore(reducer, {
tasks: {
past: [],
present: [...],
future: []
},
...
});
class TimeTravelPanel extends React.Component {
render() {
return (<div className="time-panel">
<div className={'time-panel__button '
+ (this.props.canUndo ? ''
: 'time-panel__button_disabled')}
onClick={this.props.undo}>Undo</div>
<div className={'time-panel__button '
+ (this.props.canRedo ? ''
: 'time-panel__button_disabled')}
onClick={this.props.redo}>Redo</div>
</div>);
}
}
class ToDo extends React.Component {
render() {
return (
<div className="todo">
...
<TimeTravelPanel
undo={this.props.undo}
redo={this.props.redo}
canUndo={this.props.canUndo}
canRedo={this.props.canRedo}
/>
</div>
);
}
...
});
function canUndo(model) {
return model.past
&& model.past.length !== 0;
}
function canRedo(model) {
return model.future
&& model.future.length !== 0;
}
const mapStateToProps = (state) => {
return {
tasks: getVisibleTasks(
state.tasks.present,
state.activeLink
),
completed: getCompletedCount(
state.tasks.present
),
remains: getRemainsCount(
state.tasks.present
),
...
};
};
const mapStateToProps = (state) => {
return {
...
areAllCompleted: areAllCompleted(
state.tasks.present
),
activeLink: state.activeLink,
links: state.links,
canUndo: canUndo(state.tasks),
canRedo: canRedo(state.tasks)
};
};
const mapDispatchToProps = (dispatch) => {
return {
...
undo: () => {
dispatch(
ReduxUndo.ActionCreators.undo()
);
},
redo: () => {
dispatch(
ReduxUndo.ActionCreators.redo()
);
}
};
};