קורס React 2020 שיעור תרגול: שילוב פקדים בעמוד


זהו נושא דיון מלווה לערך המקורי שב־https://www.tocode.co.il/bundles/react/lessons/16-multipage-lab

יש מצב לכיוון לגבי השאלה ה 3
איך אני יוצר 3 דפים ולא גורם לזה להיות באותו הדף?

הי,

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

שאלה:
אם יש לי תיבת טקסט אבל אני רוצה שהמידע שארשום בה יישמר במשתנה או כאיבר במערך/שדה באובייקט רק כשאלחץ על כפתור (כלומר, אני לא רוצה לשנות את הערך השמור ולהציג אותו מחוץ לתיבת הטקסט בכל הקלדה, אלא רק כשלוחצים על הכפתור) עדיין הvalue שלה חייב להיות בstate? הרי אפשר להסתפק בכך שיהיה render רק מתי שלוחצים על הכפתור ולא בכל הקלדה…

הי,

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

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

היי יינון,

בתרגיל 2 כלל לא הרגשתי צורך להפריד את פקד בחירת הצבעים מפקד ה input text ולעטוף אותם בפקד חיצוני.
במקום זאת הפקד של הצבעים אצלי מנהל את ה-state של עצמו ומרנדר למסך גם תגית input text לסינון פריטים מרשימת הצבעים.

בקיצור נמרץ, כאשר המשתמש מזין אל תיבת ה-input text קורה אירוע בפקד רשימת הצבעים שמוביל לשינוי ה-state של רשימת הצבעים, וכך הרשימה מתעדכנת.

הפספסתי משהו? האם מדובר בתכנות שגוי או פחות מומלץ?

אזכיר שבתרגיל עצמו הנחית להשתמש בפקד רשימת צבעים, פקד ל-input ופקד חיצוני שיקשר ביניהם…

הי @dekeltsairi

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

היי ינון.

אני לא ראיתי שהתייחסת בשיעוריך עד לתרגילים הנ"ל לאופן המומלץ לעבור בין עמודים בריאקט
כשחיפשתי מה האופן המומלץ לנהל מספר דפים בריאקט הפנו אותי לספרייית react-router.
אך זה השיעור האחרון בקורס שלך ואני מניח שלא תכננת שכך נעבור בין עמודים.
האם בשלב הזה האופן הנכון בשבילינו לעבור בין עמודים היא על ידי תגית link רגילה?
אשמח להבהרה קטנה

הי דקל,

אני מניח שאתה מתכוון כאן לתרגיל השלישי, אבל התשובה נכונה באופן כללי -

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

דרך אחרת היא לקחת את מה ש react-router עושה אבל לכתוב את זה לבד ובקטן. כלומר להחזיק קומפוננטה אחת שמחליטה איזה קומפוננטה תוצג (לפי משתנה State כלשהו) ואז ב render שלה להסתכל על המשתנה ולהציג קומפוננטה פנימית אחרת. ״מעבר בין דפים״ כאן הוא פשוט שינוי של משתנה ה State, שיוביל ל Render חדש עם הקומפוננטה הפנימית החדשה. כל עוד אתה לא משנה את ה URL עם ה History API של הדפדפן אז משתמש לא יוכל לעשות Bookmark או לרענן כל אחד מהדפים בנפרד, אבל בשביל התרגיל הזה זה לא נורא בכלל.

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

תודה

הי @edenz

ברור בשמחה - בואי נתחיל עם השאלה ״מה ה State של הדבר הזה שאת בונה?״

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

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

אני מקווה שזה היה ברור, ואם לא יכולה גם להדביק כאן את הקוד שניסית לכתוב ונמשיך איתו

היי ינון,
בשאלה 2 יש לי צורך לשמור list בתוך state.
אני מגדירה אותו כך:

const [itemList,setItemList] = useState(new Set(["miriam","yair"]));

אך ברגע שאני מנסה להשתמש בו (להעביר בתור prop לפקד אחר) מתקבלת שגיאה “List is not defined”
יש אפשרות לעזור לי בעניין?

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

אבל אני ממליץ להמשיך לחשוב על זה ולפתור לבד לפני שאת מסתכלת בפיתרון שלי

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

main.js

import React from 'react';
import ReactDOM from 'react-dom';

import FormsManager from './formsManager';
import LoginForm from './loginForm';
import CitiAndCountryForm from './cityAndCountryForm';
import SummaryForm from './summaryForm';

import '../css/style.css';

const App = () => {
    const forms = [LoginForm , CitiAndCountryForm ,  SummaryForm];
    return (
        <FormsManager forms = {forms}/>
    );
}


ReactDOM.render(<App/> , document.querySelector('main'));

formsManager.js

import React, { useState } from 'react';

export default function FormsManager({forms}) {
    const [currentIndexForm , setCurrentIndexForm] = useState(0);
    const [dataObjectOfAllPages , setDataObjectOfAllPages] = useState({});

    const CurrentForm = forms[currentIndexForm];
    const nextFormExist = forms[currentIndexForm + 1] ? true : false;
    const previousFormExist = forms[currentIndexForm - 1] ? true : false;

    function renderNextForm() {
        setCurrentIndexForm( i => i + 1);
    }

    function renderPreviousForm() {
        setCurrentIndexForm( i => i - 1);
    }

    function saveMyDataObj(dataObj) {
        setDataObjectOfAllPages({...dataObjectOfAllPages , ...dataObj});
    }

    return(
        <>
            <h1>Forms - Exercise 3</h1>
            <div className = "containerForFormAndButtons">
                <CurrentForm saveMyDataObj = {saveMyDataObj} dataObjectOfAllPages = {dataObjectOfAllPages}/>
                <div className="renderBtnsContainer">
                    { previousFormExist && <button onClick = {renderPreviousForm}>Previous</button> }
                    { nextFormExist && <button onClick={renderNextForm}>Next</button> }
                </div>
            </div>
        </>
    );
}

