Playing with sagas for a stuf implementation of tasks

This commit is contained in:
2019-10-24 04:12:49 +02:00
parent feda28ec47
commit 64b0cdc951
10 changed files with 245 additions and 31 deletions

View File

@@ -1,13 +1,16 @@
import { AppState } from "@kredens/frontend/store";
import { deleteTask, scheduleTask } from "@kredens/frontend/store/tasks/actions";
import { Task, TaskScheduleType } from "@kredens/frontend/store/tasks/types";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
export default () => {
const [taskName, setTaskName] = useState("");
const tasks = useSelector<AppState, { [key: string]: Task }>(state => state.tasks.items);
const dispatch = useDispatch();
useEffect(() => {
dispatch({type: "FETCH_TASKS"});
}, [])
const onTaskAddClick = () => {
dispatch(scheduleTask(Math.random().toString(36), {

View File

@@ -1,4 +1,7 @@
import { combineReducers, createStore } from "redux";
import { applyMiddleware, combineReducers, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./sagas";
import { tasksReducer } from "./tasks/reducers";
const rootReducer = combineReducers({
@@ -7,8 +10,14 @@ const rootReducer = combineReducers({
export type AppState = ReturnType<typeof rootReducer>;
const sagaMiddleware = createSagaMiddleware();
export default function configureStore() {
const store = createStore(rootReducer);
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
return store;
}

View File

@@ -0,0 +1,35 @@
import { all, call, put, takeEvery } from "redux-saga/effects";
import { getTasks, Task as APITask } from "../api/tasks";
import { taskFetchError, taskFetchOk, taskFetchStart } from "./tasks/actions";
import { Task, TaskQuery, TaskScheduleType } from "./tasks/types";
export function* fetchTasksSaga(query: TaskQuery = { limit: 10 }) {
yield put(taskFetchStart(query));
try {
const tasks: APITask[] = yield call(getTasks);
yield put(
taskFetchOk(
query,
tasks
.map<[string, Task]>(t => [
t.id.toString(),
{
name: t.name,
schedule: {
type: TaskScheduleType.Once,
due: t.due.toISO()
}
}
])
.reduce((res, [id, task]) => ({ ...res, [id]: task }), {})
)
);
} catch (error) {
yield put(taskFetchError(query, `${error}`));
}
}
export default function* rootSaga() {
yield all([yield takeEvery("FETCH_TASKS", fetchTasksSaga)]);
}

View File

@@ -1,4 +1,5 @@
import { Task, TasksAction, TasksActionType } from "./types";
import { DateTime } from "luxon";
import { Task, TaskQuery, TasksAction, TasksActionType } from "./types";
export function scheduleTask(id: string, task: Task): TasksActionType {
return {
@@ -14,3 +15,35 @@ export function deleteTask(id: string): TasksActionType {
id
};
}
export function taskFetchStart(query: TaskQuery): TasksActionType {
return {
type: TasksAction.TASKS_FETCH_START,
query,
started: DateTime.utc().toISO()
};
}
export function taskFetchOk(
query: TaskQuery,
results: { [key: string]: Task }
): TasksActionType {
return {
type: TasksAction.TASKS_FETCH_OK,
query,
results,
fetched: DateTime.utc().toISO()
};
}
export function taskFetchError(
query: TaskQuery,
error: string
): TasksActionType {
return {
type: TasksAction.TASKS_FETCH_ERROR,
query,
error,
fetched: DateTime.utc().toISO()
};
}

View File

@@ -1,7 +1,9 @@
import objectHash from "object-hash";
import { TasksAction, TasksActionType, TasksState } from "./types";
const initialState: TasksState = {
items: {}
items: {},
queries: {}
};
export function tasksReducer(
@@ -24,6 +26,45 @@ export function tasksReducer(
.filter(([key]) => key !== action.id)
.reduce((res, [key, task]) => ({ ...res, [key]: task }), {})
};
case TasksAction.TASKS_FETCH_START:
return {
...state,
queries: {
...state.queries,
[objectHash(action.query)]: {
result: "fetching",
started: action.started
}
}
};
case TasksAction.TASKS_FETCH_OK:
return {
...state,
items: {
...state.items,
...action.results
},
queries: {
...state.queries,
[objectHash(action.query)]: {
result: "ok",
items: Object.keys(action.results),
fetched: action.fetched
}
}
};
case TasksAction.TASKS_FETCH_ERROR:
return {
...state,
queries: {
...state.queries,
[objectHash(action.query)]: {
result: "error",
error: action.error,
fetched: action.fetched
}
}
};
default: {
return state;
}

View File

@@ -19,26 +19,82 @@ export interface Task {
schedule: TaskSchedule;
}
export interface TaskQuery {
query?: string;
after?: string;
limit: number;
}
interface TaskQueryOk {
result: "ok";
items: string[];
fetched: string;
}
interface TaskQueryFetching {
result: "fetching";
started: string;
}
interface TaskQueryError {
result: "error";
error: string;
fetched: string;
}
export type TaskQueryResult = TaskQueryOk | TaskQueryFetching | TaskQueryError;
export interface TasksState {
items: {
[key: string]: Task;
};
queries: {
[key: string]: TaskQueryResult;
};
}
export enum TasksAction {
SCHEDULE_TASK = "SCHEDULE_TASK",
DELETE_TASK = "DELETE_TASK"
DELETE_TASK = "DELETE_TASK",
TASKS_FETCH_START = "TASKS_FETCH_START",
TASKS_FETCH_OK = "TASKS_FETCH_OK",
TASKS_FETCH_ERROR = "TASKS_FETCH_ERROR"
}
interface ScheduleTaskAction {
type: typeof TasksAction.SCHEDULE_TASK;
type: TasksAction.SCHEDULE_TASK;
id: string;
task: Task;
}
interface DeleteTaskAction {
type: typeof TasksAction.DELETE_TASK;
type: TasksAction.DELETE_TASK;
id: string;
}
export type TasksActionType = ScheduleTaskAction | DeleteTaskAction;
interface TaskFetchStartAction {
type: TasksAction.TASKS_FETCH_START;
query: TaskQuery;
started: string;
}
interface TaskFetchOkAction {
type: TasksAction.TASKS_FETCH_OK;
query: TaskQuery;
results: { [key: string]: Task };
fetched: string;
}
interface TaskFetchErrorAction {
type: TasksAction.TASKS_FETCH_ERROR;
query: TaskQuery;
error: string;
fetched: string;
}
export type TasksActionType =
| ScheduleTaskAction
| DeleteTaskAction
| TaskFetchOkAction
| TaskFetchStartAction
| TaskFetchErrorAction;

View File

@@ -1,6 +1,15 @@
{
"extends": "../../tslint.json",
"rules": {
"no-implicit-dependencies": [true, "dev", ["@kredens/frontend"]]
}
}
"extends": "../../tslint.json",
"rules": {
"no-implicit-dependencies": [
true,
"dev",
["@kredens/frontend"]
],
"no-submodule-imports": [
true,
"@kredens/frontend",
"redux-saga/effects"
]
}
}