2. React Material UI, common ERRORS, Spread operator, Avoiding Multiple executions, execution, Axios

 1. Create the application and add imports of Material UI

# Open Visual studio and open a terminal
# Create react app and go into the app folder
npx create-react-app my-app01 --template typescript
cd my-app01

#Install Material UI (with lab)
npm install @mui/material @emotion/react @emotion/styled @mui/lab

#Install Material UI icons
npm install @mui/icons-material  

#Font Awesome
npm i --save @fortawesome/fontawesome-svg-core
npm install --save @fortawesome/free-solid-svg-icons
npm install --save @fortawesome/react-fontawesome

#Install font Roboto
npm install @fontsource/roboto

#Install Axios (POST, GET..)
npm install axios

#Install  bootstrap
npm install react-bootstrap bootstrap

#Install router-dom
npm i -D react-router-dom

#If "require" is needed
npm i --save-dev @types/node

#If "md5" encryption is needed
npm install md5
npm install --save @types/md5

#If WebSockets is needed
npm i react-use-websocket

2. Import Roboto font and bootstrap CSS in the index.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'

import '@fontsource/roboto/300.css'
import '@fontsource/roboto/400.css'
import '@fontsource/roboto/500.css'
import '@fontsource/roboto/700.css'

import 'bootstrap/dist/css/bootstrap.min.css' //Import bootstrap css

3. JSX functions considerations and errors

3.1 Declaring a function with one parameter not enclosed in {}

As far as I can see , you must call the function using an object and not using directly the properties!! 
// No arguments are enclosed by {}
function MyLabel (text: string) {
return (
<label>{text}</label>
);
}

To call this function NOTE the errors:

export function TaskApp1() {
    const mytext='first try';
    const myprops ={text: 'second try'}; //defining an object
    const myprops1:any ={text: 'third try'}; //defining a predefined object

    return (
        <>
            //1. Fails: Type '{ text: string; }' is not assignable to type 'string'.ts(2322)
            <MyLabel text={mytext}></MyLabel>  

            //2. Fails: Type '{ text: string; }' is not assignable to type 'string'.ts(2322)
            <MyLabel {...myprops}></MyLabel>

            //OK 
            <MyLabel {...myprops1}></MyLabel>
        </>
    )
}

3.2 Declaring a function with two or more parameters not enclosed in {}

As far as I can see , you must call the function using an object and not using directly the properties!! 

// No arguments are enclosed by {}
function MyLabel (text1: string, text2: string) {
return (
<label>{text1}-{text2}</label>
);
}


To call this function NOTE the errors:

export function TaskApp2() {
    const mytext1='Test 1a';
    const mytext2='Test 2a';
    const myprops ={text1: 'Test 1b', text2: 'Text2b'};
    const myprops1:any ={text1: 'Test 1c', text2: 'Test 2c'};
    

    return (
        <>

//1. Fails: Type '{ text1: string; text2: string; }' is not assignable to type 'string'.ts(2322) <MyLabel text1={mytext1} text2={mytext2}></MyLabel>

//2. Fails: Type '{ text1: string; text2: string; }' is not assignable to type 'string'.ts(2322)
<MyLabel {...myprops}></MyLabel>

//OK
<MyLabel {...myprops1}></MyLabel>

</> ) }

3.3 Declaring a function with one or more parameters enclosed in {}

As far as I can see , you must call the function using an object and not using directly the properties!! 

// Arguments are enclosed by {}
function MyLabel ({text1,text2}:{text1: string, text2: string}) {
return (
<label>{text1}-{text2}</label>
);
}


To call this function , no errors are displayed

export function TaskApp2() {
    const mytext1='Test 1a';
    const mytext2='Test 2a';
    const myprops ={text1: 'Test 1b', text2: 'Text2b'};
    const myprops1:any ={text1: 'Test 1c', text2: 'Test 2c'};
    

    return (
        <>

//OK <MyLabel text1={mytext1} text2={mytext2}></MyLabel>

//OK
<MyLabel {...myprops}></MyLabel>

//OK
<MyLabel {...myprops1}></MyLabel>

</> ) }

4. Common errors

4.0 Error: ENOSPC: System limit for number of file watchers reached, watch '/home/ximo/...'

Stackoverflow  says that the number of files monitored by inotify package has reached the limit
So in Ubuntu 

sudo gedit /etc/sysctl.conf

Add a line at the bottom

fs.inotify.max_user_watches=524288

Save the file and restart 

sudo sysctl -p


4.1 Different names of attributes in HTML and React JSX

Using for and class instead of htmlFor and className, and using comments in JSX is one of the most frequent errors.
<label htmlFor="name"     >   <!--NO <label for="name"     > -->
<label className="myclass">   <!--NO <label class="myclass"> -->


4.2 Different types of comments in JSX

<!--------------------------------------------------------
   comments in JSX
---------------------------------------------------------->

<!-- Comments in properties -->
<input 
  type="text"
  //aria-describedby='textHelp'
/>

