简体   繁体   English

如何通过 onClick function 在该上下文 onClick function 中创建在该上下文 onClick

[英]How do I pass a React Context's state through an onClick function created in a method inside that context class?

I have some code that makes an array of div elements to place inside a CSS grid, which is rendered via React since that's extremely fast after the first render.我有一些代码可以将一组 div 元素放置在 CSS 网格中,该网格通过 React 渲染,因为在第一次渲染后速度非常快。 I want to be able to interact with those elements via multiple components, so I set up some context that stores the state of the grid.我希望能够通过多个组件与这些元素进行交互,因此我设置了一些上下文来存储网格的 state。 I'm running into a problem now where I want to set an onClick function on those grid elements so that when they are clicked on, that updates the context's state.我现在遇到了一个问题,我想在这些网格元素上设置 onClick function 以便在单击它们时更新上下文的 Z9ED39E2EA931586B6A985A6942EF573E。 In this case I'm trying to keep track of the selected elements by an array.在这种情况下,我试图通过数组跟踪选定的元素。 However, I can't seem to reference selectedTiles in the onClick function.但是,我似乎无法在 onClick function 中引用 selectedTiles。 It either won't compile, crashes on runtime, or doesn't do anything.它要么无法编译,要么在运行时崩溃,要么什么都不做。

To be clear, if I remove the code lines relating to selectedTiles, it works and toggles the class just fine.需要明确的是,如果我删除与 selectedTiles 相关的代码行,它可以正常工作并切换 class 就好了。 I am also binding the function already in the constructor.我还在构造函数中绑定了 function 。

Here's the code I have right now, in the three relevant files.这是我现在在三个相关文件中的代码。

index.tsx索引.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import UserInterface from './components/userinterface';
import DisplayGrid from './components/displaygrid';
import {GridContextProvider} from './gridcontext';

