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

הי @yeshaya

כמה הערות על הקוד - פעם הבאה עדיף פשוט להדביק את הקוד בטקסט לא בתמונה.

בכל מקרה לעניינינו:

  1. נהוג להשתמש ב const לפני משתנים שחוזרים מ useState, ולהשתמש בשם דומה למשתנה ולפונקציה שמעדכנת אותו, כלומר שורה 7 היתה צריכה להיות:
const [color, setColor] = useState(initialColor);
  1. זה לא בטעות ששמרתי רק את הצבע בתוך הסטייט. לא נהוג לשמור אוביקטים או מערכים בסטייט ועדיף לשמור משתנים פשוטים. יש על זה שיעור כאן:
    https://www.tocode.co.il/bundles/react/lessons/state

  2. שורה 14 שלך מיותרת. מספיק להפעיל את פונקציית ה setter אנחנו לא משנים משתנים שמגיעים מסטייט בצורה של השמה (ובגלל זה אני מעדיף להגדיר אותם בתור const)

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

const colors = ['pink', 'blue', 'red', 'green']
  1. בדרך כלל לא משתמשים באלמנטי br יותר. עדיף להשתמש ב CSS בשביל רווחים. יש על זה מספר שיעורים בקורס Front End החל מהשיעור הזה:
    https://www.tocode.co.il/bundles/frontend/lessons/box-model

  2. הכי חשוב - שמתי לב שכתבת input עם מאפיין onChange אבל ללא value. זה יכול ליצור בריאקט הרבה באגים מוזרים ומומלץ אם כותבים value או onChange להקפיד לכתוב את שניהם. כתבתי על זה קצת פה:
    https://www.tocode.co.il/blog/2021-06-react-keypress-solution

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

<input type="number" value={color} onChange={(e) => setColor(e.target.value)}

היי ינון,

אני בפתרון תרגיל 2- ומשום מה השדות לא מתעדכנים בעת אירוע על אחת השדות .

