Tag logo

Short notes about React

Aug 11, 2024/
#react
/-10 min

Yaşayan Yazı, bir yerimden uydurduğum bir terimdir ve ileriki dönemde içeriği zenginleşebilecek ama şu anki haliyle de faydalı olabilecek yazıları belirtir.

Geçenlerde yeni React dökümantasyonuna baştan sona göz atmaya karar vermiştim. Bu esnada aldığım ve framework'e dair anahtar noktaları kısa notlarımı örneklerle paylaşacağım.

Genel

  • React'ın "Strict Mode"u development esnasında her komponent fonksiyonunu iki kez çağırır ve Pure olmayan komponentleri yakalamanızı sağlar. Render sayısını hesaplarken buna dikkat edin.

  • Uygulama başladığında yalnızca bir kez çalıştırılacak kod komponent dışına koyulabilir.

    1if (typeof window !== "undefined") {
    2  checkAuthToken();
    3  loadDataFromLocalStorage();
    4}
    5
    6function App() {
    7  // ...
    8}

Rendering

  • Komponent, renderlanacak HTML elementleri döndüren fonksiyondur. İşin aslına bakarsak HTML gibi duran ancak JSX dediğimiz bir dil uzantısıdır. Kendisi de typescript olduğundan typescript'in elementlerle kullanılmasını kolaylaştırır. Nihayetinde kendisi de HTML'e çevrilir.

    1function MyButton() {
    2  return <button>I'm a button</button>;
    3}
    4
    5export default function MyApp() {
    6  return (
    7    <div>
    8      <h1 className="heading-1">Welcome to my app</h1>
    9      <MyButton />
    10    </div>
    11  );
    12}
  • Bir komponent içerisine öğe almayacaksa kapanan etiket (closing tag) ile yazılmalıdır.

    1<CustomElement />
  • Birden fazla element return edilemez. Bunları

    <div>...</div>
    ya da
    <>...</>
    etrafına sarmalıyız. İkincisinin adı Fragment olarak geçer ve HTML'e ekstra bir element eklemeden bu işi görür. Sebebi ise bir fonksiyonun array olmadığı takdirde birden fazla sonuç döndürememesidir.

    1<div>
    2  <CustomElement />
    3  <div />
    4</div>
  • typescript değişkenleri ve ifadeleri (expression) JSX'de süslü parantez içinde kullanılabilir.

    1const user = {
    2  fullName: "Hedy Lamarr",
    3  imageUrl: "https://i.imgur.com/yXOvdOSs.jpg",
    4  imageSize: 90,
    5};
    6
    7export default function Profile() {
    8  return (
    9    <>
    10      <h1>{user.fullName}</h1>
    11      <img
    12        className="avatar"
    13        src={user.imageUrl}
    14        alt={`Photo of ${user.fullName}`}
    15        style={{
    16          width: user.imageSize,
    17          height: user.imageSize,
    18        }}
    19      />
    20    </>
    21  );
    22}
  • Koşullu render için 3 farklı yaklaşım uygulayabiliriz. Koşullardan dönen

    false
    ,
    null
    ve
    undefined
    değerleri JSX'de gözardı edilir.

    1// first way: if statement
    2if (isPacked) {
    3  return <li className="item">{name}</li>;
    4}
    5
    6return <li className="item">{name}</li>;
    7
    8// second way: ternary operator
    9return <li className="item">{isPacked ? name + " ✔" : name}</li>;
    10
    11// third way: and operator
    12return (
    13  <li className="item">
    14    {name} {isPacked && "✔"}
    15  </li>
    16);
  • Birden fazla elementi render etmek için JSX array'leri kullanabiliriz. Array değiştiğinde React'ın doğru öğeleri render etmesi için her bir öğeye

    key
    prop vermeliyiz.

    1const products = [
    2  { title: "Cabbage", id: 1 },
    3  { title: "Garlic", id: 2 },
    4  { title: "Apple", id: 3 },
    5];
    6
    7const listItems = products.map((product) => (
    8  <li key={product.id}>{product.title}</li>
    9));
    10
    11return <ul>{listItems}</ul>;

