简体   繁体   中英

React state for array is not appended using spread operator

I'm creating Sudoku app using React.js.

I've faced an issue of values being written to state sudoku only after first inner loop of createSudokValues() (which creates only the first row of sudoku). The outer loop is supposed to generate the rest of the rows (by setSudoku(prevSquares => [...prevSquares, ...row]); ).

What am I missing? Thanks!

import React, { useEffect, useState } from 'react';
import '../App.css';
import { SquareType } from '../interfaces/customInterfaces';
import Square from './Square';

function App() {
    let row: Array<SquareType> = [];
    const [sudoku, setSudoku] = useState<SquareType[]>([]);
    const possibleOptionsForDigit = [1, 2, 3, 4, 5, 6, 7, 8, 9];

    function generateRandomArrayIndex(unusedDigits: Array<number> ) {
        return Math.floor(Math.random() * unusedDigits.length);
    }

    function unusedDigitInRowAndColumn( sudoku: Array<SquareType>, row: Array<SquareType>, columnIndex: number ) {
        let digitsExistingInRow: Array<number> = [];
        let digitsExistingInColumn: Array<number> = [];
        let unusedDigitsInRow: Array<number> = [];
        let unusedDigitsInColumn: Array<number> = [];
        let unusedDigits: Array<number>;
        let randomDigitFromUnused: number;
        
        digitsExistingInRow = row.map(square => square.digit);
        unusedDigitsInRow = possibleOptionsForDigit.filter(digit => !digitsExistingInRow.includes(digit));
    
        digitsExistingInColumn = sudoku.filter(square => square.index === columnIndex).map(square => square.digit);
        unusedDigitsInColumn = possibleOptionsForDigit.filter(digit => !digitsExistingInColumn.includes(digit));
    
        unusedDigits = unusedDigitsInRow.filter(digit => unusedDigitsInColumn.includes(digit)); 
        randomDigitFromUnused = unusedDigits[generateRandomArrayIndex(unusedDigits)];
    
        return randomDigitFromUnused;
    }

    function createSudokValues() {
        let generatedUnusedDigit: number = 0;

        for ( let y = 1; y <= 9; y++ ) {
            for ( let columnIndex = 1; columnIndex <= 9; columnIndex++ ) {
                while (row.length <= 9) {
                    console.log('row ', row)
                    generatedUnusedDigit = unusedDigitInRowAndColumn(sudoku, row, columnIndex);
                    row.push(
                        {
                            digit: generatedUnusedDigit,
                            index: columnIndex,
                            shown: true
                        }
                    );
                    break;
                }
                
            }
            // end of inner loop

            // eslint-disable-next-line no-loop-func
            setSudoku(prevSquares => [ ...prevSquares, ...row]);
            row = [];
            // end of outer loop
        }
    }

    useEffect(() => {
        console.log('sudoku ', sudoku)
    });

    useEffect(() => {
        createSudokValues();
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
        <div className="App">
            <div className="pageContainer">
                <p>
                    Sudoku
                </p>
                <div className="sudokuContainer">
                    {
                        sudoku.map((square, idx) =>
                        <Square key={idx} {...square}></Square>
                        )
                    }
                </div>
            </div>
        </div>
    );
}

export default App;

This is the first row printed to the screen:

数独

Which is equivalent to the first row console.logged:

数独第一行

控制台记录数独

instead of using a variable inside the function you should initialize the row array every time the first loop runs.

So this function:

function createSudokValues() {
    let generatedUnusedDigit: number = 0;

    for ( let y = 1; y <= 9; y++ ) {
        for ( let columnIndex = 1; columnIndex <= 9; columnIndex++ ) {
            while (row.length <= 9) {
                console.log('row ', row)
                generatedUnusedDigit = unusedDigitInRowAndColumn(sudoku, row, columnIndex);
                row.push(
                    {
                        digit: generatedUnusedDigit,
                        index: columnIndex,
                        shown: true
                    }
                );
                break;
            }
            
        }
        // end of inner loop

        // eslint-disable-next-line no-loop-func
        setSudoku(prevSquares => [ ...prevSquares, ...row]);
        row = [];
        // end of outer loop
    }
}

should be:

function createSudokValues() {
    let generatedUnusedDigit = 0;

    let row: Array<SquareType> = [];
    let tempSudoku: Array<SquareType> = [];
    for (let y = 1; y <= 9; y++) {
      for (let columnIndex = 1; columnIndex <= 9; columnIndex++) {
        while (row.length <= 9) {
          console.log("row ", row);
          generatedUnusedDigit = unusedDigitInRowAndColumn(
            sudoku,
            row,
            columnIndex
          );
          row.push({
            digit: generatedUnusedDigit,
            index: columnIndex,
            shown: true
          });
          break;
        }
        tempSudoku.push(...row);
        row = [];
      }
    }
    setSudoku(tempSudoku);
  }

It makes little sense to setState inside the loop because it would be bad for performance reasons, since state variable would trigger useless renders. And also you should remove the let row: Array<SquareType> = []; initialization at the beginning of the App component.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM