Kako instalirati i koristiti React na Linuxu? Trebamo li proširiti svoje frontend znanje sa frameworkom u kojem jednostavno možemo podijeliti elemente web stranice (aplikacije) u samostalne komponente? Ovdje pitanje naravno sadržava i odgovor.

Isplati li se naučiti React?

Problem sa HTML/JS je taj da teško možemo održavati/nadograđivati veće projekte. React nam omogućava da se lakše snalazimo sa komponentama i da lakše povežemo evente preko state-a. Imamo i “build” opciju koja optimizira kod. Komponente možemo kasnije prekopirati u novi projekt i koristiti sa manjim prilagodbama.

Osim mobilne, možemo napraviti web aplikacije ili Electron desktop aplikacije. Posao React developera je tražen i dobro plaćen posao pa se definitivno isplati malo pozabaviti sa ovim što ćemo raditi u ovoj objavi.

Prije svega treba nam nodejs instaliran. Ako koristite neku od popularnijih linux distribucija šanse su velike da već imate node instaliran.

Ako nemamo node, možemo unijeti naredbu da izlistamo sve node paketiće:

apt-cache search node #debian/ubuntu
pacman -Ss node       #arch/manjaro

…nakon toga instaliramo sa:

sudo pacman -S nodejs npm   #arch/manjaro
sudo apt install nodejs npm #debian/ubuntu

Najbolji editor za React je VSCode. Ali pošto je VSCode tehnički spyware, postoji i verzija sa odstranjenom “telemetrijom”. Ova verzija se zove VSCodium i na stranici imate upute za instalaciju.

Instalacija projekta

Otvaramo terminal i unosimo:

npx create-react-app moja-aplikacija
cd moja-aplikacija
npm start

Instalacija će potrajati…

Za testne projekte možemo koristiti softlink sa ‘node_modules’ direktorija. Tako da samo prekopiramo sve osim ‘node_modules’ u novi dir a zatim ubacimo softlink (iliti folder shortcut, windowski rečeno).

Na ovaj način svaki novi instalirani paketić ide u samo jedan ‘node_modules’ direktorij.

Datoteke i mape

package.jsonnode config datoteka
public/npr. u index.html možemo uvesti vanjski js, css ili font
src/mapa našeg projekta
src/index.jsdatoteka koja se prva pokreće
src/App.jsdatoteka sa komponentama

Komponente

Komponente smještamo u ‘src/components’ ili da manje tipkamo skraćeno ‘src/comps’. Tako da header komponenta ide u ‘src/comps/header/header.js’. Možemo i direktno u src mapu ‘src/header/header’. Poželjno je da komponenta bude u mapi zato što komponenta može imati svoj css ili slike.

Funkcionalno i objektno-orijentirano

React se u 99% slučajeva koristi sa funkcionalnim komponentama (znaći “arrow function” komponenta) ali dokumentacija je uglavnom temeljena na OOP primjerima…

Clean start

Kada počistimo ‘index.js’ i ‘App.js’ dobijemo nešto ovako:

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

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
//App.js
function App() {
  return (
    <div className="App">
	<h1>Hello World!</h1>
    </div>
  );
}

export default App;

JSX

JSX je JS proširenje koje nam omogućava da oblikujemo HTML (template) kod direktno u JS-u. Za početak dovoljno je upamtiti da je bitno da koristimo ‘className’ umjesto ‘class’ i “for” kao “htmlFor” u HTML-u.

<div className="App">
  <h1>Hello World!</h1>
  <label htmlfor={this.input} />
</div>

Komponenta

mkdir ./src/comps
mkdir ./src/comps/big-button/
touch ./src/comps/big-button/big-button.js
touch ./src/comps/big-button/big-button.css
//big-button.js

import './big-button.css';

const BigButton = () => {
	
	const buttonText = "I'm big button!";
	
	return(
		<>	
			<div className="big-button"> {buttonText} </div>
		</>	
	);
};

export default BigButton;
/* big-button.css */

.big-button {
	background: blueviolet;
	padding: 20px;
	color: white;
	display: inline-block;
	cursor: pointer;
	user-select: none;
}

.big-button:hover {
	background: black;
}

Uvoz komponente

import BigButton from './comps/big-button/big-button';

function App() {
  return (
    <div className="App">
		<BigButton />
    </div>
  );
}

export default App;

Props

“Props” je način na koji unosimo podatke u komponentu. Radi se o XML atributima. Usput, preko props atributa možemo poslati funkcije i komponente. “Props drilling” je kada “lančano” prenosimo vrijednost iz jednu u drugu komponentu preko JSX props atributa.

