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

תודה רבה,
אך כשאני משנה לכתיב שכתבת למעלה מתקבלת השגיאה: Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
אני לא מצליחה להבין את הבעיה

אה כן אני רואה. הבעיה בגדול שאת מפעילה את הפונקציה updateScore במקום להעביר אותה פנימה לתוך הקומפוננטה Sq. כלומר הבעיה בשורות:

            <Sq func = {updateScore(0)} isRed = {square[0]}/>

            <Sq func = {updateScore(1)} isRed = {square[1]}/>

            <Sq func = {updateScore(2)} isRed = {square[2]}/>

            <Sq func = {updateScore(3)} isRed = {square[3]}/>

            <Sq func = {updateScore(4)} isRed = {square[4]}/>

בשביל להבין את ההבדל בין העברה להפעלה אני ממליץ לצפות בשיעור הזה:
https://www.tocode.co.il/bundles/frontend/lessons/js-functions

ואז לענות על התרגיל השני כאן:
https://www.tocode.co.il/bundles/frontend/lessons/functions-lab

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

הפיתרון לתרגיל השני:

<script>

        var b1 = document.querySelector('#btn1');

        var b2 = document.querySelector('#btn2');

        var panel = document.querySelector('p');

        function writeText(text) {

            panel.textContent=text;

        }

        b1.addEventListener('click', ()=>writeText('Yo'));

        b2.addEventListener('click', ()=>writeText('Nice To Meet You'));

    </script>

הפיתרון לפונקציה updateScore:

            <Sq func = {()=>updateScore(0)} isRed = {square[0]}/>

            <Sq func = {()=>updateScore(1)} isRed = {square[1]}/>

            <Sq func = {()=>updateScore(2)} isRed = {square[2]}/>

            <Sq func = {()=>updateScore(3)} isRed = {square[3]}/>

            <Sq func = {()=>updateScore(4)} isRed = {square[4]}/>

זה עובד!
תודה רבה

לייק 1

זה הקוד המלא
אשמח אם תוכל לתת לי טיפים לייעול הקוד

    export default function Sq(props){

        const {isRed,func} = props;

        const backgroundColor = isRed ? 'red' : 'gray';

        let style={

            width:"100px",

            height:"100px",

            backgroundColor: backgroundColor,

            margin:"10px",

            display:"inline-block"

        }

        return(

            <>

             <div style = {style} onClick = {func}></div>       

            </>

        )

    }





        export default function Boxes(props){

            const {square,func} = props;

            return(

                <>

                {

                    square.map((element,index) => <Sq isRed = {element} func={()=>(func(index))}></Sq>)

                }

                </>

            )

        }




    export default function Score(props){

        const {newGame,score} = props;

        return(

            <>

                <div>

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

                    <p>{score}</p>

                </div>

            </>

        )

    }






    const App = () => {

      const [square,setSquare] = useState([false,false,false,false,false,false,false,false,false,true])

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

      function newGame(inx){

          setInterval(

              function(){

                  let newArr = [false,false,false,false,false,false,false,false,false,false];

                  newArr[Math.floor(Math.random() * 5)] = true;

                  setSquare(newArr);

              },1000

          )

          setScore(0);

      }

      function updateScore(inx2) {

          const diff = square[inx2] ? 10 : -5;

          setScore(currentScore => currentScore + diff);

        }

      return (

        <div>

          <Boxes square = {square} func = {updateScore}/>

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

        </div>

      )

    }

הי
הבעיה המרכזית כאן שנשארה זה המערך ב State. אין בו באמת צורך; מספיק לשמור את האינדקס של המקום היחיד שהוא true

היי פיתרון שלי לתרגיל 1, אשמח להערות. חילקתי ל3 קבצים

שמתי לב שאמרת לרשום קוד ולא תמונות רק עכשיו

מצרף את הקוד שלי לפתרון תרגיל 2 , אשמח להערות



import React, { useState } from 'react';

import ReactDOM from 'react-dom';

import List from './list.js';

import Input from './input.js';

const App = () => {

  const [arrUesrInput, setArrUserInput] = useState("");

  const arrColors = ['red', 'blue','purple','yellow','pink'];

  let updateList = (word) => {setArrUserInput(word);};

  return (

    <>

    <Input updateList={updateList}/>

    <List arrUesrInput={arrUesrInput}

          arrColors={arrColors}/>

    </>

  )

};

