השוואת פתרונות פקדים מורכבים בקורס react

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

  1. אני מקבל את ההערה הזו ואני לא מבין למה(בגלל השורה המודגשת):
    "Warning: setState(…): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
  2. בלחיצה הראשונה זה לא עובד, רק מהלחיצה השנייה
    console.log('itemClicked itPosition ’ + this.state.itPosition); מדפיס לי
    "itemClicked itPosition undefined"
    אבל בקליקים הבאים זה מסתדר
  3. אני רוצה לעשות מחלקה של אסטרטגיה, היא לא מרנדרת שום דבר על המסך פשוט מבצעת לוגיקה. האם אפשר לעשות את זה בריאקט? מכיוון שאני לא רוצה שתהיה לי פונקציית RENDER, אלא רק מחלקה שמבצעת בדיקת תוצאה, עירבול הסדר, איפוס המערך וכו’
function Square(props)
{ 
    return <li className={props.color=="It" ? "red" : "gray"} onClick={() => props.click(props.id)}>{props.color}</li>;
}

class App extends React.Component
{ 
  
  constructor(props)
  {
    super(props);
        
    this.state = {
      points:0,
      items: this.createDefaultList()
    };
    
    this.resetGame();
  }
  
  createDefaultList = () =>{
    return [{color:"NotIt"},{color:"NotIt"},{color:"NotIt"},{color:"NotIt"}];
  }
  
  getArrayRandomIndex = (array) =>
  {
    return Math.floor(Math.random() * array.length) + 0;
  }
  
  calcPoints = (pressedPosition, ItPositiion) => {
    if (ItPositiion == pressedPosition)
    {
      this.setState((prevState) => ({points:prevState.points + 10}));
    }
    else
    {
      this.setState((prevState) => ({points:prevState.points - 5}));
    }
  }
  
  resetGame = () =>
  {    
    this.state.items.map((item) => {item.color = "NotIt"}); 
    
    const itNewPosotion = this.getArrayRandomIndex(this.state.items);
    
    **this.setState((prevstate) => ({itPosition : itNewPosotion}));**

    this.state.items[itNewPosotion].color = "It";
  }
  
  itemClicked = (e) => {
    console.log('itemClicked itPosition ' + this.state.itPosition);
    
    this.calcPoints(e, this.state.itPosition);
        
    this.resetGame();
  }
  
  render()
  {
    
    const squers = this.state.items.map((square,index) => <Square key={index} id={index} color={square.color} click={this.itemClicked}/>)
                                        
    return <div>
      <ul className="noStyle">
           {squers}
      </ul>
      <div>Score: {this.state.points}</div>
          </div>;
  }
}

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

הי,

הודעת השגיאה ברורה: היא אומרת שאתה מפעיל setState לפני שהקומפוננטה הופיעה על המסך. בקוד שלך אסור לקרוא ל setState מתוך render או מתוך ה constructor.

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

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

השגיאה פחות הייתה ברורה לי, תודה!
לא כל כך הבנתי את הערתך, "לגבי מחלקה חיצונית - זה מעולה באמת אין צורך שם בריאקט (קוד הפקד מריאקט ישתמש במחלקה זו)"
לא עדיף לעשות מחלקה חיצונית שמנהלת את המשחק? ואם כן, איך עושים את זה?
`
הקוד המתוקן:

function Square(props)
{ 
    return <li className={props.color=="It" ? "red" : "gray"} onClick={() => props.click(props.id)}>{props.color}</li>;
}

class App extends React.Component
{ 
  
  constructor(props)
  {
    super(props);
    
    const defaultList = this.createDefaultList();
    
    const itNewPosotion = this.getArrayRandomIndex(defaultList);
    
    this.state = {
      points:0,
      items: defaultList,
      itPosition : itNewPosotion,
    };
    
    this.state.items[itNewPosotion].color = "It";
  }
  
  createDefaultList = () =>{
    return [{color:"NotIt"},{color:"NotIt"},{color:"NotIt"},{color:"NotIt"}];
  }
  
  getArrayRandomIndex = (array) =>
  {
    return Math.floor(Math.random() * array.length) + 0;
  }
  
  calcPoints = (pressedPosition, ItPositiion) => {
    if (ItPositiion == pressedPosition)
    {
      this.setState((prevState) => ({points:prevState.points + 10}));
    }
    else
    {
      this.setState((prevState) => ({points:prevState.points - 5}));
    }
  }
  
  initArray = () => 
  {
    this.state.items.map((item) => {item.color = "NotIt"}); 
  }
  
  resetGame = () =>
  {    
    this.initArray();
    
    const itNewPosotion = this.getArrayRandomIndex(this.state.items);
    
    this.setState((prevstate) => ({itPosition : itNewPosotion}));

    this.state.items[itNewPosotion].color = "It";
  }
  
  itemClicked = (e) => {
    this.calcPoints(e, this.state.itPosition);
        
    this.resetGame();
  }
  
  render()
  {
    
    const squers = this.state.items.map((square,index) => <Square key={index} id={index} color={square.color} click={this.itemClicked}/>)
                                        
    return <div>
      <ul className="noStyle">
           {squers}
      </ul>
      <div>Score: {this.state.points}</div>
          </div>;
  }
}

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

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

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

class RedSpotterGame {
  // fill code here for example
  getScore() {
  }

  getSquareColors() {
  }

  userClickedOnSquare(index) {
  }
}

ואז בריאקט בתוך ה constructor יוצר אוביקט כזה ומשתמש בו במהלך הקוד:

class RedSpotter extends React.Component {
  constructor(props) {
    super(props);
    this.game = new RedSpotterGame();
  }

  render() {
    return (
        <h1>Red Spotter Game</h1>
        <p>Score: {this.game.getScore()}</p>
    );
  }
}


הפתרון המתוקן עם המחלקת הניהול:
`

function Square(props)
{ 
    return <li className={props.color=="It" ? "red" : "gray"} onClick={() => props.click(props.id)}>{props.color}</li>;
}

class RedSpotterGame{
  
  constructor() {
    this.score = 0;
  }
  
  createDefaultList() {
    return [{color:"NotIt"},{color:"NotIt"},{color:"NotIt"},{color:"NotIt"}];
  }
  
  getArrayRandomIndex()
  {
    return Math.floor(Math.random() * this.list.length) + 0;
  }
  
  getNewList()
  {
    this.list = this.createDefaultList();
    this.itIndex = this.getArrayRandomIndex();
    this.list[this.itIndex].color="It";

    return this.list;
  }
  
  squareClicked(index)
  {   
    if (index == this.itIndex)
      {       
        this.score = this.score +10;
      }
    else{
      this.score = this.score - 5;
    }
  }
  
  getScore() {
    return this.score;
  }
}

class App extends React.Component
{ 
  
  constructor(props)
  {
    super(props);

    this.game = new RedSpotterGame();

    this.state = {
      items: this.game.getNewList(),
    };
  }
   
  itemClicked = (e) => {
    this.game.squareClicked(e);
    this.setState({items: this.game.getNewList()});
  }
  
  render()
  {
    const squers = this.state.items.map((square,index) => <Square key={index} id={index} color={square.color} click={this.itemClicked}/>)
                                        
    return <div>
      <ul className="noStyle">
           {squers}
      </ul>
      <div>Score: {this.game.getScore()}</div>
          </div>;
  }
}

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

לייק 1

פתרון ביניים תרגילים 2+3 - התרגיל היה דיי ארוך ולא הקפדתי על וולידציות למיניהן. כמו כן על התמונה.

function ErrorArea(props)
{
  return <div>{props.error != undefined ? <h3>{props.error}</h3> : <h3></h3>}</div>;
}

class InputItems extends React.Component
{
  constructor(props)
  {
    super(props);
      
    this.state={error:''};
  }
   
  validate = (name,quantity) =>
  {
    if (name == undefined || name == '')
      {
        this.setState({error:'Name Not Good!!!'});
        return false;
      }
    else if (quantity == undefined || quantity == '')
      {
        this.setState({error:'Quantity is empty!!!'});
        return false;
      }
    return true;
  }
  
  tryAddItem = (name,quantity,contFunc) => {
      
      const res = this.validate(name,quantity);
      if (res == false) return;
      
      this.setState({error:''});
      contFunc(name,quantity);
    }
    
  clear = () => {
    this.inputName.value = '';
    this.inputQuantity.value = '';
  }
  
  render(){    
    return <div>
        <div>
          <span>
            <label>Name: </label>
            <input ref={(inputName) => {this.inputName = inputName}}/>
          </span>
        </div>
        <div>
          <span>
            <label>Quantity: </label>
            <input ref={(inputQuantity) => (this.inputQuantity = inputQuantity)} />
          </span>
        </div>
      <button onClick={() => this.tryAddItem(this.inputName.value,this.inputQuantity.value,this.props.itemAdded)}>Add</button>
      <ErrorArea error={this.state.error}/>
    </div>;
  }
}
///////////////////////////////////////////////
class ListItem extends React.Component
{
  constructor(props)
  {
    super(props);
    
    this.state={quantity:props.data.quantity}; 
  }
  
  buy = () =>
  {   
    this.setState((prevState) => 
                  {return {quantity : String(
                    parseInt(prevState.quantity) - 
                    parseInt(this.inputField.value))}});
  }
  
  render()
  {    
  return <li>
      <span>Name: {this.props.data.name}, 
            Quantity: {this.state.quantity}, 
            Used:</span>
      <input 
        ref={(inputField) => {this.inputField = inputField}}/>
      <button onClick={this.buy}>Add</button>
      <button onClick={() => this.props.deleteItem(this.props.id)}>Delete</button>
    </li>;
  }
}

class ListItems extends React.Component
{
  constructor(props)
  {
    super(props);
    this.state = {items:[],
                  filteredItems:[],
                 filteredText:''};
  }
  
  addItem = (name,quantity) => {
    
    const copyArr = this.state.items.slice();
    copyArr.push({name:name,
                 id:name,
                 quantity:quantity});
    
    this.setState({items:copyArr});
  }
  
  deleteItem = (id) => 
  {       
    
    const newArray = this.state.items.filter((item) => {
      return item.id != id;
    });
    
    this.setState({items : newArray});
  }
  
  render()
  {    
    
    const filteredItems = this.state.items.filter((item) =>
    {
      return item.name.includes(this.state.filteredText);
    });
    
    const ListItems = filteredItems.map(
      (item)=><ListItem 
                key={item.name} 
                data={item} 
                deleteItem={this.deleteItem} 
                id={item.id} />);
    return <div>
        <div>Search:<input 
                      onChange={()=> {this.setState({filteredText:this.inputFilter.value})}} 
                      ref={(inputFilter) => {this.inputFilter = inputFilter}}></input></div>
        <ul>{ListItems}</ul>
      </div>;
  }
}

class App extends React.Component
{    
  itemAdded = (name,quantity) =>
  {           
    this.listItems.addItem(name,quantity);
  }
  
  render()
  {          
    return <div>
      <InputItems itemAdded={this.itemAdded} 
        ref={(inputItems) => (this.inputItems = inputItems)}/>
      <ListItems ref={(listItems) => (this.listItems = listItems)}/>
    </div>;
  }
}

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

הי,

המבנה טוב. כמה דברים קטנים:

  1. השאלה אם משהו הוא undefined או ריק קצת מעיקה. הייתי מוציא אותה לפונקציה (נקרא לה blank), ובכלל התוצאה של value אף פעם לא תהיה undefined אז לדעתי כל הבדיקה מיותרת

  2. באופן כללי ב JavaScript עדיף להשתמש בסימן === (שלושה סימני שווה) להשוואה. אפשר לקרוא על זה קצת כאן
    https://codeburst.io/javascript-double-equals-vs-triple-equals-61d4ce5a121a

  3. המבנה שכתבת ב tryAddItem נקרא Decorator. אתה לוקח פונקציה מבחוץ (contFunc) ומוסיף עליה התנהגות (את הוולידציה). יש תמיכה מובנית בשפה בסגנון הזה ושווה להכיר אותו:
    https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841

  4. בהפעלת setState עם פונקציה (מה שיש לך ב buy למשל) לא חייבים return, אבל זה מבלבל כי צריך שם סוגריים עגולים במבנה מסוים. זו דוגמא שעובדת:

this.setState(oldState => ({ count: oldState.count + 1 }))
  1. במחלקה ListItem אתה משתמש בפונקציית חץ בתוך render - כפרמטר ל onClick של הכפתור. זה בסדר למשל בלולאה אם הפרמטר שמעבירים כל פעם משתנה. במקרה שלך הפרמטר קבוע (this.props.id) ולכן עדיף להגדיר את הפונקציה פעם אחת בגוף המחלקה, כמו שעשית עם buy.

קבלתי את הערותייך.
תודה!