Skip to content

Commit

Permalink
refactor to rtk-query (#26)
Browse files Browse the repository at this point in the history
* refactor to rtk-query

* remove gon

* add sorting tasks
  • Loading branch information
frontstall committed Aug 6, 2021
1 parent c086e32 commit 2d6a78a
Show file tree
Hide file tree
Showing 20 changed files with 231 additions and 200 deletions.
11 changes: 10 additions & 1 deletion server/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default (app, defaultState = {}) => {

app
.get('/', (_req, reply) => {
reply.view('index.pug', { gon: state });
reply.view('index.pug');
})
.post('/api/v1/lists', (req, reply) => {
const { name } = req.body;
Expand Down Expand Up @@ -79,5 +79,14 @@ export default (app, defaultState = {}) => {
state.tasks = state.tasks.filter((t) => t.id !== taskId);

reply.code(204).send();
})
.get('/api/v1/lists', (req, reply) => {
reply.code(200).send(state.lists);
})
.get('/api/v1/lists/:id/tasks', (req, reply) => {
const tasks = state.tasks.filter(
({ listId }) => listId === Number(req.params.id)
);
reply.code(200).send(tasks);
});
};
2 changes: 0 additions & 2 deletions server/views/index.pug
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ html.h-100(lang="en")
meta(name='viewport', content='width=device-width, initial-scale=1')
link(href=assetPath('main.css') rel="stylesheet")
meta(name='viewport', content='width=device-width, initial-scale=1')
script.
var gon = !{JSON.stringify(gon)};
body
#root
script(src=assetPath('main.js'))
12 changes: 7 additions & 5 deletions src/api/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
const host = '';
const prefix = 'api/v1';

export const baseUrl = [host, prefix].join('/');

const routes = {
lists: () => [host, prefix, 'lists'].join('/'),
tasks: () => [host, prefix, 'tasks'].join('/'),
listTasks: (id) => [host, prefix, 'lists', id, 'tasks'].join('/'),
list: (id) => [host, prefix, 'lists', id].join('/'),
task: (id) => [host, prefix, 'tasks', id].join('/'),
lists: () => ['lists'].join('/'),
tasks: () => ['tasks'].join('/'),
listTasks: (id) => ['lists', id, 'tasks'].join('/'),
list: (id) => ['lists', id].join('/'),
task: (id) => ['tasks', id].join('/'),
};

export default routes;
21 changes: 2 additions & 19 deletions src/app/init.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@

import React from 'react';
import { Provider } from 'react-redux';
import keyBy from 'lodash/keyBy.js';
import { setLocale } from 'yup';

import adapter from '../store/adapter.js';
import createStore from '../store/index.js';
import store from '../store/index.js';
import App from './App.jsx';

const normalize = (entities) => keyBy(entities, 'id');

const init = (preloadedState) => {
const init = () => {
setLocale({
mixed: {
required: 'Required!',
Expand All @@ -23,19 +19,6 @@ const init = (preloadedState) => {
},
});

const normalizedStore = {
currentListId: preloadedState.currentListId,
tasks: adapter.upsertMany(
adapter.getInitialState(),
normalize(preloadedState.tasks)
),
lists: adapter.upsertMany(
adapter.getInitialState(),
normalize(preloadedState.lists)
),
};
const store = createStore(normalizedStore);

const vdom = (
<Provider store={store}>
<App />
Expand Down
3 changes: 3 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const defaultListId = 1;

export default defaultListId;
24 changes: 6 additions & 18 deletions src/features/lists/List.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
// @ts-check

import React, { useRef, useState } from 'react';
import React, { useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import axios from 'axios';
import cn from 'classnames';
import { toast } from 'react-toastify';
import { BsX } from 'react-icons/bs';
Expand All @@ -12,18 +11,12 @@ import {
setCurrentListId,
selectCurrentListId,
} from '../../store/currentListIdSlice.js';
import { listsActions } from './listsSlice.js';
import routes from '../../api/routes.js';

const defaultListId = 1; // TODO move to config or context or whatever
const listStates = {
idle: 'idle',
loading: 'loading',
};
import { useRemoveListMutation } from '../../services/api.js';
import defaultListId from '../../config/index.js';

const List = ({ list }) => {
const dispatch = useDispatch();
const [state, setState] = useState(listStates.idle);
const [removeList, { isLoading }] = useRemoveListMutation();

const currentListId = useSelector(selectCurrentListId);

Expand All @@ -36,14 +29,9 @@ const List = ({ list }) => {

const remove = async () => {
try {
setState(listStates.loading);
const url = routes.list(list.id);
await axios.delete(url);
setState(listStates.idle);
dispatch(listsActions.remove(list.id));
await removeList(list.id);
dispatch(setCurrentListId(defaultListId));
} catch (err) {
setState(listStates.idle);
buttonRef.current?.focus();
toast('Network error');
}
Expand All @@ -64,7 +52,7 @@ const List = ({ list }) => {
<button
onClick={remove}
className="btn link-danger"
disabled={state === listStates.loading}
disabled={isLoading}
ref={buttonRef}
type="button"
>
Expand Down
16 changes: 13 additions & 3 deletions src/features/lists/ListsList.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
// @ts-check

import React from 'react';
import { useSelector } from 'react-redux';
import { toast } from 'react-toastify';

import { useGetListsQuery } from '../../services/api.js';
import Loader from '../../lib/Loader.jsx';
import List from './List.jsx';
import { listsSelectors } from './listsSlice.js';

const ListsList = () => {
const lists = useSelector(listsSelectors.selectAll);
const { data: lists, error, isLoading } = useGetListsQuery();

if (isLoading) {
return <Loader />;
}

if (error) {
toast('Network error');
return <span>Error while loading</span>;
}

return (
<ul className="list-group list-group-flush" data-testid="lists">
Expand Down
21 changes: 11 additions & 10 deletions src/features/lists/NewListForm.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
// @ts-check

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { Formik, Field, Form } from 'formik';
import axios from 'axios';
import * as Yup from 'yup';
import cn from 'classnames';
import { toast } from 'react-toastify';
import { BsCheck } from 'react-icons/bs';

import { listsActions, listsSelectors } from './listsSlice';
import { setCurrentListId } from '../../store/currentListIdSlice';
import routes from '../../api/routes.js';
import { useAddListMutation, useGetListsQuery } from '../../services/api';

const NewListForm = () => {
const dispatch = useDispatch();
const { data: lists, isLoading } = useGetListsQuery();
const [addList] = useAddListMutation();

const addList = async ({ text }, { resetForm }) => {
if (isLoading) {
return null;
}

const onSubmit = async ({ text }, { resetForm }) => {
try {
const url = routes.lists();
const { data } = await axios.post(url, { name: text });
dispatch(listsActions.add(data));
const data = await addList({ name: text }).unwrap();
dispatch(setCurrentListId(data.id));
resetForm();
} catch (error) {
toast('Network error');
}
};

const lists = useSelector(listsSelectors.selectAll);
const listsNames = lists.map((i) => i.name);

const validationSchema = Yup.object().shape({
Expand All @@ -38,7 +39,7 @@ const NewListForm = () => {
return (
<Formik
initialValues={{ text: '' }}
onSubmit={addList}
onSubmit={onSubmit}
validationSchema={validationSchema}
validateOnBlur={false}
validateOnMount={false}
Expand Down
21 changes: 0 additions & 21 deletions src/features/lists/listsSlice.js

This file was deleted.

27 changes: 15 additions & 12 deletions src/features/tasks/NewTaskForm.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
// @ts-check
/* eslint-disable no-template-curly-in-string */

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import { Formik, Field, Form } from 'formik';
import axios from 'axios';
import * as Yup from 'yup';
import cn from 'classnames';
import { toast } from 'react-toastify';

import { tasksActions, tasksSelectors } from './tasksSlice';
import routes from '../../api/routes.js';
import { selectCurrentListId } from '../../store/currentListIdSlice';
import {
useAddTaskMutation,
useGetTasksByListIdQuery,
} from '../../services/api';

const NewTaskForm = () => {
const dispatch = useDispatch();
const currentListId = useSelector(selectCurrentListId);

const tasks = useSelector(tasksSelectors.selectByCurrentListId);
const { data: tasks, isLoading } = useGetTasksByListIdQuery(currentListId);
const [addTask] = useAddTaskMutation();

if (isLoading) {
return null;
}

const tasksNames = tasks.map((i) => i.text);

const validationSchema = Yup.object().shape({
text: Yup.string().trim().required().min(3).max(20).notOneOf(tasksNames),
});

const addTask = async ({ text }, { resetForm }) => {
const onSubmit = async ({ text }, { resetForm }) => {
try {
const url = routes.listTasks(currentListId);
const response = await axios.post(url, { text });
dispatch(tasksActions.add(response.data));
await addTask({ listId: currentListId, text });
resetForm();
} catch (error) {
toast('Network error');
Expand All @@ -39,7 +42,7 @@ const NewTaskForm = () => {
<Formik
initialValues={{ text: '' }}
validationSchema={validationSchema}
onSubmit={addTask}
onSubmit={onSubmit}
validateOnBlur={false}
validateOnMount={false}
validateOnChange={false}
Expand Down
39 changes: 14 additions & 25 deletions src/features/tasks/Task.jsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,38 @@
// @ts-check

import React, { useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import axios from 'axios';
import React, { useRef } from 'react';
import { toast } from 'react-toastify';

import { tasksActions } from './tasksSlice.js';
import routes from '../../api/routes.js';

const taskStates = {
idle: 'idle',
loading: 'loading',
};
import {
useRemoveTaskMutation,
useToggleCompletedMutation,
} from '../../services/api.js';

const Task = ({ task }) => {
const dispatch = useDispatch();
const [state, setState] = useState(taskStates.idle);
const [removeTask, { isLoading: isLoadingOnRemove }] =
useRemoveTaskMutation();
const [toggleTaskCompleted, { isLoading: isLoadingOnToggleCompleted }] =
useToggleCompletedMutation();
const isLoading = isLoadingOnRemove || isLoadingOnToggleCompleted;

const checkboxRef = useRef();
const buttonRef = useRef();

const remove = async () => {
try {
setState(taskStates.loading);
const url = routes.task(task.id);
await axios.delete(url);
setState(taskStates.idle);
dispatch(tasksActions.remove(task.id));
await removeTask(task.id);
} catch (err) {
setState(taskStates.idle);
buttonRef.current?.focus();
toast('Network error');
}
};

const toggleCompleted = async ({ target }) => {
try {
setState(taskStates.loading);
const url = routes.task(task.id);
const { data } = await axios.patch(url, { completed: target.checked });
dispatch(tasksActions.update(data));
await toggleTaskCompleted({ id: task.id, completed: target.checked });
} catch (err) {
toast('Network error');
}
setState(taskStates.idle);
checkboxRef.current?.focus();
};

Expand All @@ -56,7 +45,7 @@ const Task = ({ task }) => {
className="me-2"
type="checkbox"
onChange={toggleCompleted}
disabled={state === taskStates.loading}
disabled={isLoading}
ref={checkboxRef}
checked={task.completed}
/>
Expand All @@ -68,7 +57,7 @@ const Task = ({ task }) => {
onClick={remove}
className="btn btn-sm btn-danger"
type="button"
disabled={state === taskStates.loading}
disabled={isLoading}
ref={buttonRef}
>
Remove
Expand Down

0 comments on commit 2d6a78a

Please sign in to comment.