// main.js

const root = document.querySelector('main');

ReactDOM.render(<App />, root);


================================================

import React, { useState } from 'react';

export default function Input(props) {

    const {updateList} = props;

    return (

       <div>

            <input placeholder='choose color' onChange={(e) => {updateList(e.target.value)}}></input>

       </div>

      )

}


=============================================


import React, { useState } from 'react';

export default function List(props) {

    const { arrUesrInput, arrColors } = props;

    /* Inputs of the user*/

  //  const arrUserInputs = list.split(' ');

    return (

        <>

            <div style={{ display: 'flex' }}> please choose from the following colors:

                {arrColors.map((color, index) =>

                    (<div key={index} style={{ marginLeft: '10px', color: 'blue' }}>{color}</div>))} </div>

            <ul>

                {

                    arrColors.map(function foo(color, index) {

                        if ((arrUesrInput.split(' ')).includes(color)) {

                            return (<li key={index} value={color}>{color}</li>);

                        }

                    })

                }

            </ul>

        </>

    )

}

הי,

לגבי תרגיל 1 - נראה טוב. יש לי כמה דברים מוזרים עם random. קודם כל:

const random = r => Math.floor(Math.random() * 10)

אבל הפונקציה לא משתמשת ב r בכלל. היה עדיף לכתוב את זה בלעדיו:

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

דבר שני זה בתוך הקוד השורה:

const [box, setBox] = useState(random);

שאני חושב שהתכוונת לכתוב עם סוגריים של הפעלה אחרי random כלומר:

const [box, setBox] = useState(random());

לגבי הקוד עצמו שמתי לב שאתה מעביר את הפונקציות changeBoxColor ו updateScore מקומפוננטה App לקומפוננטה Box, אבל הרבה פעמים קורא לשתי הפונקציות האלה יחד אחת אחרי השניה. נדמה לי שעדיף לחשוב על מבנה פונקציות שלא תצטרך לקרוא להן יחד - לדוגמה פונקציית winRound ו loseRound, כאשר אחת גם תעלה נקודות וגם תשנה צבע והשניה רק תוריד נקודות.


וראיתי גם הרבה מאפייני style בקוד. שווה להסתכל על הספריות emotion.js:

ו Styled Components:
https://www.tocode.co.il/past_workshops/52

שנותנות דרך טובה יותר לכתוב קוד סטייל בתוך קוד JS. או אולי הכי פשוט להשתמש ב CSS.

הי, איך הקוד של תרגיל 2?

import React from 'react';
import ReactDOM from 'react-dom';
import { useState } from 'react';

import '../css/main.css';

const List = (props) => {
  const { filter } = props;
  const listOfItem = ['michael', 'ron', 'yoni', 'yarin','ido',
                     'aviad', 'ynon', 'moshe', 'david', 'yaron'];
  return (
    <>
      <ul>
      {listOfItem.map((item, index) => (
        item.includes(filter) && <li>{item}</li>
      ))}
      </ul>
    </>
  )
}

const SrarchBox = (props) => {
  const { newSearch } = props;

  return(
    <>
      <input type="text" onChange={newSearch} />
    </>
  )
}

const App = () => {
  const [filter, setFilter] = useState([]);

  const newSearch = (e) => setFilter(e.target.value);

  return (
    <>
      <SrarchBox newSearch={newSearch}/>
      <List filter={filter} />
    </>
  )
};

// main.js
const root = document.querySelector('main');
ReactDOM.render(<App />, root);

שים לב שכשאתה כותב שדה קלט עם onChange:

      <input type="text" onChange={newSearch} />

תמיד להוסיף לו גם value, אחרת עלולים להיות באגים מוזרים שהטקסט בתיבה לא מתואם עם המשתנה

הי, בתרגיל משחק ניחוש מספר, רציתי להסתיר את הכפתור של הגרלת המספר לאחר הלחיצה, ולהראות את השדה לניחוש המספרים לשם כך כתבתי קוד כזה:
image

function hideShow() {
    if (givenNumber !== undefined) {
        givenNumberContainer.classList.add("hide");
        comparedNumberContainer.classList.remove("hide");
    } 
   else {
    comparedNumberContainer.classList.add("hide");
    givenNumberContainer.classList.remove("hide");
    } 
}
hideShow()