Değerlerin key özelliği aynı olursa, bir uyarı mesajı alırsınız: "Warning: Each child in a list should have a unique 'key' prop.".

Anahtar değiştiğinde komponent değişmemişse bile rerender edilir. Bu nedenle, Math.random gibi her çağrıldığında farklı bir değer üreten bir fonksiyon kullanmamalısınız.

  • React state değiştikten sonra kod bloğu tamamlandığında DOM'u günceller. State'in değiştiği satırın ardından yeni DOM'a erişmemiz gerekirse

    flushSync
    metodunu kullanabiliriz.

    1import { useState, useRef } from "react";
    2import { flushSync } from "react-dom";
    3
    4export default function TodoList() {
    5  const listRef = useRef(null);
    6  const [todos, setTodos] = useState(initialTodos);
    7
    8  function handleAdd() {
    9    flushSync(() => {
    10      setTodos([...todos, { id: nextId++, text: text }]);
    11    });
    12
    13    listRef.current.lastChild; // can access new todo dom element
    14  }
    15
    16  return (
    17    <>
    18      <button onClick={handleAdd}>Add</button>
    19      <input value={text} onChange={(e) => setText(e.target.value)} />
    20      <ul ref={listRef}>
    21        {todos.map((todo) => (
    22          <li key={todo.id}>{todo.text}</li>
    23        ))}
    24      </ul>
    25    </>
    26  );
    27}

Event Handler

  • Kullanıcı bir olay tetiklediğinde çalışacak fonksiyona event handler denir. Bu fonksiyonları props olarak iletebiliriz. Varsayılan olarak fonksiyona

    Event
    nesnesi geçilir. Ekstra veri göndermek istersek arrow function kullanabiliriz.

    1function MyButton() {
    2  function handleClick() {
    3    alert("You clicked me!");
    4  }
    5
    6  return (
    7    // <button onClick={(e) => handleClick(e, customProp)}>
    8    <button onClick={handleClick}>Click me</button>
    9  );
    10}

Göreceğiniz üzere fonksiyonun referansını iletiyoruz. Eğer

onClick={handleClick()}
gibi fonksiyonu çağırmış olsaydık, event bağımsız her render'da tetiklenirdi.

  • Event props isimlerinin başına

    on
    , handler isimlerinin başına ise
    handle
    konulması yaygın bir kullanımdır. Örn:
    onMouseEnter={handleMouseEnter}

  • Event propagation komponentlerde de geçerlidir. Örneğe bakacak olursak, önce button sonra div

    onClick
    metodu çağrılır.
    onScroll
    harici tüm olaylar yukarıya yayılır. Bunu engellemek için
    e.stopPropagation()
    kullanılır.

    1export default function Toolbar() {
    2  return (
    3    <div
    4      className="Toolbar"
    5      onClick={() => {
    6        alert("You clicked on the toolbar!");
    7      }}
    8    >
    9      <button
    10        onClick={() => {
    11          e.stopPropagation();
    12          alert("Playing!");
    13        }}
    14      >
    15        Play Movie
    16      </button>
    17    </div>
    18  );
    19}
  • Formun

    onSubmit
    olayı gibi bazı olaylar varsayılan tarayıcı davranışına sahiptir ve sizin kodunuzdan sonra çalıştırır. Bunu engellemek için
    e.preventDefault
    kullanabiliriz.

  • Event handler'ları yukarıdan aşağıya doğru (capturing) çalıştırmak istersek olayların capture versiyonlarını kullanabiliriz.

    1<div
    2  onClick={() => {
    3    alert("Call 1");
    4  }}
    5>
    6  <div
    7    onClickCapture={() => {
    8      alert("Call 2");
    9    }}
    10  >
    11    <div
    12      onClickCapture={() => {
    13        alert("Call 3");
    14      }}
    15    >
    16      Click!
    17    </div>
    18  </div>
    19</div>
    Call 2
    Call 3
    Call 1

