diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b51149cf5..84477da1a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,7 +5,7 @@ module.exports = { }, extends: [ 'plugin:react/recommended', - "plugin:react-hooks/recommended", + 'plugin:react-hooks/recommended', 'airbnb-typescript', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', @@ -14,11 +14,11 @@ module.exports = { ], overrides: [ { - 'files': ['**/*.spec.jsx'], - 'rules': { + files: ['**/*.spec.jsx'], + rules: { 'react/jsx-filename-extension': ['off'], - } - } + }, + }, ], parser: '@typescript-eslint/parser', parserOptions: { @@ -34,18 +34,21 @@ module.exports = { 'import', 'react-hooks', '@typescript-eslint', - 'prettier' + 'prettier', ], rules: { // JS - 'semi': 'off', + semi: 'off', '@typescript-eslint/semi': ['error', 'always'], 'prefer-const': 2, curly: [2, 'all'], - 'max-len': ['error', { - ignoreTemplateLiterals: true, - ignoreComments: true, - }], + 'max-len': [ + 'error', + { + ignoreTemplateLiterals: true, + ignoreComments: true, + }, + ], 'no-redeclare': [2, { builtinGlobals: true }], 'no-console': 2, 'operator-linebreak': 0, @@ -57,7 +60,11 @@ module.exports = { 2, { blankLine: 'always', prev: '*', next: 'return' }, { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, - { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, { blankLine: 'always', prev: 'directive', next: '*' }, { blankLine: 'always', prev: 'block-like', next: '*' }, ], @@ -73,16 +80,16 @@ module.exports = { 'react/jsx-props-no-spreading': 0, 'react/state-in-constructor': [2, 'never'], 'react-hooks/rules-of-hooks': 2, - 'jsx-a11y/label-has-associated-control': ["error", { - assert: "either", - }], - 'jsx-a11y/label-has-for': [2, { - components: ['Label'], - required: { - some: ['id', 'nesting'], + 'jsx-a11y/label-has-for': [ + 2, + { + components: ['Label'], + required: { + some: ['id', 'nesting'], + }, + allowChildren: true, }, - allowChildren: true, - }], + ], 'react/jsx-uses-react': 'off', 'react/react-in-jsx-scope': 'off', @@ -91,7 +98,9 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-unused-vars': ['error'], '@typescript-eslint/indent': ['error', 2], - '@typescript-eslint/ban-types': ['error', { + '@typescript-eslint/ban-types': [ + 'error', + { extendDefaults: true, types: { '{}': false, @@ -99,7 +108,13 @@ module.exports = { }, ], }, - ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts', 'src/vite-env.d.ts', 'cypress'], + ignorePatterns: [ + 'dist', + '.eslintrc.cjs', + 'vite.config.ts', + 'src/vite-env.d.ts', + 'cypress', + ], settings: { react: { version: 'detect', diff --git a/src/Store.tsx b/src/Store.tsx index 01f59bec1..1faf30d69 100644 --- a/src/Store.tsx +++ b/src/Store.tsx @@ -1,17 +1,17 @@ import React, { useEffect, useReducer } from 'react'; -import { Filters, Todo } from './types/Todo'; +import { Actions, Filters, Todo } from './types/Todo'; import { filterTodos } from './utils/services'; import { useLocalStorage } from './hooks/useLocalStorage'; type Action = - | { type: 'filter'; payload: Filters } - | { type: 'addTodo'; payload: Todo } - | { type: 'updateTodo'; payload: Todo } - | { type: 'toggleTodo'; payload: number } - | { type: 'deleteTodo'; payload: number } - | { type: 'toggleAllTodos' } - | { type: 'clearCompleted' } - | { type: 'renameTodo'; payload: number | null }; + | { type: Actions.Filter; payload: Filters } + | { type: Actions.AddTodo; payload: Todo } + | { type: Actions.UpdateTodo; payload: Todo } + | { type: Actions.ToggleTodo; payload: number } + | { type: Actions.DeleteTodo; payload: number } + | { type: Actions.ToggleAllTodos } + | { type: Actions.ClearCompleted } + | { type: Actions.RenameTodo; payload: number | null }; interface State { allTodos: Todo[]; @@ -24,12 +24,12 @@ const reducer = (state: State, action: Action): State => { let updatedTodos = state.allTodos; switch (action.type) { - case 'addTodo': { + case Actions.AddTodo: { updatedTodos = [...state.allTodos, action.payload]; break; } - case 'updateTodo': { + case Actions.UpdateTodo: { updatedTodos = state.allTodos.map(todo => todo.id === action.payload.id ? { ...todo, title: action.payload.title } @@ -38,17 +38,17 @@ const reducer = (state: State, action: Action): State => { break; } - case 'deleteTodo': { + case Actions.DeleteTodo: { updatedTodos = state.allTodos.filter(todo => todo.id !== action.payload); break; } - case 'clearCompleted': { + case Actions.ClearCompleted: { updatedTodos = state.allTodos.filter(todo => !todo.completed); break; } - case 'toggleTodo': { + case Actions.ToggleTodo: { updatedTodos = state.allTodos.map(todo => todo.id === action.payload ? { ...todo, completed: !todo.completed } @@ -57,7 +57,7 @@ const reducer = (state: State, action: Action): State => { break; } - case 'toggleAllTodos': { + case Actions.ToggleAllTodos: { const isAllCompleted = state.allTodos.every(todo => todo.completed); updatedTodos = state.allTodos.map(todo => ({ @@ -67,7 +67,7 @@ const reducer = (state: State, action: Action): State => { break; } - case 'filter': { + case Actions.Filter: { return { ...state, activeFilter: action.payload, @@ -75,7 +75,7 @@ const reducer = (state: State, action: Action): State => { }; } - case 'renameTodo': { + case Actions.RenameTodo: { return { ...state, renamingTodo: action.payload, diff --git a/src/components/FilterButton.tsx b/src/components/FilterButton.tsx index 385d50c60..5e756fb2a 100644 --- a/src/components/FilterButton.tsx +++ b/src/components/FilterButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; -import { Filters } from '../types/Todo'; +import { Actions, Filters } from '../types/Todo'; import { DispatchContext, StateContext } from '../Store'; type Props = { @@ -19,7 +19,7 @@ const FilterButton: React.FC = ({ filterItem }) => { const handleFilterClick = () => { if (!isSelectedFilter) { if (dispatch) { - dispatch({ type: 'filter', payload: filterItem }); + dispatch({ type: Actions.Filter, payload: filterItem }); } } }; diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 23c5d938b..db3ec198c 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Filters } from '../types/Todo'; +import { Actions, Filters } from '../types/Todo'; import FilterButton from './FilterButton'; import { DispatchContext, StateContext } from '../Store'; @@ -11,7 +11,7 @@ const Footer: React.FC = ({}) => { const handleClearCompleted = () => { if (dispatch) { - dispatch({ type: 'clearCompleted' }); + dispatch({ type: Actions.ClearCompleted }); } }; diff --git a/src/components/Form.tsx b/src/components/Form.tsx index 6cf834625..83c819312 100644 --- a/src/components/Form.tsx +++ b/src/components/Form.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { DispatchContext, StateContext } from '../Store'; -import { Todo } from '../types/Todo'; +import { Actions, Todo } from '../types/Todo'; const Form: React.FC = () => { const [todoTitle, setTodoTitle] = useState(''); @@ -26,7 +26,7 @@ const Form: React.FC = () => { }; if (dispatch) { - dispatch({ type: 'addTodo', payload: newTodo }); + dispatch({ type: Actions.AddTodo, payload: newTodo }); } }; @@ -36,7 +36,7 @@ const Form: React.FC = () => { const handleFormSubmit = (event: React.FormEvent) => { event.preventDefault(); - if (todoTitle.trim() === '') { + if (!todoTitle.trim()) { return; } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 4eb30b93c..6621f18cb 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import cn from 'classnames'; import Form from './Form'; import { DispatchContext, StateContext } from '../Store'; +import { Actions } from '../types/Todo'; const Header: React.FC = () => { const { allTodos } = useContext(StateContext); @@ -11,13 +12,12 @@ const Header: React.FC = () => { const handleToggleAll = () => { if (dispatch) { - dispatch({ type: 'toggleAllTodos' }); + dispatch({ type: Actions.ToggleAllTodos }); } }; return (
- {/* this button should have `active` class only if all todos are completed */} {allTodos.length > 0 && (