useRef

Η useRef μπορεί να χρησιμοποιηθεί σε δύο περιπτώσεις.

Καταχώρηση τιμών σε ref variable

Η πρώτη περίπτωση στην οποία χρησιμοποιείται η useRef είναι όταν θέλουμε να κρατήσουμε την τιμή μιας μεταβλητής χωρίς να χάνεται ή (αρχικοποιείται) σε κάθε re-render.

Στο παρακάτω παράδειγμα έχουμε ένα κουμπί που γίνεται toggled σε κάθε κλικ.

import { useState } from 'react';
import './assets/main.css';

export default function App() {
  const [toggleClass, setToggleClass] = useState("off");

  function handleClick(e) {
    setToggleClass( (toggleClass === "on") ? "off" : "on");
  }

  return (
    <button className = {toggleClass} onClick={handleClick}>you toggled me</button>
  );
}

Θα πρέπει να υπάρχουν οι αντίστοιχες κλάσεις on και off καταχωρημένες στα css για να δείτε σωστά το αποτέλεσμα.

Στο παρακάτω παράδειγμα έχουμε ένα κουμπί που γίνεται toggled σε κάθε κλικ αλλά θέλουμε να μετράμε και τα κλικ.

Η προτεινόμενη λύση όμως δεν λειτουργεί όπως μπορείτε να δείτε διότι σε κάθε re-render η counter μηδενίζεται.

import { useState } from 'react';
import './assets/main.css';

export default function App() {
  const [toggleClass, setToggleClass] = useState("off");
  let counter = 0;

  function handleClick() {
    setToggleClass( (toggleClass === "on") ? "off" : "on");
    counter++;
  }

  return (
    <button className = {toggleClass} onClick={handleClick}>you toggled me {counter} times</button>
  );
}

Για να λύσουμε το παραπάνω πρόβλημα χρησιμοποιούμε μια ref variable. Έτσι, σε κάθε re-render η ref variable κρατάει την τιμή της.

import { useRef } from 'react';
import { useState } from 'react';
import './assets/main.css';

export default function App() {
  const [toggleClass, setToggleClass] = useState("off");
  let counterRef = useRef(0);

  function handleClick(e) {
    setToggleClass( (toggleClass === "on") ? "off" : "on");
    counterRef.current++;
  }

  return (
    <button className = {toggleClass} onClick={handleClick}>you toggled me {counterRef.current} times</button>
  );
}

Στη ref variable αποθηκεύεται ένα αντικείμενο (json) με την ιδιότητα current και στη οποία καταχωρούνται οι όποιες τιμές.

Η ref variable είναι mutable, άρα μπορούμε να κάνουμε γραφή-ανάγνωση (read-write).

Η ref variable δεν προκαλεί (trigger) re-render.

Όπως όλες οι hook functions, δηλώνεται στην αρχή του component.

Μπορούμε να έχουμε περισσότερες από μία ref variables.

Η ref variable αναφέρεται σε ένα στοιχείο html και όχι σε components.

Αναφορά ref variable σε στοιχείο html

Η δεύτερη περίπτωση στην οποία χρησιμοποιείται η useRef είναι όταν θέλουμε να έχουμε αναφορά σε ένα (ή περισσότερα) στοιχεία του DOM.

import { useRef } from 'react';

export default function App() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref ={inputRef} />
      <button onClick ={handleClick}>Focus the input</button>
    </>
  );
}

Το παραπάνω παράδειγμα θα μπορούσε να αντικατασταθεί με το παρακάτω χωρίς useRef.

Δεν το προτιμούμε διότι σε περίπτωση αλλαγής του DOM μπορεί να υπάρξει πρόβλημα.

export default function App() {
  
  function handleClick() {
    document.querySelector("input").focus();
  }

  return (
    <>
      <input />
      <button onClick = {handleClick}>Focus the input</button>
    </>
  );
}

Πρέπει να χρησιμοποιηθεί η ειδική ιδιότητα ref στο στοιχείο του DOM στο οποίο θέλουμε να έχουμε αναφορά.

Άλλο ένα παράδειγμα με αναφορά σε στοιχείο του DOM μέσω της ref.

import { useRef } from 'react';

export default function App() {
  const inputRef = useRef(null);

  function handleFocusInput() {
    inputRef.current.focus();
  }

  function handleClearInput() {
    inputRef.current.value = "";
  }

  return (
  <>
    <input ref={inputRef} />
    <button onClick={handleFocusInput}>Focus the input</button>
    <button onClick={handleClearInput}>Clear the input</button>
  </>
  );
}

Πολλαπλές μεταβλητές ref

Μπορούμε επίσης να έχουμε πολλαπλές μεταβλητές ref.

import { useRef } from 'react';
import "./assets/main.css";