<Demo imProp="hello" />

je dostupan preko:

const Demo = (props) => {
   console.log( props.imProp );
}

Pa za primjer, da napravimo još jednu komponentu:

mkdir ./src/comps/button-two/
touch ./src/comps/button-two/button-two.js
touch ./src/comps/button-two/button-two.css
//button-two.js
import './button-two.css';

const ButtonTwo = (props) => {
			
	return(
		<>	
			<div className="button-two"> {props.buttonText} </div>
		</>	
	);
};

export default ButtonTwo;
/*button-two.css*/
.button-two {
	background: green;
	padding: 20px;
	color: white;
	display: inline-block;
	cursor: pointer;
	user-select: none;
}

.button-two:hover {
	background: black;
}

Ugniježđenje (nesting)

<Main>
	<Edit />
	<Message />
	<About />
</Main>

Unutarnje componente dobivamo sa “props.children”:

const Main = (props) => {
		
	return(
		<div className="main">	
			{props.children}
		</div>	
	);

};

export default Main;

onClick i onChange eventi

Klik event integriramo ‘vako:

<button onClick={() => console.log('hello')  } > hello </button>

A input vrijednost dobivamo sa:

<input
        type="text"
        onChange={(e) => console.log(e.target.value)  }
/>

Usput, vrijednost je prikazana dvaput u konzoli zbog development moda (StrictMode).

State i useState

State je vrijednost koja na promjeni ponovno renderira prikaz aplikacije.
Znači imamo varijablu i metodu. U state pohranjujemo sve vrijednosti pa
i JSON liste koje preuzimamo preko API-ja.

“useState” hook (kuka) nam omogučava da postavimo state varijablu i metodu.

import React, { useState } from 'react';
const [count, setCount] = useState(0);

U ovom slučaju, “count” je varijabla a “setCount” metoda. “useState(0)”
znači da je “0” početna vrijednost “count” varijable.

Za primjer ćemo složiti novu komponentu. Komponenta mijenja stil elementa (css klasu) na klik.

mkdir ./src/comps/state-test/
touch ./src/comps/state-test/state-test.js
touch ./src/comps/state-test/state-test.css
//state-test.js

import React, { useState } from 'react';
import './state-test.css';

const Statetest = () => {
			
	const [style, setStyle] = useState('green-button');				
		
	const toggleStyle = () => {
		const styleClass = (style == 'green-button') ? 'purple-button' : 'green-button';
		setStyle(styleClass);
	}
			
	return(
		<>	
			<div className={style} onClick={() => toggleStyle()} > Toggle Style </div>
		</>	
	);

};

export default Statetest;
/* state-test.css */

.green-button {
	background: green;
	padding: 20px;
	color: white;
	display: inline-block;
	cursor: pointer;
	user-select: none;
}

.green-button:hover {
	background: black;
}

.purple-button {
	background: blueviolet;
	padding: 20px;
	color: white;
	display: inline-block;
	cursor: pointer;
	user-select: none;
}

.purple-button:hover {
	background: black;
}

Dugme mijenja boju na klik.

useRef

Sa “useRef” hook-om možemo dobiti referencu HTML elementa.

import { useRef } from "react";
//...
console.log(inputElement); // => INPUT HTML ELEMENT
//...
<input type="text" ref={inputElement} />

useReducer

“useReducer” hook možemo koristiti za kompliciraniji state managment. State možemo odvojiti u jednu datoteku. Imamo objekt sa početnim (init) vrijednostima i objekt sa metodama/varijablama state-a.

Primjer

mkdir ./src/comps/reducer-test/
touch ./src/comps/reducer-test/reducer-test.js
touch ./src/comps/reducer-test/reducer-test.css
touch ./src/comps/reducer-test/reducer-test-state.js
//reducer-test-state.js
export const initialState = {
  theme: 'light-theme', 
  inputText: 'No value!'
};

export const reducer = (state, action) => {
  switch (action.type) {
  
    case 'setInputText': {
      return { ...state, inputText: action.payload };
    }
    
    case 'setDarkTheme': {
      return { ...state, theme: 'dark-theme' };
    } 
     
    case 'setLightTheme': {
      return { ...state, theme: 'light-theme' };
    }  
        
    default: {
      return state;
    }
  }
}; 
//reducer-test.js
import './reducer-test.css';
import React, { useReducer } from 'react';
import { initialState, reducer } from './reducer-test-state.js';