Props

  • Bileşenden bileşene veri aktarmak için

    props
    kullanırız. Klasik fonksiyona parametre geçme mantığı.

    1function Avatar({ person, size = 100 }) {
    2  // ...
    3}
    4
    5function Profile() {
    6  return (
    7    <div className="card">
    8      <Avatar person="Enes" size={80} />
    9    </div>
    10  );
    11}
  • Children elementleri props'un

    children
    özelliğinden okuyabiliriz.

    1function Heading({ children }) {
    2  return <div className="heading">{children}</div>;
    3}
    4
    5export default function Text() {
    6  return <Heading>Hello</Heading>;
    7}

State

  • Zamanla değişebilen ve ekranın güncellenmesine yol açacak değerler için state değişkenlerini kullanırız. Bi nevi komponent bazlı bellektir. Klasik değişkenlerden farklı olarak render esnasında değeri korunur.

  • State, komponentin DOM'daki yeriyle ilişkilendirilir. Aynı konumda render olan komponentlerde state'ini sıfırlamak istersek

    key
    parametresini güncelleyebiliriz.

  • State tanımlamak için

    useState
    hook'u kullanılır.

    1import { useState } from "react";
    2
    3function MyButton() {
    4  // count: current value
    5  // setCount: setter function
    6  // 0: initial value
    7  const [count, setCount] = useState(0);
    8
    9  function handleClick() {
    10    setCount(count + 1);
    11  }
    12
    13  return <button onClick={handleClick}>Clicked {count} times</button>;
    14}

React'da

use
ile başlayan fonksiyonlara
hook
diyoruz ve esasen React'ın özelliklerini kullanabilmemize yarıyorlar. Komponentlerin root'unda kullanılabilirler. Koşul ya da döngü içine kullanmamalıyız. Gerekiyorsa yeni bir bileşen oluşturabiliriz.

  • After the state setter is called, the rerender is triggered when the code block is executed.

  • React Hook'ların değerlerini çağrılma sırasına göre anlar. Arka arkaya tanımladığımız state değerleri dizide tutulur ve okunacağı zamanda indeksi birer arttırarak okur. Bu sebeptendir ki sadece komponentin root'unda tanımlanmalıdır.

  • State değeri obje ise güncellerken spread operatörünü kullanmalıyız.

    1const [person, setPerson] = useState({
    2  name: "Niki de Saint Phalle",
    3  artwork: {
    4    title: "Blue Nana",
    5    city: "Hamburg",
    6    image: "https://i.imgur.com/Sd1AgUOm.jpg",
    7  },
    8});
    9
    10function handleTitleChange(e) {
    11  setPerson({
    12    ...person,
    13    artwork: {
    14      ...person.artwork,
    15      title: e.target.value,
    16    },
    17  });
    18}
  • Setter metodu çalıştığı anda state değeri yenilenmez. Örneğe bakarsak üç kez çağırılan setNumber metoduyla birlikte son değerin 3 olmasını bekleriz ancak yanılırız. Her setter çalıştığında state değeri render başında okunan değer yani 0 olacaktır. onClick bloğu çalışmayı tamamladıktan sonra rerender tetiklenir ve state değeri güncellenir. Komponent'in bellekteki değerini anlık elde etmek istersek updater fonksiyonu kullanabiliriz.

    1import { useState } from "react";
    2
    3export default function Counter() {
    4  const [count, setCount] = useState(0);
    5
    6  return (
    7    <>
    8      <h1>{count}</h1>
    9      <button
    10        onClick={() => {
    11          setCount(count + 1); // setCount(0 + 1)
    12          setCount(count + 1); // setCount(0 + 1)
    13          setCount(count + 1); // setCount(0 + 1)
    14          // yeni değer sonraki rerender'da 1 olur.
    15        }}
    16      >
    17        +3
    18      </button>
    19      <button
    20        onClick={() => {
    21          setCount((count) => count + 1); // setCount(0 + 1)
    22          setCount((count) => count + 1); // setCount(1 + 1)
    23          setCount((count) => count + 1); // setCount(2 + 1)
    24          // yeni değer sonraki rerender'da 3 olur.
    25        }}
    26      >
    27        +3
    28      </button>
    29    </>
    30  );
    31}

