צעדים ראשונים עם jotai

19/11/2023

ניהול סטייט בריאקט הוא אחת הבעיות העתיקות עם הספריה ובעיה שכנראה תישאר איתנו עוד הרבה זמן. רידאקס הוא עדיין פיתרון מאוד פופולארי, אבל הוא לא פשוט ואפילו עם redux toolkit לוקח זמן להתרגל אליו.

ג'וטאי היא ספריית ניהול סטייט מינימליסטית לריאקט שמנסה לתת משהו דומה לאטומים של סוליד בעולם של ריאקט. בואו נראה איך זה עובד.

1. האתגר - שיתוף סטייט בין קומפוננטות

כשאני כותב "ניהול סטייט" זה נשמע מאיים אבל המשמעות היא מאוד פשוטה - אני רוצה לקחת משהו שקומפוננטה מסוימת יודעת ולהשתמש בו בקומפוננטה אחרת. לפני ריאקט זאת לא היתה בעיה כי כל המידע היה גלובאלי. בעבודה בריאקט כל הזמן צריך לחשוב איזה קומפוננטה שומרת איזה מידע, ואיך היא תעביר אותו לקומפוננטות אחרות.

בדוגמה היום נבנה אפליקציה שמציגה מספר "מונים" על המסך, כשכל מונה כולל כפתור להעלות את המספר באחד, להוריד באחד או למחוק את המונה. אנחנו נראה איך כל מונה יכול לגשת למערך המונים הכללי בלי שצריך להעביר אותו במפורש בתור Property.

2. פיתרון - ג'וטאי

ג'וטאי מאפשרת להגדיר מידע בתור atom ואז לגשת אליו מכל קומפוננטה. שימו לב לתוכנית הדוגמה הראשונה שלהם:

import { atom, useAtom } from 'jotai';

const counter = atom(0);

export default function Page() {
  const [count, setCounter] = useAtom(counter);
  const onClick = () => setCounter(prev => prev + 1);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={onClick}>Click</button>
    </div>
  )
}

המשתנה counter מוגדר מחוץ לקומפוננטה, ובכל זאת הקומפוננטה יכולה לגשת אליו, לקרוא את ערכו ולשנות אותו.

אם יהיו מספר קומפוננטות כולן יוכלו לגשת לאותו אטום, וכך אנחנו משתפים מידע בין קומפוננטות. המידע שמור באטום וכל קומפוננטה שצריכה מפעילה useAtom על אותו אטום.

אם רוצים לקחת את זה עוד צעד קדימה אפשר ליצור מערך של מונים, ולאפשר לקומפוננטות להוסיף או להוריד מונים. הקוד שמגדיר את האטומים הוא:

import { atom } from "jotai";

export const countersAtom = atom([]);
export const maxValueAtom = atom((get) =>
  get(countersAtom).reduce((acc, val) => (acc > get(val) ? acc : get(val)), 0)
);

וזה נחמד כי אפשר להגדיר אטום מתוך אטום אחר, בדוגמה שלנו maxValueAtom מוגדר מתוך אטום המונים. קוד הקומפוננטות הוא:

import "./styles.css";
import { useAtom, atom } from "jotai";
import { countersAtom, maxValueAtom } from "./state";

function Counter({ myAtom }) {
  const [counter, setCounter] = useAtom(myAtom);
  const [_, setCounters] = useAtom(countersAtom);

  return (
    <div className="counter">
      <button onClick={() => setCounter((c) => c + 1)}>+1</button>
      <button onClick={() => setCounter((c) => c - 1)}>-1</button>
      <span>{counter}</span>
      <button
        onClick={() =>
          setCounters((c) => {
            return c.filter((cc) => cc !== myAtom);
          })
        }
      >
        Delete
      </button>
    </div>
  );
}

export default function App() {
  const [counters, setCounters] = useAtom(countersAtom);
  const [maxValue, _] = useAtom(maxValueAtom);
  const createCounter = () => {
    setCounters((counters) => [...counters, atom(0)]);
  };

  return (
    <div className="App">
      <h1>Hello Jotai</h1>
      <p>Max value = {maxValue}</p>
      <button onClick={createCounter}>Create Counter</button>
      {counters.map((counter, idx) => (
        <Counter myAtom={counter} />
      ))}
    </div>
  );
}

או לייב בקודסנדבוקס כאן: https://codesandbox.io/s/elegant-hoover-6yfywd?file=/src/App.js

3. מה עוד חסר

למרות ההתחלה הקלה ג'וטאי לא מרגיש כמו פיתרון מספיק טוב לתוכניות גדולות. בעיקר היתה חסרה לי דרך לגשת לערכים שבאטומים מחוץ לקומפוננטות ריאקט. אז נכון אפשר ליצור אטומים מתוך אטומים, אבל לוגיקה מורכבת של עבודה חייבת להיכתב בתוך הקומפוננטות.

כן שווה לשחק איתו אם אתם מחפשים משהו קטן שדומה לקונטקסט אבל מגיע עם ממשק קצת יותר קל לעבודה.