Upload plików w aplikacji jest dość często pojawiającą się funkcjonalnością. Pozwala na wysyłanie plików do innych osób, serwisów, dostarczaniu danych do aplikacji żeby nie musieć wypełniać ich ręcznie lub zwykłym umieszczaniu zdjęć na stronie. Podczas tworzenia aplikacji możemy wykorzystać jedną z wielu bibliotek, która dodaje w naszej aplikacji frontendowej taką funkcjonalność ale może da się to zrobić samemu?

input type=“file”

Tak naprawdę stworzenie takiej funkcjonalności bez żadnej bibliotek nie jest niczym trudnym. Musimy się tylko oprzeć o znany element HTML - input z odpowiednim typem file

<input type="file"/>

Już samo takie polecenie powoduje, że wyświetla nam się przycisk wraz z informacją dotyczącą jaki plik wybraliśmy.

See the Pen Input file by Aleksander (@Feridum) on CodePen.

Oczywiście na tym nie kończą się możliwości tego elementu ponieważ razem z tym typem dostajemy kilka atrybutów, którymi możemy sterować pracą inputa. Znajdziemy tam:

Warto jeszcze na chwilę zatrzymać się przy atrybucie accept. Dopuszcza on kilka możliwości ustawiania wartości:

Dodatkowo możemy podać wiele wartości oddzielając je przecinkiem np.: accept=".png,.jpg".

Upload pliku w React

Jednak rzadko kiedy widzimy takie pole, ponieważ nie pasuje ono zazwyczaj do wyglądu strony i taki element psułby wizualnie całość aplikacji. Zamiast tego widzimy zwykłe przyciski lub inne mechanizmy, które ukrywają input a wystawiają ładnie wyglądający element. Jak to zrobić w React.js? Przykładowy komponent może wyglądać następująco:

<div>
    <input
      type="file"
      className="inputFile"
      ref={file}
      onChange={handleChange}
      data-testid="inputFile"
    />
    <div className="container">
    <div className="fileName" data-testid="fileName">
        {fileState && fileState.name}
    </div>
    <div className="button" onClick={handleClick}>
        Wybierz plik
    </div>
    </div>
</div>

Przy tworzeniu takich komponentów pierwszy krok to będzie umieszczenie oraz ukrycie natywnego <input type="file"/>. Dalej wykorzystujemy mechanizm ref'a w React’cie aby dostać się do tego elementu i go kliknąć oraz potem by pobrać odpowiedni plik z atrybutu files. Cała reszta to tak naprawdę odpowiednie ostylowanie naszego komponentu. Prawda, że proste?

Testy w React

Została ostatnia rzecz do zrobienia. Skoro mamy komponent w React’cie to warto go przetestować. Ostatnio testuję komponenty React’a z wykorzystaniem react-testing-library (jeśli korzystacie z Enzyma to polecam spróbować tej biblioteki - osobiście znacznie lepiej pisze mi się teraz testy). Oczywiście to co tutaj pokażę to jest najprostszy przypadek i pokazuje tylko jak można podejść do tego problemu. Każdy formularz jest unikalny i będziemy w nim testować coś innego.

const file = new File(["filec content"], "file.png", {
  type: "image/png"
});

describe("shows the children when the checkbox is checked", () => {
  it("display filename in box after upload", () => {
    const { getByTestId } = render(<UploadFile />);

    const input = getByTestId("inputFile");

    Object.defineProperty(input, "files", {
      value: [file]
    });

    fireEvent.change(input);

    expect(getByTestId("fileName").textContent).toEqual("file.png");
  });
});

Tak może wyglądać test do komponentu, który stworzyliśmy przed chwilą. Najważniejsza jest ta część, która odpowiada za stworzenie pliku w JS oraz zasymulowanie wybrania go przez użytkownika.

Tworzenie pliku:

const file = new File(["filec content"], "file.png", {
  type: "image/png"
});

Symulacja wybrania pliku przez użytkownika:

Object.defineProperty(input, "files", {
  value: [file]
});

fireEvent.change(input);

Całość kodu możecie zobaczyć na CodeSandbox. A jak u was wygląda upload plików? Rozwiązujecie to podobnie czy zupełnie inaczej? A może wolicie biblioteki - jeśli tak to z czego korzystacie i dlaczego?