export default function App() {
  const firstCatRef = useRef(null);
  const secondCatRef = useRef(null);
  const thirdCatRef = useRef(null);

  function handleScrollCat(index) {
    let cat;
    switch (index) {
      case 0:
        cat = firstCatRef;
        break;
      case 1:
        cat = secondCatRef;
      break;      
      case 2:
        cat = thirdCatRef;
      break;    
    }
    cat.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  return (
    <>
      <nav>
        <button onClick={() => handleScrollCat(0)}>Tom</button>
        <button onClick={() => handleScrollCat(1)}>Maru</button>
        <button onClick={() => handleScrollCat(2)}>Jellylorum</button>
      </nav>
      <div className = "image-container">
            <img
              src="https://placekitten.com/g/200/200"
              alt="Tom"
              ref={firstCatRef}
            />
            <img
              src="https://placekitten.com/g/300/200"
              alt="Maru"
              ref={secondCatRef}
            />
            <img
              src="https://placekitten.com/g/250/200"
              alt="Jellylorum"
              ref={thirdCatRef}
            />
      </div>
    </>
  );
}

Πίνακας μεταβλητών ref

Αν έχουμε πίνακα μεταβλητών ref ο οποίος μπορεί να είναι στατικός ή δυναμικός, δουλεύουμε όπως στο παράδειγμα που ακολουθεί.

import { useRef } from 'react';
import "./assets/main.css";

const catList = [];
for (let i = 0; i < 10; i++) {
  catList.push({
    id: i,
    imageUrl: 'https://placekitten.com/250/200?image=' + i
  });
}

export default function App() {
  const catRef = useRef([]);
  let counter = 0;

  function handlePrev() {
    counter--;
    if(counter < 0) {
      counter = 0;
      return;
    }
    catRef.current[catList[counter].id].scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function handleNext() {
    counter++;
    if(counter === catList.length) {
      counter--;
      return;
    }
    catRef.current[catList[counter].id].scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  return (
    <>
      <nav>
        <button onClick = {() => handlePrev()}>Prev</button>
        <button onClick = {() => handleNext()}>Next</button>
      </nav>

      <div className = "image-container">
        {catList.map((item) => {
        return <img
            key = {item.id}
            src = {item.imageUrl}
            alt = {"hello " + item.id}
            ref = {(node) => {catRef.current[item.id] = node}}
          />
        })}
      </div>
      {handlePrev()}
    </>
  );
}

Χρησιμοποιούμε την εντολή ref = {(node) => {catRef.current[item.id] = node}} για να περάσουμε την ref σε κάθε στοιχείο ή node.

Στην αρχική δήλωση της useRef γράφουμε: const catRef = useRef([]);

Χρησιμοποιούμε επίσης στο παράδειγμα μια απλή μεταβλητή let counter = 0; για να κρατάμε τη τρέχουσα εικόνα. Εδώ η counter αρχικοποιείται μία φορά γιατί δεν έχουμε re-rendering.

Διαγραφή στοιχείου πίνακα μέσω ref

import { useRef } from 'react';
import "./assets/main.css";

const catList = [];
for (let i = 0; i < 10; i++) {
  catList.push({
    id: i,
    imageUrl: 'https://placekitten.com/250/200?image=' + i
  });
}

export default function App() {
  const catRef = useRef([]);
  let counter = 0;

  function handlePrev() {
    counter--;
    if(counter < 0) {
      counter = 0;
      return;
    }
    catRef.current[catList[counter].id].scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function handleNext() {
    counter++;
    if(counter === catList.length) {
      counter--;
      return;
    }
    catRef.current[catList[counter].id].scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function handleDelete() {
    catRef.current[catList[counter].id].remove();
    catList.splice(counter, 1);
  }

  return (
    <>
      <nav>
        <button onClick = {() => handlePrev()}>Prev</button>
        <button onClick = {() => handleNext()}>Next</button>
        <button onClick = {() => handleDelete()}>Delete Current</button>
      </nav>

      <div className = "image-container">
        {catList.map((item) => {
        return <img
            key = {item.id}
            src = {item.imageUrl}
            alt = {"hello " + item.id}
            ref = {(node) => {catRef.current[item.id] = node}}
          />
        })}
      </div>
      {handlePrev()}
    </>
  );
}

Πρόσβαση σε DOM nodes διαφορετικών Component

Για προσβαση σε dom node ενός άλλου component θα πρέπει να έχει προηγηθεί η αποδοχή της πρόσβασης από το component. Αυτό γίνεται με τη βοήθεια της συνάρτησης forwardRef() όπως φαίνεται παρακάτω.

import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

export default function App() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <>
      <MyInput ref={ref} />
      <button onClick={handleClick}>Focus the input</button>
    </>
  );
}