זה הקוד :

 const App = ()=>{
    
    const[hour,setHour] = useState(1);
    const[minute, setMinute] = useState(1);
    const[secound, setSecond] = useState(1);
    
    function handlerHour(e){
        
        const newHour = e.target.value;
        setHour(newHour);
        setMinute(newHour*60);
        setSecond(newHour*360);

    }
    function handlerMinute(e){
        
        const newMinute = e.target.value;
        setMinute(newMinute);
        setHour(newMinute/60);
        setSecond(newMinute*60);

    }
    function handlerSecond(e){
        
        const newsSecond = e.target.value;
        setSecond(newsSecond);
        setMinute(newsSecond/60);
        setHour(newsSecond/60/60);
       

    }





    return (
        <>
        <input name="hours" type='number'  onChange={handlerHour}></input>
        <input name="minutes" type="number"  onChange={handlerMinute}></input>
        <input name="seconds" type="number"  onChange={handlerSecond}></input>
        </>
)

אגב שמתי לב גם שאם אני מגדיר קומפוננט שhfhk את כל ה Inputs ותטפל ב state ואותה להטמיע בקומפוננט הראשי - האירועים לא יפעילו בעת שינוי תוכן כטריגר אשמח להבין מדוע

הי @avraam_b,

יש פה שתי בעיות מרכזיות בקוד:

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

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

היי ינון,

תודה על המענה .

אשמח לשמוע את דעתך על הפתרון שלי- עשיתי שימוש באובייקט תחת state אחד בלבד ועושה רושם שזה עונה לדרישה.

מצרף את הקוד:

import React from 'react';

import ReactDOM from 'react-dom';

import { useState } from 'react';

const App = ()=>{

   

    const[time,setTime] = useState({

        hour:0,

        minute:0,

        second:0  

    });

      

    function handlerHour(e){

       

        const newHour = e.target.value;

        setTime({

            hour:newHour,

            minute:newHour*60,

            second:newHour*3600

        });

       

    }

    function handlerMinute(e){

       

        const newMinute = e.target.value;

        setTime({

            hour:newMinute/60,

            minute:newMinute,

            second:newMinute*60

        });

     

    }

    function handlerSecond(e){

       

        const newsSecond = e.target.value;

        setTime({

            hour:newsSecond/3600,

            minute:newsSecond/60,

            second:newsSecond

        });

    }

       

    return (

        <>

        <input name="hours" type='number' value={time['hour']} onChange={handlerHour}></input>

        <input name="minutes" type="number"  value={time['minute']} onChange={handlerMinute}></input>

        <input name="seconds" type="number"   value={time['second']} onChange={handlerSecond}></input>

        </>

    )

};

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

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

הי @avraam_b

אולי לא הייתי מספיק ברור - אנסה לחדד. אין הבדל בין הפעלת useState שלוש פעמים לבין הפעלה אחת ושמירת אוביקט. אם כבר, הפעלה 3 פעמים של useState היא עדיפה כי היא מאפשרת לשנות רק את אחד הפרמטרים וחוסכת לך סיבוכים של שמירת אוביקט מורכב בסטייט
(על סיבוכים אלה ראה שיעור כאן:
https://www.tocode.co.il/bundles/react/lessons/state)

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

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

היי ינון,

תודה על המשוב.

להלן הפתרון הסופי שלי:

const App = ()=>{

    const [time,setTime] = useState(0);

        

    function timeHandler(e){

        let currentTime = e.target.value;

        if(e.target.matches('[name=hours]')){

           setTime(currentTime);

           return;

           

        }else if(e.target.matches('[name=minutes]')){

           setTime(currentTime/60)

           return;

        }

        setTime(currentTime/3600);

    }

   

    return(

       

        <>

        <h1>Time Convert</h1>

        <label>Hours:

        <input name='hours' type='number' value={time} onChange={timeHandler}></input>

        </label>

        <hr></hr>

        <label>Minutes:

        <input name='minutes' type='number' value={time*60} onChange={timeHandler}></input>

        </label>

        <hr></hr>

        <label>Seconds:

        <input name='seconds' type='number' value={time*3600} onChange={timeHandler}></input>

        </label>

       

        </>

    )

}

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

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

אשמח לקבל עליו משוב

זה נראה הרבה יותר טוב.

עוד שתי מחשבות -

  1. נדמה לי שיותר הגיוני לשמור את השניות בתור ״הערך״ השמור, כי כל פעולות הכפל והחילוק עלולות לאבד דיוק (לדוגמה נסה לכתוב 912 שניות)

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

<input factor={3600} type='number' value={time*3600} onChange={timeHandler}></input>

ואז בפונקציה מחלקים תמיד ב factor.

לייק 1

היי ינון,

אשמח לקבל משוב גם על תרגיל 3:

import React from 'react';

import ReactDOM from 'react-dom';

 import { useState } from 'react';

const App =  ()=>{

    const[randomNum, setRandomNum]= useState(0);

    const [showResults, setShowResults] = useState(true);

    const [message,setMessage] = useState(' ');

    const style = {

        width:'200px',

        padding:'10px',

        borderStyle:'solid',

       

    }

       

    function randomNumber(e){

        setRandomNum(Number(Math.floor(Math.random()*1001)));

        setShowResults(false);

                            

    }

    console.log(randomNum);

    function inputHandler(e){

       

        const guess = Number(e.target.value);

           

        if(guess===randomNum){

            setShowResults(true);

            setMessage(`Correct guess`);

        }else if(guess<randomNum){

            setShowResults(false);

            setMessage(`Too small ,Please try again`);

           

        }

        else {

            setShowResults(false);

            setMessage(`Too big ,Please try again`);}

                  

    }

    return(

        <>

        <h1>Choose Number Between 0 to 1000</h1>

        <button onClick={randomNumber}>Random number</button>

        <p className='show'  >{showResults?randomNum:null}</p>

        <input type='number' onChange={inputHandler}></input>

        <br></br>

        <p style={style}>{message}</p>

        </>

    )

}

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

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

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

הי ינון,

איך הפיתרון שלי לתרגיל 3?

תודה

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

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

const rndNum = Math.floor(Math.random()*1000);

const App = () => {
  const [num, setNum] = useState(1);
  const [state, setState] = useState("?");

  function chagneNum(e){
    if(Number(e.target.value) > rndNum)
      setState("to high");
    else if(Number(e.target.value) < rndNum)
      setState("to low");
    else 
      setState("correct");

    setNum(Number(e.target.value));
  }

  return (
    <div>
      <h3>The random number is: {rndNum}</h3><br />
      <text>Guess the number: </text>
      <input type="number" value={num} onChange={chagneNum} />
      <h3>The number {num} is {state}</h3>
    </div>
  )
};

נראה טוב

עכשיו נסה שאלת המשך - מה אם אני רוצה להוסיף כפתור ״בדיקה״ ולהראות את התוצאה רק כשלוחצים על הכפתור? (ואז כשמשנים את הערך שוב התוצאה נעלמת מהמסך)

היי, אשמח אם תוכל לתת רעיונות איך להפוך את הקוד של תרגיל 5 ליותר גנרי:
אור

export default function ColorPicker(props){
    function pickedColor(e){
        setColor(e.target.value);
    }
    
    function ActualColor(tabNumber){
        //hashHexColor --> decNoHexColor
        const noHasColor = color.slice(1);
        const decColor =  parseInt(noHasColor,16);
        //-->rgb divider
        var r = Math.floor(decColor / (256*256));
        var g = Math.floor(decColor/ 256) % 256;
        var b = decColor % 256;
        //get new color
        const middleTab=2;
        const numberForChangeColor=(tabNumber-middleTab)*80;
        r=(r-numberForChangeColor>255 ? 255: r-numberForChangeColor);
        b=(b-numberForChangeColor>255 ? 255: b-numberForChangeColor);
        g=(g-numberForChangeColor>255 ? 255: g-numberForChangeColor);
        return ( `rgb(${(r<0 ? 0 : r)},${(g<0 ? 0 : g)},${((b<0 ? 0 : b))})`);
    }
    const [color, setColor] = useState('#ff0000');
    return(
        <>
        <p>{ActualColor(4)}</p>
        <div>
        <div style={{display:'inline-block' , background:ActualColor(0) , width:'50px', height:'30px'}}></div>
        <div style={{display:'inline-block' , background:ActualColor(1) , width:'50px' , height:'30px'}}></div>
        <div style={{display:'inline-block' , background:ActualColor(2) , width:'50px' , height:'30px'}}></div>
        <div style={{display:'inline-block' , background:ActualColor(3) , width:'50px' , height:'30px'}}></div>
        <div style={{display:'inline-block' , background:ActualColor(4) , width:'50px' , height:'30px'}}></div>
        </div>
        <p>pick your color: <input type="color" onChange={pickedColor} value={color}/></p>
        </>
    );
}

היי
בתרגיל הראשון אפשר לדעת מה השגיאה

import { useState } from "react"

export default function App(){
    const [name,setName]=useState("hello")
   function change(e){   
    setName={e.target.value}
   }
  
return(
  
    <div>
        <div><input type="text" value={name} onChange={change}></input></div>
        <div><input type="text" value={name} onChange={change}></input></div>
        <div><input type="text" value={name} onChange={change}></input></div>
        <div><input type="text" value={name} onChange={change}></input></div>
        <div><input type="text" value={name} onChange={change}></input></div>

       
    </div>
)
}

הי,

השורה הזאת:

היא לא הדרך להפעיל את setName. צריך לקרוא לה בתור פונקציה:

setName(e.target.value)

היי ינון
פתרון לתרגיל 3, אם יש הערות:

import React from “react”;
import { useState } from “react”;

export default function RandomNum() {

const [val, setVal] = useState('');
let saveNum = "";

function click(){
    const myRndNum = Math.floor(Math.random() * 1000) + 1;
    saveNum = myRndNum
    
    if(val == myRndNum){
        alert("equal");
    }else{
        alert("Sorry, not Equal\n The Random Number is: " + myRndNum);
    }
}

function change(event){
    setVal(event.target.value);
}

return(
    <>
    <div>
        Enter Number between 1-1000 <input type="text" value={val} onChange={change}></input> <br /> 
        <button onClick={click}>Check</button>
    </div>
    <br />
    </>
)

}

היי ינון

תרגיל 4
אחת הבעיות היו לי זה שעשיתי כמו בסרטון את האובייקט של ה-style, זה עבד עד שניסיתי לשנות אותו ב-onClick של הכפתור. זה לא עבד. אז עשיתי אותו כ-className כדי לשנות אותו ב-style ולתת לו state משלו (BgColor).
אני לא יודע אם הדרך הזו נכונה, אשמח להערות

import React from "react";
import { useState } from "react";

export default function PickColor(){

    const [val, setVal] = useState('');
    const [BgColor, setBgColor] = useState();

    function SetColor(){
        const myColor = "#" + val;
        setBgColor(myColor);
    }

    function change(event){
        setVal(event.target.value);
    }

    return (
        <>
        <hr />
        <h1>Pick Color</h1>

        <input type="text" maxLength="6" value={val} onChange={change}></input>&nbsp;
        <button onClick={SetColor}>Set Color</button>
        <br /><br />

        <div className="colorBox" style={{background: BgColor}}>
            
        </div>

        <hr />
        </>
    )
}

הי

לא הבנתי כרגע זה מופיע עם אוביקט style וזה נראה נכון. מה בדיוק לא עבד?

היי
התכוונתי שהיתה לי בעיה, ופתרתי אותה בעזרת state נוסף

היי ינון
תרגיל 5

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

אשמח לקבל הערות

import React from "react";
import { useState } from "react";

export default function ChooseColor(){
    const [ValMiddle, setValMiddle] = useState();

    let myArray = [];

    function change(event){
        
        const choosenColor = document.getElementById("myColor").value;
        
        for(let i=0 ; i<8 ; i++){
            myArray.push( '#' + (Math.random().toString(16) + "000000").substring(2,8) );
            document.getElementById(i).style.background = myArray[i];    
        }
        
        setValMiddle(choosenColor);
    }

    return(
        <>
        <hr />
        <h1>Choose Color</h1>

        <label>
            Select Color:<br />
            <input type="color" id="myColor" onChange={change}></input>
        </label>
        <br />
        <br />
        <table border="1" cellSpacing="0" cellPadding="0" className="myTable">
            <tr>
                <td id="0"></td>
                <td id="1"></td>
                <td id="2"></td>
                <td id="3"></td>
                <td style={{background: ValMiddle}}></td>
                <td id="4"></td>
                <td id="5"></td>
                <td id="6"></td>
                <td id="7"></td>
            </tr>
        </table>

        <hr />
        </>
    )
}

בקובץ App.css שמתי את זה:
.myTable tr td {
width: 30px;
height: 30px;
}