כשאני מרעננת את העמוד אני מקבלת הודעת שגיאה:
Uncaught TypeError: Cannot read properties of null (reading ‘classList’)

איך אני משנה את המצב הזה?

תודה!

הי,

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

function Game() {
  const [givenNumber, setGivenNumber] = useState(undefined);

  if (givenNumber) {
    return <div>hide button</div>
  else {
    return <div>show button</div>
  }
}

שלום ינון,

בתרגיל מספר 3:

אני צריך לשמור בסטייט 4 פרטים: userName, password, country, city.
אפשר ליצור סטייט לכל אחד בנפרד.
אבל אני רוצה לשמור הכל בתוך אובייקט משהו בסגנון הזה:
const [userInformation, setUserInformation] = useState([{userName: ‘’, password: ‘’, country: ‘’, city: ‘’}]);

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

עוד שאלה:

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

const arr = [[0, "user name:", "fux972"], [1, 'password:', 'SH@fux10']];

{arr.map( el => (
                <div key={el}>
                    <label>
                        choose your {el[1]}
                        <input type="text" placeholder={el[2]} onChange={ (e) => changeuserNameOrPassword(e.target.value, el[0])}/>
                    </label>
                </div>
            ))}

האם זה הדרך הנכונה?

הי

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

אם בכל זאת אתה מחליט לשמור אוביקט אחד צריך לשים לב לנושא Mutability ולהעביר אוביקט חדש כל פעם לפונקציית ה setter. אני מדבר על זה בשיעור כאן:
https://www.tocode.co.il/bundles/react/lessons/state?tab=video

לשאלה השניה אני הייתי מעדיף לראות סטייט וקוד יותר פשוטים, כלומר בסטייט לשמור שני משתני מחרוזת רגילים (שם משתמש וסיסמה) וב JSX לכתוב שתי תיבות input אחת אחרי השניה ולא בלולאה. הסיבה:

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

  2. כן אפשר לדמיין מערכת גנרית יותר של טפסים שמקבלת ״פרטים של טופס״ ויוצרת קומפוננטות מתאימות ל input-ים. למשל ספריה כמו:
    useForm
    שווה להסתכל עליה ולראות אם אתה מצליח לשלב/לעבוד איתה בתור בונוס.

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

המשך ערב טוב!

אני אשמח לדעתך על הקוד שכתבתי לתרגיל 3

function Page1(props) {
    const { setUserName, setPassword } = props;
    return (
        <div>
            <p>הזן שם משתמש וסיסמא</p>
            <input type="text" onChange={(e) => setUserName(e.target.value)} /><span>שם משתמש</span>
            <br />
            <input type="password" onChange={(e) => setPassword(e.target.value)} /><span>סיסמא</span>
        </div>
    );
}
function Page2(props) {
    const { setCity, setCountry } = props;
    return (
        <div>
            <p>הזן אזור מגורים</p>
            <input type="text" onChange={(e) => setCountry(e.target.value)} /><span>ארץ </span>
            <br />
            <input type="text" onChange={(e) => setCity(e.target.value)} /><span>עיר</span>
        </div>
    );
}
function Page3(props) {
    const { userName, password, country, city } = props;
    return (
        <div>
            <p>הפרטים שהזנת:</p>
            <p>שם משתמש {userName}</p>
            <p>סיסמא {password}</p>
            <p>ארץ {country}</p>
            <p>עיר {city}</p>
        </div>
    )
}

export default function FormPages() {
    const [counter, setCouter] = useState(0);
    const [userName, setUserName] = useState("");
    const [password, setPassword] = useState("");
    const [city, setCity] = useState("")
    const [country, setCountry] = useState("");

    function next() {
        setCouter(x => x + 1);
    }
    function prev() {
        setCouter(x => x - 1);
    }

    switch (counter) {
        case 0:
            return (
                <>
                    <Page1 setPassword={setPassword} setUserName={setUserName} />
                    <button type="button" onClick={next}>הבא</button>
                </>);
            break;
        case 1:
            return (
                <>
                    <Page2 setCountry={setCountry} setCity={setCity} />
                    <button type="button" onClick={next}>הבא</button>
                    <button type="button" onClick={prev}>הקודם</button>
                </>)
            break;
        case 2:
            return (
                <>
                    <Page3 userName={userName} password={password} country={country} city={city} />
                    <button type="button" onClick={prev}>הקודם</button>
                </>);
            break;
    }
}```

הי,

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

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

const pages = [Page1, Page2, Page3]

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

מצרפת קוד של תרגיל 3.
אשמח מאד לדעת האם יש דרך יותר יעילה ונכונה לפתור אותו
Details:

import React, { useState } from "react";
import User from "./User";
import Country from "./Country";
import Summary from "./Summary";

const Details = () => {
    const [userName, setUserName] = useState("");
    const [password, setPassWord] = useState("");
    const [country, setCountry] = useState("");
    const [city, setCity] = useState("");
    const [page, setPage] = useState(1)

    return (
        <>
            {page == 1 ? <User setPage={setPage} setUserName={setUserName} setPassWord={setPassWord}></User>
                : page == 2 ? <Country setPage={setPage} setCountry = {setCountry} setCity = {setCity}></Country>
                    : <Summary setPage={setPage} user = {userName} pass = {password} cou = {country} city = {city}></Summary>}
        </>
    )
}

export default Details

User:

import React, { useState } from "react";

const User = (props) => {
    const {setPassWord, setUserName, setPage} = props
    return (
        <>
            <input onChange={e => setUserName(e.target.value)} type="text" placeholder="Type user name"></input>
            <input onChange={e => setPassWord(e.target.value)} type="text" placeholder="Type Password"></input>
            <button disabled>Previous</button>
            <button onClick={e => setPage(2)}>Next</button>
        </>
    )
}

export default User

Country:

import React, { useState } from "react";

const Country = (props) => {
    const {setPage, setCity, setCountry} = props
    return (
        <>
            <input onChange={e => setCountry(e.target.value)} type="text" placeholder="Type your country"></input>
            <input onChange={e => setCity(e.target.value)} type="text" placeholder="Type your city"></input>
            <button onClick={e => setPage(1)}>Previous</button>
            <button onClick={e => setPage(3)}>Next</button>
        </>
    )
}

export default Country

Summary:

import React, { useState } from "react";

const Summary = (props) => {
    const {user, pass, cou, city, setPage} = props
    return (
        <>
        <p>Your user name is {user}</p>
        <p>Your password is {pass}</p>
        <p>You live in {city}, {cou}</p>
        <button onClick={e => setPage(2)}>Previous</button>
        <button disabled>Next</button>
        </>
    )
}

export default Summary

הי נראה מעולה דבר אחד שהייתי כותב אחרת זה בקובץ הראשון אני לא אוהב להשתמש בסימן שאלה ונקודותיים במקרים כאלה כי קצת קשה לקרוא את זה, ומעדיף כתיב של switch כלומר:

import React, { useState } from "react";
import User from "./User";
import Country from "./Country";
import Summary from "./Summary";

const Details = () => {
    const [userName, setUserName] = useState("");
    const [password, setPassWord] = useState("");
    const [country, setCountry] = useState("");
    const [city, setCity] = useState("");
    const [page, setPage] = useState(1);

    const renderPage = () => {
        switch (page) {
            case 1:
                return <User setPage={setPage} setUserName={setUserName} setPassWord={setPassWord} />;
            case 2:
                return <Country setPage={setPage} setCountry={setCountry} setCity={setCity} />;
            case 3:
                return <Summary setPage={setPage} user={userName} pass={password} cou={country} city={city} />;
            default:
                return null;
        }
    };

    return <>{renderPage()}</>;
};

export default Details;

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

import React, { useState } from "react";
import User from "./User";
import Country from "./Country";
import Summary from "./Summary";

const Details = () => {
    const [userName, setUserName] = useState("");
    const [password, setPassWord] = useState("");
    const [country, setCountry] = useState("");
    const [city, setCity] = useState("");
    const [page, setPage] = useState(1);

    // Collect all props into a single object
    const sharedProps = {
        setPage,
        userName,
        setUserName,
        password,
        setPassWord,
        country,
        setCountry,
        city,
        setCity,
    };

    const renderPage = () => {
        switch (page) {
            case 1:
                return <User {...sharedProps} />;
            case 2:
                return <Country {...sharedProps} />;
            case 3:
                return <Summary {...sharedProps} />;
            default:
                return null;
        }
    };

    return <>{renderPage()}</>;
};

export default Details;