[英]React UseContext change not re-rendering component
I am trying to make a simple 'Nonogram'/'Picross' game using React to learn UseContext and UseReducer, but am puzzled as to why my top component (App) is not re-rendering when a value it uses changes.我正在尝试使用 React 制作一个简单的“Nonogram”/“Picross”游戏来学习 UseContext 和 UseReducer,但我对为什么我的顶级组件(应用程序)在它使用的值发生变化时没有重新渲染感到困惑。 Perhaps I am missing something basic, but I've read through documentation and examples online and can't see why it is not re-rendering.
也许我遗漏了一些基本的东西,但我已经在线阅读了文档和示例,但看不出它为什么不重新渲染。
Expectation: User goes on the application, clicks on the squares to change their value (draw a cross by clicking on the squares), and the text underneath the board reads "Congratulations,", as it is based on the value of 'isComplete'预期:用户打开应用程序,单击方块以更改它们的值(通过单击方块画一个十字),并且板下方的文本显示“Congratulations”,因为它基于“isComplete”的值
Problem: As above, but 'Keep trying' remains.问题:如上所述,但“继续尝试”仍然存在。
I added a button to see the boardState as defined in the UseReducer function, too.我添加了一个按钮来查看在 UseReducer function 中定义的 boardState。
Code is as follows:代码如下:
App.js应用程序.js
import './App.css';
import { useReducer } from 'react';
import Table from './Table';
import BoardContext from './BoardContext';
import boardReducer from './BoardReducer';
function App() {
//Puzzle layout
const puzzleArray = [
[true, false, true],
[false, true, false],
[true, false, true]
];
//Creating a set of blank arrays to start the game as the userSelection
const generateUserSelection = () => {
const userSelection = [];
puzzleArray.forEach(row => {
let blankRow = [];
row.forEach(square => {
blankRow.push(false)
});
userSelection.push(blankRow);
})
return userSelection;
};
//Initial Context value
const boardInfo = {
puzzleName: "My Puzzle",
puzzleArray: puzzleArray,
userSelection: generateUserSelection(),
isComplete: false
};
const [ boardState, dispatch ] = useReducer(boardReducer, boardInfo)
return (
<BoardContext.Provider value={{board: boardState, dispatch}}>
<div className="App">
<header className="App-header">
<p>
Picross
</p>
<Table />
</header>
<div>
{boardState.isComplete ?
<div>Congratulations!</div>
: <div>Keep trying</div>
}
</div>
<button onClick={() => console.log(boardState)}>boardState</button>
</div>
</BoardContext.Provider>
);
}
export default App;
Table.jsx:表.jsx:
import { useContext, useEffect } from 'react';
import './App.css';
import Square from './Square';
import BoardContext from './BoardContext';
function Table() {
useEffect(() => {console.log('table useEffect')})
const { board } = useContext(BoardContext);
const generateTable = solution => {
const squareLayout = []
for (let i = 0; i < solution.length; i++) {
const squares = []
for (let j = 0; j < solution[i].length; j++) {
squares.push(
<Square
position={{row: i, column: j}}
/>
);
};
squareLayout.push(
<div className="table-row">
{squares}
</div>
);
};
return squareLayout;
};
return (
<div className="grid-container">
{generateTable(board.puzzleArray)}
</div>
);
}
export default Table;
Square.jsx Square.jsx
import { useContext, useState } from 'react';
import './App.css';
import BoardContext from './BoardContext';
function Square(props) {
const { board, dispatch } = useContext(BoardContext)
const [ isSelected, setIsSelected ] = useState(false);
const { position } = props;
const handleToggle = () => {
console.log(board)
board.userSelection[position.row][position.column] = !board.userSelection[position.row][position.column]
dispatch(board);
setIsSelected(!isSelected);
}
return (
<div className={`square ${isSelected ? " selected" : ""}`}
onClick={handleToggle}
>
{position.row}, {position.column}
</div>
);
}
export default Square;
Thanks谢谢
Edit: I know for a simple application like this it would be very easy to pass down state through props, but the idea is to practice other hooks, so wanting to avoid it.编辑:我知道对于像这样的简单应用程序,通过道具传递 state 非常容易,但想法是练习其他钩子,所以想避免它。 The ideas I am practicing in this would ideally be extensible to bigger projects in the future.
我在这方面实践的想法理想情况下可以扩展到未来更大的项目。
Edit 2: As requested, here's my BoardReducer.js file:编辑 2:根据要求,这是我的 BoardReducer.js 文件:
const boardReducer = (state, updateInfo) => {
let isComplete = false;
if (JSON.stringify(updateInfo.userSelection) === JSON.stringify(state.puzzleArray)) {
isComplete = true;
}
updateInfo.isComplete = isComplete;
return updateInfo;
}
export default boardReducer;
(using JSON.stringify as a cheap way to check matching arrays as it's only a small one for now!) (使用 JSON.stringify 作为检查匹配 arrays 的廉价方法,因为它现在只是一个小方法!)
You are mutating your state object in a couple places:你在几个地方改变你的 state object :
const handleToggle = () => {
console.log(board);
board.userSelection[position.row][position.column] = !board.userSelection[position.row][position.column]; // <-- mutation!
dispatch(board);
setIsSelected(!isSelected);
}
And in reducer在减速机中
const boardReducer = (state, updateInfo) => {
let isComplete = false;
if (JSON.stringify(updateInfo.userSelection) === JSON.stringify(state.puzzleArray)) {
isComplete = true;
}
updateInfo.isComplete = isComplete; // <-- mutation!
return updateInfo; // <-- returning mutated state object
}
Since no new state object is created React doesn't see a state change and doesn't rerender your UI.由于没有创建新的 state object,因此 React 没有看到 state 更改并且不会重新呈现您的 UI。
useReducer will typically employ a "redux" pattern where the reducer function consumes the current state
and an action
to operate on that state, and returns a new state object. useReducer will typically employ a "redux" pattern where the reducer function consumes the current
state
and an action
to operate on that state, and returns a new state object.
You should dispatch an action that toggles the user selection and checks for a complete board.您应该发送一个操作来切换用户选择并检查是否有完整的板。
Board Reducer板式减速机
When updating state you should shallow copy any state objects that you are updating into new object references, starting with the entire state
object. When updating state you should shallow copy any state objects that you are updating into new object references, starting with the entire
state
object.
const boardReducer = (state, action) => {
if (action.type === "TOGGLE") {
const { position } = action;
const nextState = {
...state,
userSelection: state.userSelection.map((rowEl, row) =>
row === position.row
? rowEl.map((colEl, col) =>
col === position.column ? !colEl : colEl
)
: rowEl
)
};
nextState.isComplete =
JSON.stringify(nextState.userSelection) ===
JSON.stringify(state.puzzleArray);
return nextState;
}
return state;
};
Create an action creator, which is really just a function that returns an action object.创建一个动作创建器,它实际上只是一个返回动作 object 的 function。
const togglePosition = position => ({
type: "TOGGLE",
position
});
Then the handleToggle
should consume/pass the row and column position in a dispatched action.然后,
handleToggle
应该在调度的操作中消耗/传递行和列 position。
const handleToggle = () => dispatch(togglePosition(position));
Demo Code:演示代码:
const puzzleArray = [
[true, false, true],
[false, true, false],
[true, false, true]
];
const userSelection = Array(3).fill(Array(3).fill(false));
const togglePosition = (row, column) => ({
type: "TOGGLE",
position: { row, column }
});
const boardReducer = (state, action) => {
if (action.type === "TOGGLE") {
const { position } = action;
const nextState = {
...state,
userSelection: state.userSelection.map((rowEl, row) =>
row === position.row
? rowEl.map((colEl, col) =>
col === position.column ? !colEl : colEl
)
: rowEl
)
};
nextState.isComplete =
JSON.stringify(nextState.userSelection) ===
JSON.stringify(state.puzzleArray);
return nextState;
}
return state;
};
export default function App() {
const [boardState, dispatch] = React.useReducer(boardReducer, {
puzzleArray,
userSelection,
isComplete: false
});
const handleClick = (row, column) => () =>
dispatch(togglePosition(row, column));
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>{boardState.isComplete ? "Congratulations!" : "Keep Trying"}</div>
<div>
{boardState.userSelection.map((row, r) => (
<div key={r}>
{row.map((col, c) => (
<span
key={c}
className={classnames("square", { active: col })}
onClick={handleClick(r, c)}
/>
))}
</div>
))}
</div>
</div>
);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.