Reducer

  • Karmaşık state'leri yönetmekte zorlanabiliriz. Reducer yapısı, aksiyon bazlı bir state update mekanizması kurar. Reducer tanımlamak için

    useReducer
    hook'unu kullanabiliriz.

    1import { useReducer } from "react";
    2
    3function tasksReducer(tasks, action) {
    4  switch (action.type) {
    5    case "add": {
    6      return [
    7        ...tasks,
    8        {
    9          id: action.id,
    10          text: action.text,
    11          done: false,
    12        },
    13      ];
    14    }
    15    case "change": {
    16      return tasks.map((t) => {
    17        if (t.id === action.task.id) {
    18          return action.task;
    19        } else {
    20          return t;
    21        }
    22      });
    23    }
    24    case "delete": {
    25      return tasks.filter((t) => t.id !== action.id);
    26    }
    27    default: {
    28      throw Error("Unknown action: " + action.type);
    29    }
    30  }
    31}
    32
    33const initialTasks = [
    34  { id: 0, text: "Visit Kafka Museum", done: true },
    35  { id: 1, text: "Watch a puppet show", done: false },
    36  { id: 2, text: "Lennon Wall pic", done: false },
    37];
    38
    39const useTasksReducer = () => useReducer(tasksReducer, initialTasks);
    40
    41export { tasksReducer, useTasksReducer };
    1import AddTask from "./AddTask.js";
    2import TaskList from "./TaskList.js";
    3import { useTasksReducer } from "./tasksReducer.js";
    4
    5export default function TaskApp() {
    6  const [tasks, dispatch] = useTasksReducer();
    7
    8  function handleAddTask(text) {
    9    dispatch({
    10      type: "add",
    11      id: nextId++,
    12      text,
    13    });
    14  }
    15
    16  function handleChangeTask(task) {
    17    dispatch({
    18      type: "change",
    19      task,
    20    });
    21  }
    22
    23  function handleDeleteTask(taskId) {
    24    dispatch({
    25      type: "delete",
    26      id: taskId,
    27    });
    28  }
    29
    30  return {
    31    // ...
    32  };
    33}

Ref

  • Ref, HTML elementlerine erişebilmek için kullanılılır dolayısıyla querySelector ile uğraşmaya gerek kalmaz. Referans tanımlamak için

    useRef
    hook'u kullanabiliriz.

    1import { useRef } from "react";
    2
    3export default function Form() {
    4  const inputRef = useRef(null);
    5
    6  function handleClick() {
    7    inputRef.current.focus();
    8  }
    9
    10  return (
    11    <>
    12      <input ref={inputRef} />
    13      <button onClick={handleClick}>Focus the input</button>
    14    </>
    15  );
    16}
  • DOM rendering esnasında erişilemez olduğu için ref değeri komponent gövdesinde okunamaz ve değiştirilemez. Yalnızca render sonrasında çalışacağından emin olduğumuz

    useEffect
    ve event handler içerisinde erişilebilir.

    1function VideoPlayer({ src, isPlaying }) {
    2  const ref = useRef(null);
    3
    4  // WRONG! this will crash.
    5  if (isPlaying) {
    6    ref.current.play();
    7  } else {
    8    ref.current.pause();
    9  }
    10
    11  // CORRECT! instead we can use useEffect
    12  useEffect(() => {
    13    if (isPlaying) {
    14      ref.current.play();
    15    } else {
    16      ref.current.pause();
    17    }
    18  });
    19
    20  return <video ref={ref} src={src} loop playsInline />;
    21}
  • Renderlar esnasında değeri korunduğu için değişken saklamak için de kullanılabilir. State'den farklı olarak değeri değiştiğinde rerender tetiklemez.

    1import { useRef } from "react";
    2
    3export default function Counter() {
    4  let count = useRef(0);
    5
    6  function handleClick() {
    7    count.current = count.current + 1;
    8    alert("You clicked " + count.current + " times!");
    9  }
    10
    11  return (
    12    <div>
    13      <p>Click count: {count.current}</p>
    14      <button onClick={handleClick}>Click me!</button>
    15    </div>
    16  );
    17}
  • Ref'leri map içerisinde kullanamayacağımızdan ötürü ref callback kullanmalıyız.

    1import { useRef } from "react";
    2
    3export default function CatFriends() {
    4  const itemsRef = useRef(new Map());
    5
    6  return (
    7    <ul>
    8      {catList.map((cat) => (
    9        <li
    10          key={cat.id}
    11          ref={(node) => {
    12            if (node) {
    13              itemsRef.current.set(cat.id, node);
    14            } else {
    15              itemsRef.current.delete(cat.id);
    16            }
    17          }}
    18        >
    19          ...
    20        </li>
    21      ))}
    22    </ul>
    23  );
    24}
  • Bir komponentin içerisindeki HTML elemente de ref atamak isteyebiliriz. Props olarak geçtiğimiz ref otomatik olarak DOM'a eklenmez. Bunun için

    forwardRef
    kullanılmamız gerekir.

    1import { forwardRef, useRef } from "react";
    2
    3const MyInput = forwardRef((props, ref) => {
    4  return <input {...props} ref={ref} />;
    5});
    6
    7export default function Form() {
    8  const inputRef = useRef(null);
    9
    10  return <MyInput ref={inputRef} />;
    11}