const ReducerTest = (props) => {
	const [state, dispatch] = useReducer(reducer, initialState);
	
	const toggleStyle = () => {
		if (state.theme === 'light-theme'){
			dispatch({ type: 'setDarkTheme' });
		}
		else {
			dispatch({ type: 'setLightTheme' });
		}
		 
		console.log(state.theme);
	}
	
	
	return(	
		<div className={state.theme}>
			<h1>Hello component!</h1>
			
			
			<p>{state.inputText}</p>
			<br />
			<input className="input"         
				type="text"
				onChange={(e) => dispatch({ type: 'setInputText', payload: e.target.value })}
				placeholder="Enter text..."
			/>
			
			
			<div className="big-button" onClick={() => toggleStyle()  } > Toggle Style </div>
		</div>
	);

};

export default ReducerTest;
/* reducer-test.css */
.dark-theme{
	color: white;
	background-color: black;
}

.light-theme{
	color: black;
	background-color: white;
}

.light-theme , .dark-theme{
	margin: 10px;
	padding:10px;
	box-shadow: 0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)
}

.big-button {
	background: blueviolet;
	padding: 20px;
	color: white;
	display: inline-block;
	cursor: pointer;
	user-select: none;
}

.big-button:hover {
	background: black;
}

.input{
	padding:8px;
	display:block;
	border:none;
	border-bottom:1px solid #ccc;
	margin-bottom: 10px;
}

Znači ovdje uvozimo “reducer” i “initialState” objekte i dodajemo ih u “useReducer” funkciju a uzimamo “state” i “dispatch”. Da postavimo “light-theme” koristimo:

dispatch({ type: 'setLightTheme' });

State varijabli pristupamo sa {state.theme}.

useEffect

useEffect koristimo za dvije stvari. Da pokrenemo nešto sa onLoad eventom ili da (ako imamo array kao drugi argument) da pokrenemo nešto na promjeni varijabli state-a u tom array-u.

// slično kao componentDidMount / componentDidUpdate:  
useEffect(() => {      
	console.log('App loaded!');
});

// 
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); //ako se 'count' promjeni, pokreni kod...

useContext

useContext služi za globalni ili app state. S ovim hook-om možemo podjeliti globalni state bez props drillinga (dodavanja varijabli preko props). Za ovo prvo trebamo context datoteku.

//app-context.js
import { createContext } from "react";
export const AppContext = createContext(null);

U “App.js”, kao primjer unosimo “count” i “setCount” u kontekst:

import { AppContext } from "./app-context";

const App = () => {
	
	const [count, setCount] = useState(0);
	
	return (
	     <AppContext.Provider value={[count, setCount]}>
	        
	        <ComponentOne />
         
         </AppContext.Provider>
	);
};

A zatim u komponenti, također uvodimo “app-context.js” i uzimamo state varijablu/metodu.

import React, { useContext } from "react";
import { AppContext } from "./app-context";

const ComponentOne = () => {
	
	const [ count, setCount ] = useContext(AppContext);
		
	return(
		<>	
			...
		</>	
	);

};

Na ovaj način možemo složiti jednu datoteku sa globalnim state-om a zatim
uvesti metode/varijable state-a u app-context.

Url putanje (routes) i učitavanje podataka po potrebi

U React-u imamo opciju da ugradimo URL putanje u aplikaciju. To radimo sa “react-router-dom” modulom. Osim toga koristimo “Suspense” i “lazy” proširenja koja služe za učitavanje podataka po potrebi.

Primjer

import React, { Suspense, lazy, useReducer} from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

const Main = lazy(() => import('./pages/main/main'));
const About = lazy(() => import('./pages/about/about'));

const App = () => {

	return (

		  <Router>
			<Suspense fallback={<div>Loading...</div>}>
			  <Routes>
				<Route path="/" element={<Main />} />
				<Route path="/about" element={<About />} />
			  </Routes>
			</Suspense>
		  </Router>
	  
	);
};

export default App;

Gore u primjeru, linija “<Suspense fallback={<div>Loading…</div>}>”
sadržava element/komponentu, prikazanu dok se “teža” komponenta učitava.

Build

Projekt kompajliramo sa:

npm run build

Nakon toga imamo optimizirani kod u “/build” direktoriju.


Github demo

Za primjer, složio sam jedan demo projekt koji demonstrira sve ovo čime smo se bavili u ovoj objavi.

Usput, nakon što preuzmete arhivu sa github stranice:

npm install
npm start