loginForm.js

import React from 'react';

export default function LoginForm({saveMyDataObj , dataObjectOfAllPages}) {
    
    const userName = dataObjectOfAllPages["userName"] || '';
    const password = dataObjectOfAllPages["password"] || '';
    const myDataObj = {};

    function onInputHandler(e) {
        e.target.id === "userName" && (myDataObj["userName"] = e.target.value);
        e.target.id === "password" && (myDataObj["password"] = e.target.value);
        saveMyDataObj(myDataObj);
    }

    return(
        <form>
            <h1>LoginForm</h1>
            <div className="form-outline mb-4">
                <input type="text" id="userName" className="form-control" value = {userName} onInput = {onInputHandler} />
                <label className="form-label" htmlFor="userName">User name</label>
            </div>

            <div className="form-outline mb-4">
                <input type="password" id="password" className="form-control" value={password} onInput = {onInputHandler}/>
                <label className="form-label" htmlFor="password">Password</label>
            </div>
        </form>
    );
}

cityAndCountryForm.js

import React from 'react';

export default function CitiAndCountryForm({saveMyDataObj , dataObjectOfAllPages}) {

    const country = dataObjectOfAllPages["country"] || '';
    const city = dataObjectOfAllPages["city"] || '';
    const myDataObj = {};

    function onInputHandler(e) {
        e.target.id === "country" && (myDataObj["country"] = e.target.value);
        e.target.id === "city" && (myDataObj["city"] = e.target.value);
        saveMyDataObj(myDataObj);
    }

    return(
        <>
            <form>
                <h1>CitiAndCountryForm</h1>
                <div className="form-outline mb-4">
                    <input type="text" id="country" className="form-control" value={country} onInput = {onInputHandler}/>
                    <label className="form-label" htmlFor="country">Country</label>
                </div>

                <div className="form-outline mb-4">
                    <input type="text" id="city" className="form-control" value={city} onInput = {onInputHandler}/>
                    <label className="form-label" htmlFor="city">City</label>
                </div>
            </form>
        </>
    );
}

summaryForm.js

import React from 'react';

export default function SummaryForm({dataObjectOfAllPages}) {

    const { userName , password , country ,city } = dataObjectOfAllPages;

    const styleL = { display: "block" , fontSize: "2rem",};
    const center = { textAlign: "center",};
    const margin = { margin: "2rem",};
    
    return(
        <>
            <form>
                <h1>SummaryForm</h1>
                <h2 style={{...center , ...margin}}>Hi {userName}</h2>
                <label style={{...center , ...styleL}} className="form-label">Password: {password}</label>
                <label style={{...center , ...styleL}} className="form-label">Country: {country}</label>
                <label style={{...center , ...styleL}} className="form-label">City: {city}</label>
            </form>
        </>
    );
}

פשוט מעולה - קוד גנרי, מסודר, נו כיף לקרוא

טיפ קטן לשיפור - ב JavaScript אפשר לכתוב כך:

myDataObj[e.target.id] = e.target.value;

במקום תוכן הפונקציה onInputHandler שכתבת

לייק 1

לגבי setState כאשר מוגדרות לי פונקציות ומשתנים.
האם בכל render מחדש נוצרות פונקציות חדשות, והפונקציות הישנות נשארות בזכרון?
זה לא בזבוז זכרון?

שלום,
כך כתבתי את תרגיל 1 האם יש דרך לייעל אותו? תודה

import React, { useState } from 'react';

const ShowScore = (props) => {

    const { score, newGame } = props;

    return (

        <>

        <p>Your score : {score}</p>

        <button onClick={newGame}>New game</button>

        </>

    )

}

const GameZone = (props) => {

    const { colorsArray, squareClicked } = props;

    return (

        <div>

            {colorsArray.map((color, idx) => (

                <div 

                style={{width:'10px', height:'10px',backgroundColor:color}}

                onClick={() => squareClicked(color)}

                ></div>

            ))}

        </div>

    )

}

export default function Targil1(){

    const [ score, setScore ] = useState(0);

    const [colorsArray, setColorsArray] = useState(['gray','gray','gray','gray','red','gray','gray','gray','gray','gray']);

    function squareClicked(color){

        if (color === 'red'){

            setScore(s => s + 10);

            changeRedPlace();

        } else score >= 5 ? setScore(s => s-5) : setScore(0);

    }

    function newGame(){

        setScore(0);

        changeRedPlace();

    }

    function changeRedPlace(){

        const num = Math.floor(Math.random()*10);

        const neArr = ['gray','gray','gray','gray','gray','gray','gray','gray','gray','gray'];

        neArr[num] = 'red';

        setColorsArray(neArr);

    }

    return (

        <>

            <GameZone colorsArray={colorsArray} squareClicked={squareClicked}/>

            <ShowScore score={score} newGame={newGame}/>

        </>

    )

}
לייק 1

הי לא בדיוק הבנתי את השאלה אפשר להוסיף דוגמת קוד ולהסביר מה בדיוק נוצר מחדש?

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

כן גם אני חשבתי על זה אבל הייתי חייבת איזשהו מערך לעבור עליו כדי שאוכל להשתמש בmap כי לא ראיתי שלימדת לופ אחר או שאפשר להשתמש בmap בלי מערך…
איך אפשר ליצור את הריבועים דינאמית בלי map?