Переосмысление плохого пользовательского опыта, чтобы помочь вам и вашим клиентам выиграть!
Формы, формы, формы. В сети их полно. Для каждой потенциальной продажи, бронирования авиабилета или новой учетной записи кто-то заполняет форму. Кто-то, кто может стать вашим клиентом.
Зачем им отказывать из-за плохого опыта?
Думая об опыте.
В реальном мире, когда вы спрашиваете человека, из какой он страны, вы бы не протянули лист бумаги со списком из более чем 200 стран, из которых он мог бы выбрать.
Если я скажу: «Я американец», вы, вероятно, поймете, что я из Соединенных Штатов. Почему бы вашему веб-приложению не делать то же самое?
Тем не менее, обычным полем формы, с которым мы сталкиваемся, является «Страна», отображаемая в виде длинного списка названий стран, которые ваши клиенты должны прокручивать. Утомительный и неприятный опыт, который мы собираемся решить здесь.
Мелочи имеют большое значение.
С этим уменьшением кода вы увидите меньше отказов, меньше ошибок и более довольных клиентов.
Кстати, если ваш веб-сайт или приложение построено не на React, я написал еще одну статью, чтобы добиться того же, используя старый добрый JavaScript.
Простой виджет поиска« предлагайте по мере ввода в JavaScript.
Практическое руководство по созданию эффективного виджета поиска, который показывает предложения при вводе текста на простом JavaScript. и используя Fetch… medium.com »
Погружение в код
Перед тем, как мы начнем, сделаем пару вещей:
- Хотя я буду предельно ясен, я предполагаю, что у вас есть некоторый опыт работы с React и уже настроен ваш проект.
- В качестве примера я использую поиск по странам, но этот компонент можно использовать для любого типа поиска, фильтрации или выбора полей.
Установите крючки.
Мы собираемся разработать наш компонент "предлагать по мере ввода", который можно будет многократно использовать где угодно. Идея в том, что мы должны иметь возможность передавать данные, и все просто работает.
Начнем с создания нового файла. Мы назовем Autocomplete.js:
Вы заметите, что мы импортируем useState
и загружаем данные (props.countries
) в items
переменную. В моем случае я работаю со списком стран, но это может быть что угодно, которое вы хотите, чтобы ваши пользователи искали.
Затем мы используем useState
для инициализации переменных состояния. Это должно быть самоочевидным, но вот краткое руководство. Поскольку мы используем хуки React, состояние будет содержать всю нашу логику, и мы инициализируем его некоторыми значениями по умолчанию:
activeItem
- это индекс текущего выбранного элемента. Поскольку при загрузке страницы ничего не выделяется, мы установим для него значение 0.filteredItems
инициализируется как пустой массив для хранения отфильтрованного списка стран.displayItems
будет переключать раскрывающееся меню отфильтрованных элементов, инициализированных как false, поэтому оно не отображается при загрузке.- и
inputValue
захватит текст, вводимый пользователем в поле ввода.
Добавляем разметку.
Разметка для этого довольно проста, нам просто нужно иметь поле ввода и тернарный оператор, который проверяет, должен ли отображаться список или нет.
Давайте посмотрим, что здесь происходит. После поля ввода у нас есть такая строка:
{state.displayItems && state.inputValue.length && state.filteredItems ?
…
Это набор условий, которые должны быть выполнены, прежде чем мы отобразим раскрывающийся список отфильтрованных стран. Он говорит,
- «Если
displayItems
равноtrue
- «И
inputValue
не пустое (пользователь ввел символы в поле ввода), - «И есть
filteredItems
для отображения (мы сопоставили элементы из нашего большого списка с текстом, введенным пользователем в поле ввода), - «Затем отобразите раскрывающийся список.
- «ЕСЛИ какое-либо из этих условий является ложным, не отображать список (
: null
)».
Затем мы добавили код для нашего раскрывающегося списка. Мы используем .map()
для перебора нашего filteredItems
для создания всех элементов списка.
Вы заметите, что в конце я добавил .slice(0, 10)
, потому что я хочу отображать не более десяти совпадающих стран одновременно. Не стесняйтесь удалить это или установить любое другое ограничение по своему усмотрению.
Отлично, пока мы настроили нашу логику состояния, поле ввода и раскрывающееся меню. Все, что нам нужно сделать, это заставить эту штуку работать!
События
Давайте начнем с обзора тех отличных впечатлений, которые мы хотим доставить нашей аудитории. Мы знаем, что пользователи будут вводить название страны в поле ввода, и нам нужно будет фиксировать то, что они вводят.
Мы также хотим предоставить ярлыки для людей, использующих клавиатуру. Например, нажатие клавиш со стрелками должно позволять пользователю перемещаться вверх или вниз, чтобы выделить страны. Нажатие клавиши ввода должно позволить пользователям выбрать выделенную страну.
Таким образом, наши пользователи должны уметь:
- Введите название страны в поле поиска.
- Нажмите / коснитесь «Enter», чтобы выбрать страну.
- Стрелка вверх и стрелка вниз для навигации по отфильтрованному списку.
- Щелкните название страны, чтобы выбрать эту страну.
Давайте закодируем эти события!
Ч-ч-изменения
Начнем с записи текста, введенного в поле ввода, создав функцию handleChange
:
Все это довольно стандартно, но давайте взглянем на переменную filteredItems
. Мы используем метод .filter()
, чтобы отфильтровать наш большой список стран до тех, которые соответствуют любому тексту, введенному в поле ввода.
optionName.toLowerCase().indexOf(inputValue.toLowerCase()) > -1
Мы используем toLowerCase()
, потому что хотим, чтобы наш фильтр был нечувствительным к регистру, установив для всех записей в нашем списке и текста, вводимого в поле ввода, текст в нижнем регистре. Если совпадений нет, возвращается пустой массив.
Затем мы обновляем наше состояние с помощью setState({...})
. Вы заметите, что мы обновили состояние filteredItems
, установили для displayItems
значение true и зафиксировали текст, введенный в поле ввода в inputValue
.
Помните все те условия, которые мы установили выше? Этот небольшой фрагмент кода справляется со всем этим.
Теперь мы можем обновить оператор return, чтобы следить за событием onChange
:
Добавление onChange={handleChange}
указывает полю поиска следить за любыми изменениями, такими как добавление или удаление текста. Когда пользователь вводит текст в поле поиска, функции handleChange
запускаются и обновляют state
.
Щелкнуть-щелкнуть.
Мы хотим, чтобы пользователи могли щелкнуть элемент в списке, чтобы выбрать страну.
Очень простой! Когда пользователь щелкает название страны в отфильтрованном списке, все, что нам нужно сделать, это обновить состояние с помощью setState
. Вы увидите, что мы сбросили все значения состояния, за исключением добавления текста элемента списка в поле ввода.
Как и выше, нам нужно обновить разметку, чтобы зафиксировать событие onClick
:
Мы обновили элементы ‹li› в отфильтрованном списке, добавив onClick = {handleClick}
, который будет отслеживать любые щелчки по элементам страны в списке и запускать функцию handleClick
.
Разблокируйте ярлыки с помощью keyDown.
Мы собираемся сделать наш компонент еще более удобным для пользователей, позволив им использовать клавиши «вверх», «вниз» и «ввод» для навигации и выбора элемента из списка. Для этого мы можем добавить обработчик для события onKeyDown
.
Хорошо, это сложный код, давайте рассмотрим его:
- Во-первых, если пользователь нажал клавишу ввода,
if (e.keyCode === 13)
, мы выбираем выделенную страну из списка,activeItem
, используяinputValue: filteredItems[activeItem]
, и сбрасываем все другие переменные состояния на значения по умолчанию. - Если пользователь нажимает клавишу со стрелкой вверх,
else if(e.keyCode === 38)
, мы сначала проверяем, находится ли текущийactiveItem
в верхней части списка. Если это так, мы ничего не делаем, но, если он не находится в верхней части списка, мы обновляемstate
, чтобы уменьшить индекс, и обновлятьinputValue
текст, отображаемый в поле ввода. - Наконец, если пользователь нажимает клавишу «вниз»
else if (e.keyCode === 40)
, мы проверяем, есть ли элементы в списке, и, если да, обновляемstate
и увеличиваем индекс, обновляя текст в поле ввода.
Имеет ли это смысл?
Вот полный код на этот момент:
Как это использовать
Выполнение этого должно предоставить вам отфильтрованный список в раскрывающемся списке под полем поиска. Сохраните файл и добавьте компонент в любое приложение или компонент следующим образом:
Как насчет дополнительного кредита?
Представим,
вам нужен простой опыт, который позволит вашей аудитории добавлять столько элементов, сколько они хотят.
Вместо того, чтобы выбирать один товар, вы можете попросить своих клиентов добавить список стран, продуктов, одежды или чего-то еще. Было бы здорово, правда?
Добавление этой возможности также дает нам возможность изучить пару других ловушек React.
В конце концов, ваш компонент будет вести себя и выглядеть примерно так:
Первое первым
Мы импортируем несколько дополнительных хуков:
import React, { useState, useEffect, useRef, useReducer } from “react”;
Мы будем использовать useRef
в качестве переменной-заполнителя для элементов нашего списка. Больше нечего об этом сказать. Хук useEffect
будет нашей триггерной функцией для добавления стран в массив, который затем может быть передан в отправку вашей формы.
Но что с useReducer
? Я не буду вдаваться в документы или подробности, потому что существует бесчисленное количество, несколько сбивающее с толку, статей и руководств. Единственное, что я должен сказать: когда вы обнаружите, что useState
не работает из-за сложной логики состояний или использования сложных массивов, то, возможно, самое время попробовать useReducer
. Внимание, это довольно загадочный материал, поэтому давайте рассмотрим код:
Как и в случае с useState
, мы устанавливаем переменные состояния. В этом случае создаваемая нами переменная называется selected
. Состояние selected
станет нашим массивом для хранения выбранных элементов из списка стран.
Также, как и в случае с useState
, у нас есть вторая переменная «триггера», которая называется dispatchSelected
. На этом сходство заканчивается, потому что useReducer
на самом деле является методом триггера, который «отправляет» глубокие обновления состояния компонента.
Мы используем switch
оператор, чтобы переключаться на разные случаи, которые мы запускаем. В нашем случае мы хотим, чтобы пользователи могли добавлять и удалять страны из массива.
В случае, если пользователь добавляет страну в массив, case "add":
, у нас есть этот фрагмент кода:
return […state, { id: state.length, name: action.name }];
По сути, это означает, что «отправьте обратно текущий список стран, которые пользователь уже добавил, и добавьте новую страну, которую выбрал пользователь».
Когда пользователь удаляет страну из массива case: "remove:"
, этот фрагмент кода:
return state.filter((_, index) => index !== action.index);
говорит: «отправить обратно текущий список стран, но удалить страну, которую пользователь удалил из списка».
Я сказал вам, что это было загадочно, но не волнуйтесь, это станет ясно через пару абзацев. Нам просто нужно добавить следующий фрагмент кода для обработки функции useReducer
.
теперь понимаешь? Если честно, сначала не было, но я не всегда самая яркая лампочка.
dispatchSelected
- это наш метод «триггера», который сообщает функции useReducer
, что делать.
type: "add",
, очевидно, означает, что мы добавляем страну в список.name: e
, (событие) представляет конкретную страну, которую нужно добавить.
Итак, мы можем снова взглянуть на строку case "add":
, и она начинает обретать смысл:
return […state, { id: state.length, name: action.name }];
...state
представляет текущий список стран.id: state.length
- это индекс нового выбранного элемента в списке, аname: action.name
- атрибут имени, переданный событиемhandleAddItems
. То есть название добавляемой страны.
Так что за кулисами возникает много вуду и желаний, чтобы это сработало, но что бы то ни было, это работает!
Теперь нам нужно обновить функции событий handleClick
и handleKeyDown
следующим образом:
Мы добавляем
fieldRef.current.value = ""; handleAddItems(<stuff>);
к нашим handleClick
и handleKeyDown
функциям. Важное примечание: в зависимости от того, нажимает ли пользователь кнопку ввода, аргумент handleAddItems
различается. Это связано с тем, что действие по событию щелчка происходит в элементе списка, а клавиша ввода - в самом массиве.
В любом случае, наши функции редуктора и обработчика готовы к работе, нам просто нужно обновить разметку следующим образом:
Для проницательного взгляда вы увидите, что мы используем .map()
для перебора itemsArray
. Откуда это пришло? Давайте создадим его с помощью useState
.
Очевидно, мы уже знаем все о useState
. Мы используем это вместе с useEffect
, чтобы добавить выбранные страны в itemsArray
. Персональный список выбранных пользователем стран.
useEffect()
говорит: «В любое время, когда переменная selected
нашего метода редуктора изменится, обновите массив, запустив setItemsArray
'.
Последний, последний код
Вау, это было много работы. Если у вас есть какие-либо вопросы по этому поводу, или я был непонятен, дайте мне знать.
Этот компонент становится очень мощным при реализации с поиском на основе искусственного интеллекта или обработкой естественного языка на сервере. Напишу об этом еще одну статью, если кому интересно.
Вот последний код:
Конечно, вы можете получить полный код на моем Github:
Или посмотрите живой пример.
Привет, спасибо, что прочитали, я очень ценю это.
Я надеюсь, что это было полезно для вас. Не стесняйтесь связываться с вопросами, откровениями, оскорблениями и т. Д.