4. React Login form

0. Introduction

These are the main steps:

  1. Create the project as indicated in a previous post
  2. Defining how to apply i18n to the project
  3. Create a component for selecting the language
  4. Create the login form

1. How to apply i18n?

Lets create a map with a key and an array of the traductions (we are using 6 languages: 0:Catalonian, 1:Spanish, 2:English, 3: French, 4: German, 5: Romanian, 6:Italian), and a funtion that retrieves the i18n text from the map knowing the key and the language number

//A map with the values in different languages
//This data can be in the same component that uses it or in a separate module
const i18nMap= new Map<string,string[]>([
 //<--key-->|<-----Catalonian---->|<------Spanish--------->|<-English-->|<---French--->|<----German---->|<----Romanian--->|<---Italian----->                
  ['login' ,["Entrar al sistema"  ,"Entrar al sistema"     ,"Login"     ,"Connexion"   ,"Anmeldung"     ,"Autentificare"  ,"Login"          ]],
  ['user'  ,["Usuari"             ,"Usuario"               ,"User"      ,"Nom"         ,"Nutzername"    ,"Nume"           ,"Nome"           ]],
  ['pwd'   ,["Paraula de pas"     ,"Contraseña"            ,"Password"  ,"Mot de passe","Passwort"      ,"Parola"         ,"Parola d'ordine"]],
  ['cancel',["Cancel·lar"         ,"Cancelar"              ,"Cancel"    ,"Annuler"     ,"Absagen"       ,"Anulare"        ,"Annulla"        ]],
])

//Get the i18n Label from the previous map
//This function should be in another modula and reuse it
export function getI18nLabel(key: string, lang : number, i18nMap: Map<string, string[]>) {
    const myArr= i18nMap.get(key)
    if (!myArr) return 'NO-TROBAT-i18n'
    else if (myArr.length<=lang) return 'NO-TROBAT-i18n'
    else return myArr[lang]
}

2. Passing data between components

The useState hook can be passed to components using this interface that in my case is stored in the file "Types.ts"
import { Dispatch, SetStateAction } from "react"

export interface MySetter {
    param: any
    setParam:  Dispatch<SetStateAction<any>>
}
This is a way of getting the hook
 const [param, setParam] = useState<any>()

The component for selecting the language has an input param of type "mySetter", so the caller component is aware of the changes of this param.

2. Select language component

The material UI Autocomplete component can add images to the select. It is copied from an example, but instead, the flags are picked from a local folder. The "require" function hels with this task. Finally the flag is displayed beside the element. Note as said in the previous point, the use of "MySetter" type as the input parameter.
import React from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";
import RoIcon from "../images/ro.png";
import { MySetter } from "./Types";

interface CountryType {
  id: number;
  code: string;
  label: string;
}

const countries: readonly CountryType[] = [
  { id: 0, code: "ca", label: "Valencià" },
  { id: 1, code: "es", label: "Español" },
  { id: 2, code: "en", label: "English" },
  { id: 3, code: "fr", label: "Francaise" },
  { id: 4, code: "de", label: "Deustch" },
  { id: 5, code: "ro", label: "Românesc" },
  { id: 6, code: "it", label: "Italiano" },
];

function getImgFileName(s: string) {
  return "../images/" + s + ".png";
}

export default function LangSel4(myLang: MySetter) {
  const [value, setValue] = React.useState<CountryType>(
    countries[myLang.param]
  );
  const [inputValue, setInputValue] = React.useState<string>("");

  return (
    <div className="container">
      <div className="row align-items-center">
        <div className="col">
          <Autocomplete
            value={value}
            onChange={(event: any, newValue: CountryType | null) => {
              const aVal = newValue !== null ? newValue : countries[0];
              setValue(aVal);
              myLang.setParam(aVal.id);
            }}
            inputValue={inputValue}
            onInputChange={(event, newInputValue) => {
              setInputValue(newInputValue);
            }}
            id="country-select-demo"
            sx={{ width: 300 }}
            options={countries}
            autoHighlight
            getOptionLabel={(option) => option.label}
            renderOption={(props, option) => (
              <Box
                component="li"
                sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
                {...props}
              >
                <img
                  loading="lazy"
                  width="20"
                  //src={`https://flagcdn.com/w20/${option.code.toLowerCase()}.png`} //WORKS
                  src={require(`../images/${option.code.toLowerCase()}.png`)}
                  srcSet={`https://flagcdn.com/w40/${option.code.toLowerCase()}.png 2x`}
                  alt=""
                />
                {option.label} ({option.code})
              </Box>
            )}
            renderInput={(params) => (
              <TextField
                {...params}
                label=" "
                inputProps={{
                  ...params.inputProps,
                  autoComplete: "new-password", // disable autocomplete and autofill
                }}
              />
            )}
          />
        </div>
        <div className="col">
          <img
            src={require(`../images/${value.code.toLowerCase()}.png`)}
            width="40"
            alt="res"
          />
        </div>
      </div>
    </div>
  );
}