Context

  • Komponentler arasında veri paylaşımını kolaylaştıran ve komponentler arası props olarak iletmeyi engelleyen yapıya context denir.

  • Props'ları komponentler arasında 100 kat aşağıya indirmek yerine bir context tanımlayıp

    useContext
    hook'u yardımıyla okuyabiliriz.

    1import React, { createContext, useContext, useState } from "react";
    2
    3// İlk olarak context oluşturulur.
    4const LanguageContext = createContext();
    5
    6// Provider içerisindeki elemanların context'e erişebilmesini sağlar.
    7const LanguageProvider = ({ children }) => {
    8  const [language, setLanguage] = useState("en");
    9
    10  // value props context'in sunacağı değerleri tutar.
    11  return (
    12    <LanguageContext.Provider value={{ language, setLanguage }}>
    13      {children}
    14    </LanguageContext.Provider>
    15  );
    16};
    17
    18// useContext hook'u context'in anlık değerlerini okur.
    19const useLanguageContext = () => useContext(LanguageContext);
    20
    21export { LanguageProvider, useLanguageContext };
    1import { LanguageProvider } from "./LanguageContext.tsx";
    2import { Navbar } from "./Navbar.tsx";
    3import { PageTitle } from "./PageTitle.tsx";
    4
    5export default function App() {
    6  return (
    7    <div className="App">
    8      <LanguageProvider>
    9        <Navbar />
    10        <PageTitle />
    11      </LanguageProvider>
    12    </div>
    13  );
    14}
    1import { useLanguageContext } from "./LanguageContext.tsx";
    2
    3export const PageTitle = () => {
    4  const { language } = useLanguageContext();
    5
    6  const titleByLang = {
    7    en: "Welcome to my App!",
    8    tr: "Uygulamama hoşgeldiniz!",
    9  };
    10
    11  return <h1>{titleByLang[language]}</h1>;
    12};
    1import { useLanguageContext } from "./LanguageContext.tsx";
    2
    3export const Navbar = () => {
    4  const { setLanguageHandler } = useLanguageContext();
    5
    6  return (
    7    <nav>
    8      <button onClick={() => setLanguageHandler("en")}>English</button>
    9      <button onClick={() => setLanguageHandler("tr")}>Türkçe</button>
    10    </nav>
    11  );
    12};

Effect

  • Render'ın meydana getirdiği yan etkileri gerçekleştirmek için kullanılır. Örneğin sayfa yüklendiğinde analitik verisi kaydetmek ya da chat uygulamasında sunucuyla bağlantı kurmak için kullanabiliriz. Bunun için

    useEffect
    hook'u kullanılır.

    1useEffect(() => {
    2  // her render'dan sonra çalışır
    3});
    4
    5useEffect(() => {
    6  // bağımlılık array'indeki değerlerin sebep olduğu renderlarda çalışır (Object.is ile kontrol edilir)
    7}, [someCompVariable, someProps, someState]);
    8
    9useEffect(() => {
    10  // yalnızca ilk renderdan sonra çalışır
    11
    12  return () => {
    13    // komponent dom'dan siliniyorsa çalışır
    14  };
    15}, []);
  • Server-side rendering esnasında çalışmaz.