<!-- comments in whole elements -->
{/* <input type='text' /> */}

4.3 Reading local files with "require" using (``) 

Reading local files and remote files can be a bit tricky. In the Autocomplete element here is a source file with some scenarios where the "require" function does not work. In this example, a language selector is defined and the "lang number" is exported to the caller components.

import React from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";
import { Dispatch, SetStateAction } from "react"

import RoIcon from "../images/ro.png";

export interface MySetter {
    param: any
    setParam:  Dispatch<SetStateAction<any>>
}

export interface User {
    name: string
    pwd: string
}
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 LangSel3(myLang: MySetter) { const [value, setValue] = React.useState<CountryType>( countries[myLang.param] ); const [inputValue, setInputValue] = React.useState<string>(""); return ( <div className="container"> {/*       <div>{`value: ${value !== null ? `'${JSON.stringify(value)}'` : 'null'}`}</div>       <div>{`inputValue: '${inputValue}'`}</div>              <br />       */} <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={RoIcon} //WORKS //src={getImgFile('va')} //NOT WORKING //src={getImgFile(option.code.toLowerCase())} //NOT WORKING //src={require(`../images/ca.png`)} //WORKS //src={require(`../images/${bb}.png`)} //WORKS //src={require(`../images/${option.code.toLowerCase()}.png`)} //WORKS src={require(`../images/${option.code.toLowerCase()}.png`)} //src={require(getImgFileName(`${option.code.toLowerCase()}`))} //NOT WORKING 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> ); }

4.4 TS7031: Binding element 'to' implicitly has an 'any' type.

Occurs when declaring a function whose parameters are not typified. See the Borislav Hadzhiev solution. To solve this error, typify the parameters of the function as follows. 

// With Functions:Error: Binding element 'id' implicitly has an 'any' type.ts(7031)
//function getEmployee({ id, name }) {
//Solution 01
function getEmployee({ id, name }: {id:number; name:string;}) {
//Solution 02 ?? But fails in jsx functions !!!!!!
function getEmployee(id:number, name:string) {
return { id, name };
}
//With Class methods class Employee { id: number; name: string; // Error: Binding element 'name' implicitly has an 'any' type.ts(7031) //constructor({ id, name }) { //solution constructor({ id, name }: {id:number; name: string;}) { this.id = id; this.name = name; } }

But if you use pure typescript functions (files with extension .ts) you can write:

export function waitForConn (ws:WebSocket, callback:Function, interval:number)  {
    if (ws.readyState === 1) {
        callback();
    }
}


4.5 TS2322: Property 'children' does not exist on type 'IntrinsicAttributes & { to: string; props: { [x: string]: any; }; }'.

This occurs when accessing the "children" property in a component for which we haven't typed the props. a function whose parameters are not typified. To solve this error the "children" property should be typified as "React.ReactNode". See the Borislav Hadzhiev solution and microcodelab_dev

import React from "react";
import { useLocation, NavLink, NavLinkProps } from "react-router-dom";

// ERROR:1 No type definition in arguments
// ERROR:2 Missing component children as React.ReactNode
// NOTE: You can use {to:string; children:React.ReactNode} & NavLinkProps
//export default function QueryNavLink({ to, ...props }) {
export default function QueryNavLink(
    { to, children, ...props }: 
    { to:string; children:React.ReactNode} & NavLinkProps ) { 
  const location = useLocation();
  return (
    <>
    {/* No Children added !!
    <NavLink to={to + location.search} {...props} />;
    */}
    <NavLink to={`${to}${location.search}`} {...props}>
      {children}
    </NavLink>
    </>
  )  
}

4.6 TS2322: Type '{ props: Props; }' is not assignable to type 'IntrinsicAttributes & Props'. Property 'props' does not exist on type 'IntrinsicAttributes & Props'.ts(2322).

This occurs when passing an object instead of individual properties of the object

export class Person {
    name: string;
    imageId: string;

    constructor(name: string, imageId: string) {
        this.name = name;
        this.imageId = imageId;
    }
}

export class Props {
    person : Person;
    size?: string ='s';
    
    constructor(person : Person, size?: string) {
        this.person = person;
        this.size = size;
}
const props1: Props ={
  person: new Person('Edu','7vQD0fP'), 
  size : 's', 
}  

root.render(
   <Profile props={props1} /> //ERROR ts:2322
);


This is solved or using the ... operator or separating all the elements of props1. Both solutions are displayed

root.render(
    <>
      <Profile {...props1} /> 
      <Profile size={props1.size} person={{name: 'edu', imageId:'7vQD0fP'}} />    
      </React.StrictMode> 
    </>
);


4.7 TS2802: The type"IterableIterator<[string, string]>" can only be iterated with  "--downlevelIteration" or with "--target" equals"es2015" or superior.

Occurs when accessing trying to iterate as in the example:

// searchParams.entries() is of type IterableIterator<[string, string]
for (const entry of searchParams.entries()) {
    const [param, value] = entry;
    console.log(param, value);
}

To solve it change the file tsconfig.json

//change "target":"es5" with "target":"es2015" 
{
  "compilerOptions": {
    "target": "es2015",
    
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
....
  

4.8 TS2802: Type '{ tasks: any[]; onChangeTask: (task: any) => void; onDeleteTask: (taskId: number) => void; }' is missing the following properties from type 'any[]': length, pop, push, concat, and 29 more. ts(2740)

Occurs  in the example. The solution is in StackOverflow and consists of typifying the variable 

//Error
let props ={ tasks: state, onChangeTask: handleChangeTask, onDeleteTask: handleDeleteTask}  ;

//Solution
let props: any ={ tasks: state, onChangeTask: handleChangeTask, onDeleteTask: handleDeleteTask}  ;

return (
   <>
      <TaskList {...props}
         /*
         tasks={state}
         onChangeTask={handleChangeTask}
         onDeleteTask={handleDeleteTask}
         */
      />
   </>
)


4.9 useLocation() may be used only in the context of a <Router> component.

As Hadzhiev states in order to use "useLocation" from react-dom, the component using it must be referenced from another component in a <BrouserRouter> or <Router> tag.

For instance if component App.tsx has a <BrowseRouter> tag in its code, it cannot use "useLocation", but the components referenced in the <BrowseRouter> tag, can use "useLocation"

5. Using the "spread" operator to manage the state

In this example, only one function is used to set the state of the "user" object. The name and the value are got from the change event
The spread operator (...) lets the updating of only some fields.
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!!
};

6. Avoiding Multiple executions of functions (Borislav Hadzhiev)

Use UseRef to define a variable to verify if it is the first render and use it in a UseEffect (,[]) hook

export default function Main() {
  const isFirstRender = useRef(true); //Avoid executing code in the beginning !!
  const [lang, setLang] =useState<string>('es') 
     
  
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return; // return early if first render
    }
    
    setLang('en') // Now only executes 1 time
  }, []);

  .....
}

7. Using Axios

https://jsonplaceholder.typicode.com/todos  offers a fake rest server for testing purposes. In this case, a get request is shown. Post, delete, print .. request can be called too. Also parameters in the URL are allowed (for instance TEST_URL = 'https://jsonplaceholder.typicode.com/todos/1'). The file is AxiosTest.tsx


import React, { useEffect, useState } from 'react'

const TEST_URL='https://jsonplaceholder.typicode.com/todos'
    
export default function AxiosTest() {

  //Using typing Object[], any data type is admitted
  const [props, setProps] = useState<Object[]>([])
    
  // reference to axios for typescript
  const axios = require('axios').default;
    
  async function getProps() {
    try {
      const response = await axios.get(TEST_URL);
      setProps(response?.data)
      console.log(response);
    } catch (error) {
      console.error(error); // Maybe alert can be used
    }
  }

  //Get data in the beginning
  useEffect(() => {
    getProps()
  }, []) //in the beginning []

  return (
    <div>
      Testing rest server:
      <br />
      {JSON.stringify(props)} //Displays the results
    </div>
  )
}

Another approach is splitting this code into 2 components: Axios01.ts and AxiosTest01.tsx

Axios01.ts: defining axios logic

// reference to axios for typescript
export const axios = require('axios').default;

export interface setFuncAny { 
  (source: any): void
}

export function EDUStringfy(myObj:any) { 
  if (!myObj) {
    return ''
  }
  return JSON.stringify(myObj)  
}

//Config is optional parameter
export async function axiosGet( url: string, mySetFunc: setFuncAny, config?:Object) {
  try {
    const response = await axios.get(url, config)
    mySetFunc(response?.data)
    console.log(response);
  } catch (error) {
    console.error(error);
    alert(JSON.stringify(error));
    mySetFunc([])
  }
}

//No need to receive data in most cases
//Config is optional parameter
export async function axiosPost( url: string, myObj:Object,  config?:Object) {
  try {
    const response = config ? await axios.post(url, myObj, config) : await axios.post(url, myObj);
    if (response?.data) {
      //console.log(response);
      return response?.data
    } 
    return []
  
  } catch (error) {
    console.error(error);
    alert(JSON.stringify(error));
    return []
  }
}


AxiosTest01.tsx: Using the axios logic

import React, { useEffect, useState } from 'react'
import { axiosGet, EDUStringfy } from './Axios01';

const TEST_URL='https://jsonplaceholder.typicode.com/todos/1'
    
export default function AxiosTest() {

  //Using typing Object[], any data type is admitted
  const [props01, setProps01] = useState<Object[]>([])
  
  //Get data in the begining
  useEffect(() => {
    axiosGet(TEST_URL,setProps01)

  }, []) //in the begining []

  return (
    <div>
      Testing rest server 01:
      <br />
      {EDUStringfy(props01)}
    </div>
  )
}


NOTE:

In the last component, the request to the rest service is called twice. It is due to the fact that it is executed in development mode. Maybe removing <React.StrictMode> from the index.tsx file can remove this fact. See CodingDeft for more info.





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