diff --git a/pages/_meta.en.json b/pages/_meta.en.json index 13f69fa..b6405bb 100644 --- a/pages/_meta.en.json +++ b/pages/_meta.en.json @@ -7,5 +7,6 @@ "newWindow": true }, "lesson-1": "Lesson 1: Hello World!", - "lesson-2": "Lesson 2: Forms" + "lesson-2": "Lesson 2: Forms", + "lesson-3": "Lesson 3: Tables" } diff --git a/pages/_meta.ru.json b/pages/_meta.ru.json index 95163ba..4d52b7e 100644 --- a/pages/_meta.ru.json +++ b/pages/_meta.ru.json @@ -7,5 +7,6 @@ "newWindow": true }, "lesson-1": "Урок 1", - "lesson-2": "Урок 2: Формы" + "lesson-2": "Урок 2: Формы", + "lesson-3": "Урок 3: Таблицы" } diff --git a/pages/lesson-2/_meta.ru.json b/pages/lesson-2/_meta.ru.json index 971b213..54d12b3 100644 --- a/pages/lesson-2/_meta.ru.json +++ b/pages/lesson-2/_meta.ru.json @@ -1,7 +1,4 @@ { - "chapter-1": "Chapter 1", - "chapter-2": "Chapter 2", - "chapter-3": "Chapter 3", - "chapter-4": "Chapter 4", - "chapter-5": "Chapter 5" + "chapter-1": "Глава 1. React-hook-form", + "chapter-2": "Глава 2. Валидация форм" } diff --git a/pages/lesson-2/chapter-2.ru.mdx b/pages/lesson-2/chapter-2.ru.mdx new file mode 100644 index 0000000..922266a --- /dev/null +++ b/pages/lesson-2/chapter-2.ru.mdx @@ -0,0 +1 @@ +# Under construction diff --git a/pages/lesson-3/_meta.en.json b/pages/lesson-3/_meta.en.json new file mode 100644 index 0000000..5bbe580 --- /dev/null +++ b/pages/lesson-3/_meta.en.json @@ -0,0 +1,3 @@ +{ + "chapter-1": "Chapter 1" +} diff --git a/pages/lesson-3/_meta.ru.json b/pages/lesson-3/_meta.ru.json new file mode 100644 index 0000000..894d4e8 --- /dev/null +++ b/pages/lesson-3/_meta.ru.json @@ -0,0 +1,3 @@ +{ + "chapter-1": "Глава 1.Таблицы" +} diff --git a/pages/lesson-3/chapter-1.en.mdx b/pages/lesson-3/chapter-1.en.mdx new file mode 100644 index 0000000..922266a --- /dev/null +++ b/pages/lesson-3/chapter-1.en.mdx @@ -0,0 +1 @@ +# Under construction diff --git a/pages/lesson-3/chapter-1.ru.mdx b/pages/lesson-3/chapter-1.ru.mdx new file mode 100644 index 0000000..5c41905 --- /dev/null +++ b/pages/lesson-3/chapter-1.ru.mdx @@ -0,0 +1,254 @@ +# Таблицы + +## Сортировка + +Наш бэкэнд будет принимать параметр sort формата `name-asc` где `name` - +название поля, а `asc` - направление сортировки. Возможные направления сортировки: +`asc` и `desc`. + +Добавим возможность сортировки для таблиц, для этого: + +- Создадим историю в сторибуке: + +```tsx +const data = [ + { + title: 'Project A', + cardsCount: 10, + updated: '2023-07-07', + createdBy: 'John Doe', + }, + { + title: 'Project B', + cardsCount: 5, + updated: '2023-07-06', + createdBy: 'Jane Smith', + }, + { + title: 'Project C', + cardsCount: 8, + updated: '2023-07-05', + createdBy: 'Alice Johnson', + }, + { + title: 'Project D', + cardsCount: 3, + updated: '2023-07-07', + createdBy: 'Bob Anderson', + }, + { + title: 'Project E', + cardsCount: 12, + updated: '2023-07-04', + createdBy: 'Emma Davis', + }, +] +export const WithSort = { + render: () => { + return ( + + + + + + + + + + + + {data.map(item => ( + + + + + + + + ))} + +
NameCardsLast UpdatedCreated by
{item.title}{item.cardsCount}{item.updated}{item.createdBy}icons...
+ ) + }, +} +``` + +Получим просто таблицу, которая пока не сортируемая. + +- Создадим стейт для сортировки: + +```tsx +type Sort = { + key: string + direction: 'asc' | 'desc' +} | null + +const [sort, setSort] = useState(null) +``` + +- Добавим обработчик клика на заголовок таблицы: + +```tsx +const handleSort = (key: string) => { + if (sort && sort.key === key) { + setSort({ + key, + direction: sort.direction === 'asc' ? 'desc' : 'asc', + }) + } else { + setSort({ + key, + direction: 'asc', + }) + } +} +``` + +и используем его в таблице: + +```tsx + + handleSort('name')}>Name + handleSort('cardsCount')}>Cards + handleSort('updated')}>Last Updated + handleSort('createdBy')}>Created by + + +``` + +- Добавим иконки в ячейки заголовка: + +```tsx + handleSort('name')}> + Name + {sort && sort.key === 'name' && {sort.direction === 'asc' ? '▲' : '▼'}} + +``` + +- Добавим `console.log()` для проверки стейта: + +```tsx +console.log(sort) +``` + +Проверяем, при клике должна меняться иконка и в консоли должен появляться правильный объект. + +![sort](./images/table-with-basic-sort.png) + +## Рефакторинг + +Мы повторяем слишком много кода в обработчиках, из-за чего мы оставляем слишком много шансов для ошибок. + +Одним из возможных решений в данной ситуации будет создание массива с заголовками таблицы и использование его для отрисовки заголовков и обработчиков: + +```tsx +const columns = [ + { + key: 'name', + title: 'Name', + }, + { + key: 'cardsCount', + title: 'Cards', + }, + { + key: 'updated', + title: 'Last Updated', + }, + { + key: 'createdBy', + title: 'Created by', + }, +] + +// ... + +{ + columns.map(column => ( + handleSort(column.key)}> + {column.title} + {sort && sort.key === column.key && {sort.direction === 'asc' ? '▲' : '▼'}} + + )) +} + +// ... +``` + +Протипизируем columns: + +```tsx +type Column = { + key: string + title: string +} +const columns: Array = ... +``` + +У нас будет несколько таблиц, поэтому имеет смысл вынести часть логики в отдельный компонент: + +```tsx +export const Header: FC< + Omit< + ComponentPropsWithoutRef & { + columns: Column[] + sort?: Sort + onSort?: (sort: Sort) => void + }, + 'children' + > +> = ({ columns, sort, onSort, ...restProps }) => { + const handleSort = (key: string, sortable?: boolean) => () => { + if (!onSort || !sortable) return + + if (sort?.key !== key) return onSort({ key, direction: 'asc' }) + + if (sort.direction === 'desc') return onSort(null) + + return onSort({ + key, + direction: sort?.direction === 'asc' ? 'desc' : 'asc', + }) + } + + return ( + + + {columns.map(({ title, key, sortable }) => ( + + {title} + {sort && sort.key === key && {sort.direction === 'asc' ? '▲' : '▼'}} + + ))} + + + ) +} +``` + +Обратите внимание, что состоянием мы будем управлять снаружи, а не внутри компонента. + +Используем новый компонент: + +```tsx + +
+ {/*...*/} +
+``` + +И, наконец, создадим нужную нам строку для бэкэнда: + +```tsx +const sortedString = useMemo(() => { + if (!sort) return null + + return `${sort.key}-${sort.direction}` +}, [sort]) + +console.log(sortedString) +``` + +## Самостоятельная работа: + +- Добавить сортировку по умолчанию (при третьем клике по заголовку таблицы сортировка должна сбрасываться (null)) diff --git a/pages/lesson-3/images/table-with-basic-sort.png b/pages/lesson-3/images/table-with-basic-sort.png new file mode 100644 index 0000000..a87799f Binary files /dev/null and b/pages/lesson-3/images/table-with-basic-sort.png differ