Como criar um File Picker com React?
5 min · 12 Mai, 2023Componente File Picker
Todos nós conhecemos o clássico <input type="file" />
, ele é um dos elementos HTML padrão, e sua customização pode ser complicada caso queiramos uma estilização mais moderna. O componente que irei mostrar aqui pode ser facilmente aplicado ao seu projeto React, uma vez que só utiliza recursos da biblioteca padrão.
A implementação completa está disponível no Stackblitz.
Estilização base
No arquivo de estilização global, iremos aplicar os seguintes estilos:
1body { 2 font-family: "Open Sans", "Helvetica Neue", sans-serif; 3 box-sizing: border-box; 4 margin: 0; 5 background-color: #151718; 6 color: whitesmoke; 7} 8 9.app-container { 10 display: flex; 11 align-items: center; 12 justify-content: center; 13 height: 100vh; 14}
Até aqui apenas modificações visuais para deixar a página mais atrativa, a ideia é centralizar o componente na tela. Agora vamos criar a estilização para o nosso file picker, crie um arquivo: FilePicker.css.
1@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"); 2 3.wrapper { 4 display: flex; 5 flex-direction: column; 6} 7 8.button { 9 width: auto; 10 height: 33px; 11 font-size: 16px; 12 border: 1px solid; 13 border-radius: 8px; 14 background-color: cornflowerblue; 15 color: whitesmoke; 16 cursor: pointer; 17 display: block; 18} 19 20.small { 21 color: rgba(255, 255, 255, 0.66); 22}
A estilização principal se centra no botão e outros elementos visuais que farão parte do componente conforme a imagem mostrada anteriormente.
Princípios do FilePicker
Para que este componente funcione, alguns princípios devem ser levados em conta:
- O único elemento nativo capaz de selecionar arquivos é o input file;
- O botão descrito na imagem anterior será responsável por acionar o explorador de arquivos do sistema;
- O componente deve indicar um feedback que o arquivo foi selecionado com sucesso.
Primeiramente vejamos como montar a base do nosso componente:
1import React from "react"; 2import "./FilePicker.css"; 3 4export function FilePicker({ multiple, accept, onSelect }) { 5 return ( 6 <div className="wrapper"> 7 <input 8 hidden 9 type="file" 10 accept={accept} 11 multiple={multiple} 12 onChange={(e) => onSelect(e.target.files)} 13 /> 14 </div> 15 ); 16}
Vamos entender parte por parte agora. Nosso componente irá envolver o <input type="file" />
, de tal forma que para customizar as opções possíveis recebemos algumas props.
multiple
: É um atributo comum neste tipo de input e permite a seleção múltipla de arquivos;accept
: É um atributo comum que permite que apenas determinados tipos sejam selecionados;onSelect
: É o nome da função que irá enviar os arquivos para o componente-pai que estiver utilizando o FilePicker, tal como um formulário.
Observe na linha 9, é definido a seguinte arrow function: (e) => onSelect(e.target.files)
. Toda vez que o input for alterado, a nossa callback onSelect
será chamada passando os arquivos como parâmetro, para esta situação optei por utilizar o evento do input chamado de onChange
. Porém, até este ponto nada aparece em nossa interface, pois por padrão ocultamos o input usando a propriedade hidden
.
Para chamar este componente FilePicker, basta chamá-lo no App.js ou no local que desejar:
1<FilePicker 2 accept=".csv, .xlsx" 3 multiple 4 onSelect={(files) => { 5 console.log(files); 6 }} 7/>
Para que um botão acione o input oculto e abra o explorador de arquivos do dispositivo, será necessário "forçar" o clique no input mesmo ele estando oculto.
useRef
O useRef
é um hook padrão da biblioteca do React. A documentação oficial define como "um hook que permite referenciar um valor que não é necessário para renderização". Resumidamente, ele é um valor que será preservado durante as renderizações dos componentes funcionais, e sua alteração não força uma re-renderização como o useState, por exemplo.
Uma das finalidades deste hook, é guardar referência de elementos da DOM, dessa forma podemos ler as informações de qualquer elemento desde que ele possua uma referência.
1import React, { useRef } from "react"; 2import "./FilePicker.css"; 3 4export function FilePicker({ multiple, accept, onSelect }) { 5 const inputRef = useRef(null); 6 7 return ( 8 <div className="wrapper"> 9 <input 10 ref={inputRef} 11 hidden 12 type="file" 13 accept={accept} 14 multiple={multiple} 15 onChange={(e) => onSelect(e.target.files)} 16 /> 17 <p>Selecione um arquivo de tipo: {accept}</p> 18 <button className="button" onClick={() => inputRef.current.click()}> 19 Selecionar 20 </button> 21 </div> 22 ); 23}
O código acima já funcione perfeitamente, porém vamos entender parte por parte do processo:
- Linha 5:
const ref = useRef(null)
, esta linha determina uma referência no React, seu valor inicial énull
; - Linha 10:
ref={inputRef}
, esta linha determina que o input agora possui uma referência do elemento na DOM. Para acessar basta utilizarinputRef.current
; - Linha 18:
inputRef.current.click()
, ao clicarmos no botão invocamos um dos eventos de um input:click
.
Dessa forma, é possível selecionar os arquivos mesmo que o input verdadeiro não esteja aparecendo na tela, pois nosso inputRef
possui informação da DOM a respeito deste elemento.
Toques finais
Com o FilePicker funcional, basta aplicar algumas melhorias visuais como um state para mostrar quantos arquivos foram selecionados e mudar a aparência do botão:
1import React, { useState, useRef } from "react"; 2import "./FilePicker.css"; 3 4export function FilePicker({ multiple, accept, onSelect }) { 5 const [selected, setSelected] = useState(null); 6 const inputRef = useRef(null); 7 8 return ( 9 <div className="wrapper"> 10 <input 11 ref={inputRef} 12 hidden 13 type="file" 14 accept={accept} 15 multiple={multiple} 16 onChange={(e) => { 17 onSelect(e.target.files); 18 setSelected(e.target.files); 19 }} 20 /> 21 <p>Selecione um arquivo de tipo: {accept}</p> 22 <button className="button" onClick={() => inputRef.current.click()}> 23 {selected ? ( 24 <> 25 <i className="bi bi-check2-circle" /> Selecionado 26 </> 27 ) : ( 28 <> 29 <i className="bi bi-upload" /> Selecionar 30 </> 31 )} 32 </button> 33 {selected && ( 34 <small className="small"> 35 {selected.length} selecionado (s) 36 <span 37 onClick={() => { 38 inputRef.current.value = ""; 39 setSelected(null); 40 onSelect(null); 41 }} 42 > 43 <i className="bi bi-x-circle" /> 44 </span> 45 </small> 46 )} 47 </div> 48 ); 49}
As melhorias foram realizadas na linha 5, com um state para saber quando os arquivos foram selecionados ou não. Nas linhas 23-32 mostramos um ícone e texto diferentes de acordo com o state. E na linha 33 adicionamos um elemento básico para mostrar quantos arquivos foram selecionados, bem como a possibilidade de "limpar". Para isso, na linha 37, o onClick reseta todos os valores, inclusive da callback onSelect
.