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 displayedexport 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/...'
Save the file and restart
sudo sysctl -p
4.1 Different names of attributes in HTML and React JSX
<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.
5. Using the "spread" operator to manage the state
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)
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
Publicar un comentario