rainmaker
rainmaker

Reputation: 250

How to dynamically change styling of a dynamic piece in react (using functional components)?

TLDR- I want to change the backgroundColor of 1 square onClick. I am not sure how to differentiate this 1 square from the other 63 since they are created dynamically

I am dynamically creating a Board composed of 64 squares and I want a square's background color to change if selected under the proper conditions (preferably under the selectPiece function) and then revert back after a move is made or the piece on the square is unselected. My problem is identifying the dynamically created square to change its state.

Board.js

    let [squareStyle] = useState('squares ');

    function Row(i){
        const newRow = [];
        let count = i * 8;   
        let squareImp;     
        for (let j = 0; j<8; j++){            
            if ((i + j) % 2 === 1){                
                squareImp = squareStyle + "g";
            }else {
                squareImp = squareStyle + "y";
            }
                newRow.push(<div key={count} id={i * 10 + j} className={squareImp} onClick={isMove ? selectMove : props.data[count].name !== null ? selectPiece : undefined  }>
                     { (props.data[count].name != null) &&
                        <img src={images[props.data[count].name]}
                        className="icons"
                        alt="chess piece" />    
                    }
                </div>)            
            count++;
            
        }
        return <div className="rows" key={i}>{newRow}</div>;
    }

If I was using class based Components, then I could use this.setState to change the individual square, using something like -

this.setState({className: "squares b"})

but with Function Components, the setState is unique to each property. So while I can use the id (currently not used for CSS) to grab the selected square, I can't create a [state, setState] variable for each square. Is it possible to dynamically create a variable? aka something like

function setBackground(int id){
    let StateChange = id + ""
    setStateChange({backgroundColor:"blue"})

It doesn't seem to be.

So after I have spent quite a bit of time reading the multitudes of posts on here about how to dynamically change the CSS in React I tried following the only article that I thought might apply to me here- How to add a CSS class to an element on click - React and set the className with a function like so -

Board.js

    function Row(i){
        const newRow = [];
        let count = i * 8; 
        for (let j = 0; j<8; j++){          
                newRow.push(<div key={count} id={i * 10 + j} className={getClassName(i, j)} onClick={isMove ? selectMove : props.data[count].name !== null ? selectPiece : undefined  }>
                     { (props.data[count].name != null) &&
                        <img src={images[props.data[count].name]}
                        className="icons"
                        alt="chess piece" />    
                    }
                </div>)            
            count++;
            
        }
        return <div className="rows" key={i}>{newRow}</div>;
    }
    //function getClassName(e, i, j){ //can't read e
    function getClassName(i, j){
        console.log(i, j)
        //console.log(e.currentTarget)
        let base = "squares ";
        if (clicked){
        // if (clicked && e.currentTarget.id == i * 10 + j){ //this is what I want but I can't figure out how to grab the target to compared the id to          
                 return base + "b";            
        } else {
             if ((i + j) % 2 === 1){                
                 return base + "g";
             }else {
                 return base + "y";
             }
        }
    }

but as you can probably tell from the commented out text, I can't figure out how to grab the event and thus use the id to compare so that I am only changing the background color of the selected square rather than the whole board.

I don't understand the nuances of event handling but I am guessing that because I am calling the function from within the className attribute of the div element rather than from the element itself, that the event just isn't available. I am not sure how to get around it.

Any help would be appreciated

Upvotes: 2

Views: 1183

Answers (1)

Grant Herman
Grant Herman

Reputation: 973

So this would be how I would do it. I abstracted Square to be its own component and it is being create via your for loop. Each of these Squares can manage their own local state so when they are clicked they can update their own state and you dont need to have messy centralized code.

FYI you seem to be using useState incorrectly as it does have its own function to update its local state. The below code is not 100% correct as Im not sure what you are doing, but it should give you a general outline.

I would also review the React docs: https://reactjs.org/docs/hooks-state.html

Just so you can get clear about the useState hook and how it can be utilized.

const { useState } from 'react'

const Square = (i, j, data, count, isMove, selectMove) => {
    const [ squareStyle, setSquareStyle ] = useState('square') // then use setSquareStyle('STYLING) to update local state
    const selectedPiece = data[count].name
    return (
        <div key={count} 
        id={i * 10 + j} 
        className={getClassName(i, j)} 
        onClick={isMove ? selectMove : selectedPiece ? selectPiece : undefined  }>
        { (selectedPiece.name != null) &&
            <img src={images[props.data[count].name]}
                className="icons"
                alt="chess piece" />    
        }
        </div>
    )
}




function Row(i){
    const newRow = [];
    let count = i * 8; 
    for (let j = 0; j<8; j++){          
            newRow.push(
                <Square 
                    i={i} 
                    j={j} 
                    count={count} 
                    isMove={isMove} 
                    selectMove={selectMove} 
                />
            )            
        count++;
        
    }
    return <div className="rows" key={i}>{newRow}</div>;
}
//function getClassName(e, i, j){ //can't read e
function getClassName(i, j){
    console.log(i, j)
    //console.log(e.currentTarget)
    let base = "squares ";
    if (clicked){
    // if (clicked && e.currentTarget.id == i * 10 + j){ //this is what I want but I can't figure out how to grab the target to compared the id to          
             return base + "b";            
    } else {
         if ((i + j) % 2 === 1){                
             return base + "g";
         }else {
             return base + "y";
         }
    }
}

Upvotes: 2

Related Questions