ReactDOM.render(
  <React.StrictMode>
    <GridContextProvider>
      <UserInterface />
      <div className="grid-wrapper">
        <DisplayGrid />
      </div>
    </GridContextProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

serviceWorker.unregister();

displaygrid.js displaygrid.js

import React from 'react';
import {GridContext} from '../gridcontext'

class DisplayGrid extends React.Component{
    static contextType = GridContext;
    render(){
        const gridData = this.context;
        return (
            <div>
                {gridData.name}<br />
                <div className= "grid-container" style={{gridTemplateColumns: gridData.columns}}>
                    {gridData.tiles}
                </div>
            </div>
        );
    }
}
export default DisplayGrid;

gridcontext.js gridcontext.js

import React from "react";

const GridContext = React.createContext();

class GridContextProvider extends React.Component {
    constructor(props) {
        super(props);
        const topGridData = JSON.parse(localStorage.getItem('grids'))[0];
        this.state = this.makeGrid(topGridData.dims[0], topGridData.dims[1], topGridData.dims[2],
            topGridData.visible, topGridData.hex);
        this.selectTile = this.selectTile.bind(this);
        this.makeGrid = this.makeGrid.bind(this);
    }
    selectTile(event) {
        let tilesArray = this.state.selectedTiles;
        if(event.currentTarget.className === "grid-tile"){
            event.currentTarget.className = "grid-tileb";
            tilesArray.push(event.currentTarget.key);
        } else {
            event.currentTarget.className = "grid-tile";
            tilesArray.splice(tilesArray.indexOf(event.currentTarget.key),tilesArray.indexOf(event.currentTarget.key));
        }
    }
    makeGrid(x, y, tilesize, visible, hex){
        let columnStr = "";
        let tileArray = [];
        const widthStr = tilesize.toString() + "px"
        for (let i = 0; i < y; i++) {
            for (let j = 0; j < x; j++) {
                if(i===0) columnStr = columnStr + "auto ";//x loops over columns so this runs once for all columns.
                let div = (
                    <div 
                        key={"x" + j.toString() + "y" + i.toString()}//for example at coordinates 5,6 id is x5y6.  starts at 0.
                        className="grid-tile"
                        style={{
                            width: widthStr,
                            height: widthStr,
                            border: "1px solid rgba(0, 0, 0," + (visible ? "0.6)" : "0.0)")
                        }}
                        onClick={this.selectTile}
                    >
                    </div>
                )
                tileArray.push(div);
            }
        }
        const gridsDataArray = JSON.parse(localStorage.getItem('grids'));
        const index = JSON.parse(localStorage.getItem('currentGrid'));
        return {
            columns: columnStr,
            tiles: tileArray,
            selectedTiles: [],
            name: gridsDataArray[index].name,
            bgurl: gridsDataArray[index].bgurl
        };
    }
    render() {
        return (
            <GridContext.Provider value={{
                columns: this.state.columns,
                tiles: this.state.tiles,
                selectedTiles: this.state.selectedTiles,
                name: this.state.name,
                bgurl: this.state.bgurl,
                setNameBgurl: (newName, newBgurl) => {
                    this.setState({name : newName,
                    bgurl : newBgurl});
                },
                setGrid: (x, y, tilesize, visible, hex) => {
                    this.setState(this.makeGrid(x, y, tilesize, visible, hex));
                }
            }}>
                {this.props.children}
            </GridContext.Provider>
        );
    }
}

export { GridContext, GridContextProvider };

This code gives me a "TypeError: this is undefined" crash on runtime when I click on a tile, pointing at line 15 of gridcontext, which is let tilesArray = this.state.selectedTiles;当我点击一个磁贴,指向 gridcontext 的第 15 行时,这段代码在运行时给我一个“TypeError:这是未定义的”崩溃,即let tilesArray = this.state.selectedTiles; which is strange since the method is bound.这很奇怪,因为该方法是绑定的。

Other feedback would be welcome since I'm new to React and not much better with javascript overall;欢迎其他反馈,因为我是 React 新手,总体而言 javascript 也好不到哪里去; I don't think I've quite wrapped my head around how javascript uses "this".我不认为我已经完全了解 javascript 如何使用“this”。 I've been mostly trying to look up tutorials but it seems there are a lot of different ways to use React so it's hard to tell what practices are standard or not.我一直在尝试查找教程,但似乎有很多不同的方法可以使用 React,所以很难判断哪些做法是标准的。

selectTile(key, className) {
        let tilesArray = this.state.selectedTiles;
        if(className && className === "grid-tile"){
            // do something 
            // if u want to change element's className. maybe u need ref
        } else {
            // do something
        }
}
    makeGrid(x, y, tilesize, visible, hex){
        ...
        for (let i = 0; i < y; i++) {
            for (let j = 0; j < x; j++) {
                if(i===0) columnStr = columnStr + "auto ";//x loops over columns so this runs once for all columns.
                // u may assume 
                const key = "x" + j.toString() + "y" + i.toString()    
                // const className = "grid-tile"
                // just pass whatever u need. that depends on u
                let div = (
                    <div 
                        key={key}//for example at coordinates 5,6 id is x5y6.  starts at 0.
                        className="grid-tile"
                        style={{
                            width: widthStr,
                            height: widthStr,
                            border: "1px solid rgba(0, 0, 0," + (visible ? "0.6)" : "0.0)")
                        }}
                        // the `key` and the `className` depends on u
                        // 
                        onClick={()=> this.selectTile(key, className)}
                    >
                    </div>
                )
                tileArray.push(div);
            }
        }
}

...

I figured out why the error is happening.我弄清楚为什么会发生错误。 I use makeGrid in the constructor before it's bound, which references selectTile, and the variable pointer for "this" is somehow set to a stale variable pointer that is undefined.我在绑定之前在构造函数中使用makeGrid,它引用selectTile,并且“this”的变量指针以某种方式设置为未定义的陈旧变量指针。 Moving the binds up to the top fixes that error.将绑定移动到顶部可以修复该错误。

Immediately after fixing that error, a second one is found.修复该错误后,立即找到第二个错误。 TypeError: tilesArray is undefined This is because I'm referencing selectTile before the state is fully formed, and it again leaves a stale variable pointer behind. TypeError: tilesArray is undefined这是因为我在 state 完全形成之前引用了 selectTile,它再次留下了一个过时的变量指针。

The constructor must now look like this:构造函数现在必须如下所示:

    constructor(props) {
        super(props);
        const topGridData = JSON.parse(localStorage.getItem('grids'))[0];
        this.selectTile = this.selectTile.bind(this);
        this.makeGrid = this.makeGrid.bind(this);
        this.state = {selectedTiles: []}
        this.state = {
            selectedTiles: this.state.selectedTiles,
            ...this.makeGrid(topGridData.dims[0], topGridData.dims[1], topGridData.dims[2],
            topGridData.visible, topGridData.hex)
        }
    }

I immediately ran into a third problem, which is that the initial empty array is being kept as a stale variable pointer despite the attempt to reuse the variable.我立即遇到了第三个问题,即尽管尝试重用变量,但初始的空数组仍被保留为过时的变量指针。 I changed the onClick to onClick={(event) => this.selectTile(this.state.selectedTiles, event)} to send in the state as an argument, and it works(though I do have to change selectTile to use a temporary array since I can't change the state directly via array mutation).我将 onClick 更改为onClick={(event) => this.selectTile(this.state.selectedTiles, event)}以发送 Z9ED39E2EA931586B6A985A6942EF573E数组,因为我无法通过数组突变直接更改 state)。 I tried to use onClick={this.selectTile.bind(this, this.state.selectedTiles)} and it turns out that has the same problem as onClick={this.selectTile} .我尝试使用onClick={this.selectTile.bind(this, this.state.selectedTiles)} ,结果发现与onClick={this.selectTile}有相同的问题。

Also it turns out that "key" is not a property stored in the div element, React kindof yanks it out and stores it internally for dealing with lists.事实证明,“key”不是存储在 div 元素中的属性,React 将其拉出并在内部存储以处理列表。 I wound up setting the id property for each element to the same string and refer to that in selectTile for modifying the array.我最终将每个元素的 id 属性设置为相同的字符串,并在 selectTile 中引用该属性来修改数组。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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