3. Login form

A simple login form with user and password and a language selector

import blue from "@mui/material/colors/blue"
import userEvent from "@testing-library/user-event"
import React, { ChangeEvent, FormEvent, useState } from "react"
import LangSel4 from "./LangSel4"
import {MySetter, User} from './Types'
import { getI18nLabel } from './Funcs'

const i18nMap= new Map<string,string[]>([
  ['login' ,["Entrar al sistema"  ,"Entrar al sistema"     ,"Login"     ,"Connexion"   ,"Anmeldung"     ,"Autentificare"  ,"Login"          ]],
  ['user'  ,["Usuari"             ,"Usuario"               ,"User"      ,"Nom"         ,"Nutzername"    ,"Nume"           ,"Nome"           ]],
  ['pwd'   ,["Paraula de pas"     ,"Contraseña"            ,"Password"  ,"Mot de passe","Passwort"      ,"Parola"         ,"Parola d'ordine"]],
  ['cancel',["Cancel·lar"         ,"Cancelar"              ,"Cancel"    ,"Annuler"     ,"Absagen"       ,"Anulare"        ,"Annulla"        ]],
])

export const Login2 = ( UserAndLang: MySetter[]  ) => {

  const [user, setUser] = useState<User>({ name: "", pwd: "" });

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    const name = event.target.name;
    const value = event.target.value;
    setUser((user) => ({ ...user, [name]: value })); // Uses the same change function for all fields!!
  };

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    UserAndLang[0].setParam(user.name)
    alert(user + "-- language -->" + UserAndLang[1].param);
  };

  const opc='va'

  return (
    <div
      className="container"
      style={{ width: 540, backgroundColor: blue[50], padding: 40, borderRadius:20 }}
    >
      <img src={require(`../images/logotipo-tavernes-valldigna-01-removebg.png`)} width="425" />
      <br /> <br /> <br />

      <h1 className='text-center '>{getI18nLabel('login',UserAndLang[1].param, i18nMap)}</h1>
      <br />
      <LangSel4 {...UserAndLang[1]} />
      <br /> <br /> <br />
           
      <form onSubmit={handleSubmit}>
        <div className="mb-3">
          <label htmlFor="name" className="form-label">
            {getI18nLabel('user',UserAndLang[1].param, i18nMap)}
          </label>
          <input
            type="text"
            name="name"
            id="name"
            value={user.name || ""}
            onChange={handleChange}
            className="form-control"
          />
        </div>
        <br />
        <div className="mb-3">
          <label htmlFor="pwd" className="form-label">
            {getI18nLabel('pwd',UserAndLang[1].param, i18nMap)}
          </label>
          <input
            type="password"
            name="pwd"
            id="pwd"
            value={user.pwd || ""}
            onChange={handleChange}
            className="form-control"
          />

        </div>
        <br />
        <br />  
        <div className="d-flex justify-content-around">
        <button type="button" className="btn btn-primary" > {getI18nLabel('login' ,UserAndLang[1].param, i18nMap)}</button>
        <button type="button" className="btn btn-secondary">{getI18nLabel('cancel',UserAndLang[1].param, i18nMap)}</button>
        </div>
      </form>
    </div>
  );
};

Important things:
1. To access the selected language, the MySetter structure is used.

 4. App.tsx file

import React, { useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import LangSel3 from './components/LangSel3'
import { MySetter } from './components/Types';
import  {Login2}  from './components/Login2';

export default function App() {
  
  const [userName, setUserName] = useState<string>('');
  const myUserName: MySetter ={param: userName, setParam: setUserName}

  const [lang, setLang] = useState<number>(6);
  const myLang: MySetter ={param: lang, setParam: setLang}
  
  const userAndLang: MySetter[] = [myUserName, myLang]
  return (
    <>
        <Login2 {...userAndLang} /> 
    </>
  );
}

5. Index.tsx

We should import the bootstrap CSS file !!
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import 'bootstrap/dist/css/bootstrap.min.css' // Import bootstrap css

import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);














 

Comentarios

Entradas populares de este blog

15. Next.js Tutorial (2). Fetching data. Async functions getStaticProps, getServerSideProps, getStaticPaths

14. Next.js Tutorial (1)

10. React deployment on Nginx (5). Passing client certificate DN to react client