Ao criar este aplicativo, você aprenderá o seguinte:
- Como criar uma interface do usuário semelhante a um repositório do GitHub
- Como trabalhar com eventos do teclado no React
- Como trabalhar com a navegação usando as setas do teclado
- Como destacar o texto correspondente durante a pesquisa
- Como adicionar ícones no React
- Como renderizar conteúdo HTML em uma expressão JSX
E muito mais.
Você pode ver a demonstração ao vivo do aplicativo aqui.
Vamos começar
Crie um novo projeto usando create-react-app:
create-react-app github-file-search-reactApós a criação do projeto, exclua todos os arquivos do diretório src pasta e criar index.js, App.js e styles.scss arquivos dentro do src pasta. Crie também components e utils pastas dentro do src pasta.
Instale as dependências necessárias:
yarn add [email protected] [email protected] [email protected] [email protected]Abrir styles.scss e adicione o conteúdo de aqui dentro dele.
Crie um novo arquivo Header.js dentro de components pasta com o seguinte conteúdo:
import React from 'react';
const Header = () => GitHub File Search
;
export default Header;Crie um novo arquivo api.js dentro de utils pasta e adicione o conteúdo de aqui dentro dele.
Neste arquivo, criamos dados estáticos para serem exibidos na interface do usuário para manter o aplicativo simples e fácil de entender.
Crie um novo arquivo ListItem.js dentro de components pasta com o seguinte conteúdo:
import React from 'react';
import moment from 'moment';
import { AiFillFolder, AiOutlineFile } from 'react-icons/ai';
const ListItem = ({ type, name, comment, modified_time }) => {
return (
{type === 'folder' ? (
) : (
)}
{name}
{comment}
{moment(modified_time).fromNow()}
);
};
export default ListItem;Nesse arquivo, coletamos os dados de cada arquivo que queremos exibir e exibimos o ícone da pasta / arquivo, o nome do arquivo, os comentários e a última vez que o arquivo foi modificado.
Para exibir os ícones, usaremos o react-icons biblioteca npm. Ele tem um site muito bom que permite pesquisar e usar com facilidade os ícones que você precisa. Confira aqui.
O componente de ícones aceita o color e size adereços para personalizar o ícone que usamos no código acima.
Crie um novo arquivo chamado FilesList.js dentro de components pasta com o seguinte conteúdo:
import React from 'react';
import ListItem from './ListItem';
const FilesList = ({ files }) => {
return (
{files.length > 0 ? (
files.map((file, index) => {
return ;
})
) : (
No matching files found
)}
);
};
export default FilesList;Neste arquivo, lemos os dados estáticos do api.js e, em seguida, exiba cada elemento da matriz de arquivos usando o método de mapa de matriz.
Agora abra o src/App.js e adicione o seguinte código dentro dele:
import React from 'react';
import Header from './components/Header';
import FilesList from './components/FilesList';
import files from './utils/api';
export default class App extends React.Component {
state = {
filesList: files
};
render() {
const { counter, filesList } = this.state;
return (
);
}
}Nesse arquivo, adicionamos um estado para armazenar os dados dos arquivos estáticos, que podemos modificar sempre que necessário. Então nós passamos para o FilesList componente a ser exibido na interface do usuário.
Agora, abra o index.js e adicione o seguinte código dentro dele:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './styles.scss';
ReactDOM.render( , document.getElementById('root'));Agora, inicie seu aplicativo executando o yarn start comando no terminal ou prompt de comando e você verá a seguinte tela inicial:
Você pode encontrar o código até este ponto em este ramo.
Adicionar funcionalidade básica de pesquisa
Agora, vamos adicionar a funcionalidade que altera a interface do usuário e nos permite pesquisar arquivos quando pressionamos a letra t no nosso teclado.
Dentro de utils pasta crie um novo arquivo chamado keyCodes.js com o seguinte conteúdo:
export const ESCAPE_CODE = 27;
export const HOTKEY_CODE = 84; // key code of letter t
export const UP_ARROW_CODE = 38;
export const DOWN_ARROW_CODE = 40;Crie um novo arquivo chamado SearchView.js dentro de components pasta com o seguinte conteúdo:
import React, { useState, useEffect, useRef } from 'react';
const SearchView = ({ onSearch }) => {
const [input, setInput] = useState('');
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
const onInputChange = (event) => {
const input = event.target.value;
setInput(input);
onSearch(input);
};
return (
My Repository /
);
};
export default SearchView;Estamos usando o React Hooks aqui para nossos métodos de estado e ciclo de vida. Se você é novo no React Hooks, confira Este artigo para uma introdução.
Neste arquivo, declaramos primeiro um estado para armazenar a entrada digitada pelo usuário. Então nós adicionamos um ref usando o useRef Gancho para que possamos focar no campo de entrada quando o componente estiver montado.
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
...
Nesse código, passando a matriz Vazia [] como o segundo argumento para o useEffect gancho, o código dentro do useEffect O gancho será executado apenas uma vez quando o componente estiver montado. Isso atua como o componentDidMount método de ciclo de vida nos componentes da classe.
Então atribuímos o ref para o campo de entrada como ref={inputRef}. Na alteração do campo de entrada dentro do onInputChange manipulador, estamos chamando o onSearch método passado como suporte ao componente do App.js Arquivo.
Agora abra App.js e substitua seu conteúdo pelo seguinte código:
import React from 'react';
import Header from './components/Header';
import FilesList from './components/FilesList';
import SearchView from './components/SearchView';
import { ESCAPE_CODE, HOTKEY_CODE } from './utils/keyCodes';
import files from './utils/api';
export default class App extends React.Component {
state = {
isSearchView: false,
filesList: files
};
componentDidMount() {
window.addEventListener('keydown', this.handleEvent);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleEvent);
}
handleEvent = (event) => {
const keyCode = event.keyCode || event.which;
switch (keyCode) {
case HOTKEY_CODE:
this.setState((prevState) => ({
isSearchView: true,
filesList: prevState.filesList.filter((file) => file.type === 'file')
}));
break;
case ESCAPE_CODE:
this.setState({ isSearchView: false, filesList: files });
break;
default:
break;
}
};
handleSearch = (searchTerm) => {
let list;
if (searchTerm) {
list = files.filter(
(file) =>
file.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 &&
file.type === 'file'
);
} else {
list = files.filter((file) => file.type === 'file');
}
this.setState({
filesList: list
});
};
render() {
const { isSearchView, filesList } = this.state;
return (
{isSearchView ? (
) : (
)}
);
}
}Agora, reinicie o aplicativo executando o yarn start comando novamente e verifique sua funcionalidade.
Como você pode ver, inicialmente todas as pastas e arquivos são exibidos. Então, quando pressionamos a letra t no teclado, a visualização muda para permitir a pesquisa nos arquivos exibidos.
Agora, vamos entender o código do App.js Arquivo.
Neste arquivo, declaramos primeiro isSearchView como uma variável de estado. Então dentro do componentDidMount e componentWillUnmount métodos de ciclo de vida que estamos adicionando e removendo o keydown manipulador de eventos, respectivamente.
Então dentro do handleEvent , estamos verificando qual tecla é pressionada pelo usuário.
- Se o usuário pressionar a tecla t, definiremos o
isSearchViewestado paratruee atualize ofilesListmatriz de estado para incluir apenas arquivos e excluir as pastas. - Se o uso pressionar a tecla Escape, definiremos o
isSearchViewestado parafalsee atualize ofilesListmatriz de estados para incluir todos os arquivos e pastas.
A razão pela qual declaramos HOTKEY_CODE e ESCAPE_CODE em arquivos separados (keyCodes.js em vez de usar diretamente o código-chave como 84) é que mais tarde, se quisermos mudar a tecla de atalho de t para s, basta alterar o código da chave nesse arquivo. Ele refletirá a alteração em todos os arquivos em que é usada sem precisar alterá-la em todos os arquivos.
Agora, vamos entender o handleSearch função. Nesta função, verificamos se o usuário inseriu algo na caixa de pesquisa de entrada e depois filtramos o (s) nome (s) do arquivo correspondente (s) que inclui esse termo de pesquisa. Em seguida, atualizamos o estado com os resultados filtrados.
Em seguida, dentro do método render, com base no isSearchView valor, exibimos a exibição da lista de arquivos ou a pesquisa do usuário.
Você pode encontrar código até este ponto em este ramo.
Adicione funcionalidade para navegar entre arquivos
Agora, vamos adicionar a funcionalidade para exibir uma seta na frente do arquivo atualmente selecionado enquanto navega na lista de arquivos.
Crie um novo arquivo chamado InfoMessage.js dentro de components pasta com o seguinte conteúdo:
import React from 'react';
const InfoMessage = () => {
return (
You've activated the file finder. Start typing to filter the file
list. Use ↑ and{' '}
↓ to navigate,{' '}
esc to exit.
);
};
export default InfoMessage;Agora, abra o App.js arquivo e importe o InfoMessage componente para usá-lo:
import InfoMessage from './components/InfoMessage';Adicione uma nova variável de estado chamada counter com o valor inicial de 0. Isso é para acompanhar o índice da seta.
Dentro de handleEvent manipulador, obtenha o filesList e counter valores do estado:
const { filesList, counter } = this.state;Adicione dois novos casos de switch:
case UP_ARROW_CODE:
if (counter > 0) {
this.setState({ counter: counter - 1 });
}
break;
case DOWN_ARROW_CODE:
if (counter < filesList.length - 1) {
this.setState({ counter: counter + 1 });
}
break;Aqui, diminuímos o counter valor do estado quando pressionamos a seta para cima no teclado e incrementamos quando pressionamos a seta para baixo.
Importe também as constantes da matriz para cima e para baixo na parte superior do arquivo:
import {
ESCAPE_CODE,
HOTKEY_CODE,
UP_ARROW_CODE,
DOWN_ARROW_CODE
} from './utils/keyCodes';Dentro de handleSearch função, redefina o counter estado para 0 no final da função, para que a seta sempre seja exibida para o primeiro arquivo da lista enquanto estiver filtrando a lista de arquivos.
this.setState({
filesList: list,
counter: 0
});Altere o método de renderização para exibir o InfoMessage componente e passe counter e isSearchView como adereços para o FilesList componente:
render() {
const { isSearchView, counter, filesList } = this.state;
return (
{isSearchView ? (
) : (
)}
);
}Agora, abra o FilesList.js arquivo e aceite o isSearchView e counter adereços e passá-los para o ListItem componente.
Seu FilesList.js O arquivo ficará assim agora:
import React from 'react';
import ListItem from './ListItem';
const FilesList = ({ files, isSearchView, counter }) => {
return (
{files.length > 0 ? (
files.map((file, index) => {
return (
);
})
) : (
No matching files found
)}
);
};
export default FilesList;Agora abra ListItem.js e substitua seu conteúdo pelo seguinte conteúdo:
import React from 'react';
import moment from 'moment';
import { AiFillFolder, AiOutlineFile, AiOutlineRight } from 'react-icons/ai';
const ListItem = ({
index,
type,
name,
comment,
modified_time,
isSearchView,
counter
}) => {
const isSelected = counter === index;
return (
{isSearchView && (
)}
{type === 'folder' ? (
) : (
)}
{name}
{!isSearchView && (
{comment}
{moment(modified_time).fromNow()}
)}
);
};
export default ListItem;Nesse arquivo, primeiro aceitamos o isSearchView e counter suporte. Em seguida, verificamos se o índice do arquivo atualmente exibido na lista corresponde ao counter valor.
Com base nisso, exibimos a seta na frente apenas para esse arquivo. Então, quando usamos a seta para baixo ou para cima para navegar pela lista, aumentamos ou diminuímos o valor do contador, respectivamente, no App.js Arquivo.
Com base no isSearchView valor que exibimos ou ocultamos a coluna de comentário e hora na exibição de pesquisa na interface do usuário.
Agora, reinicie o aplicativo executando o yarn start comando novamente e verifique sua funcionalidade:
Você pode encontrar o código até este ponto em este ramo.
Adicione funcionalidade para destacar o texto correspondente
Agora, vamos adicionar a funcionalidade para destacar o texto correspondente do nome do arquivo quando filtramos o arquivo.
Abrir App.js e mude o handleSearch para o seguinte código:
handleSearch = (searchTerm) => {
let list;
if (searchTerm) {
const pattern = new RegExp(searchTerm, 'gi');
list = files
.filter(
(file) =>
file.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 &&
file.type === 'file'
)
.map((file) => {
return {
...file,
name: file.name.replace(pattern, (match) => {
return `${match}`;
})
};
});
} else {
list = files.filter((file) => file.type === 'file');
}
this.setState({
filesList: list,
counter: 0
});
};Nesse código, primeiro usamos o RegExp construtor para criar uma expressão regular dinâmica para pesquisa global e sem distinção entre maiúsculas e minúsculas:
const pattern = new RegExp(searchTerm, 'gi');Em seguida, filtramos os arquivos que correspondem aos critérios de pesquisa:
files.filter(
(file) =>
file.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 &&
file.type === 'file'
);Em seguida, chamamos o método de mapa de matriz no resultado obtido da funcionalidade de filtro acima.
No método map, usamos a string replace método.
o replace O método aceita dois parâmetros:
- padrão para procurar
- função a ser executada para cada padrão correspondente
Nós usamos o replace método para encontrar todas as correspondências para o pattern e substitua-o pela corda ${match}. Aqui match conterá o texto correspondente do nome do arquivo.
Se você verificar a estrutura JSON no diretório utils/api.js arquivo, a estrutura de cada arquivo fica assim:
{
id: 12,
type: 'file',
name: 'Search.js',
comment: 'changes using react context',
modified_time: '2020-06-30T07:55:33Z'
}Como queremos substituir o texto apenas do campo de nome, espalhamos as propriedades do objeto de arquivo e apenas alteramos o nome, mantendo outros valores como estão.
{
...file,
name: file.name.replace(pattern, (match) => {
return `${match}`;
})
}Agora, reinicie o aplicativo executando o yarn start comando novamente e verifique sua funcionalidade.
Você verá que o HTML é exibido como na interface do usuário ao pesquisar:
Isso ocorre porque estamos exibindo o nome do arquivo no diretório ListItem.js arquivo da seguinte maneira:
{name}E para prevenir Cross-site scripting (XSS) ataques, o React escapa todo o conteúdo exibido usando a Expressão JSX (que está entre colchetes).
Portanto, se realmente queremos exibir o HTML correto, precisamos usar um suporte especial conhecido como dangerouslySetInnerHTML. Passa o __html nome com o HTML para exibir o valor como este:
Agora, reinicie o aplicativo executando o yarn start comando novamente e verifique sua funcionalidade:
Como você pode ver, o termo de pesquisa está sendo destacado corretamente no nome do arquivo.
É isso aí!
Você pode encontrar o código até este ponto em este ramo.
Código fonte completo do GitHub: aqui
Demonstração ao vivo: aqui
Confira meus outros artigos sobre React, Node.js e Javascript em Médio, dev.to e assine para receber atualizações semanais diretamente na sua caixa de entrada aqui.