I'm trying to make a simple Tic Tac Toe and I managed to get it working but without react. Now I'm trying to refactor the code and make it work with React, but I can't.
The logic itself works, but the problem is that the functions (restart and makeMove) aren't being called upon pressing the tiles or clicking the restart button
TicTacToe.js:
import React, { useState } from 'react'
import './TicTacToe.css'
const TicTacToe = () => {
const x = '10006';
const o = '9898';
const [firstPlayerMove, setFirstPlayerMove] = useState();
const [gameWon, setGameWon] = useState();
const [moves, setMoves] = useState();
const showMsg = (msg) => {
document.getElementById("msg").innerHTML = msg;
}
const setup = () => {
setFirstPlayerMove(true);
setGameWon(false);
setMoves(0);
showMsg("It's X's turn.");
}
const restart = () => {
Array.prototype.forEach.call(document.getElementsByClassName("tile"), function(element) {
element.innerHTML = "";
});
setup();
}
const makeMove = (tile) => {
if(gameWon) {
return false;
}
if(isValidMove(tile)) {
setMoves(moves+1);
if(firstPlayerMove) {
tile.innerHTML = '&#' + x + ';';
}
else {
tile.innerHTML = '&#' + o + ';';
}
if(checkWin()) {
return true;
}
if(moves === 9) {
showMsg("Draw.");
return true;
}
setFirstPlayerMove(!firstPlayerMove);
if(firstPlayerMove) {
showMsg("It's X's turn.");
}
else {
showMsg("It's O's turn.");
}
}
}
function isValidMove(tile) {
return tile.innerHTML === '';
}
const checkWin = () => {
var tiles = document.getElementsByClassName("tile");
var xTiles = new Array(9);
var oTiles = new Array(9);
for(var i = 0; i < tiles.length; i++) {
if(tiles[i].innerHTML === String.fromCharCode(x)) {
xTiles[i] = 1;
}
if(tiles[i].innerHTML === String.fromCharCode(o)) {
oTiles[i] = 1;
}
}
if(hasWinningPattern(xTiles)) {
showMsg("Player 1 (X) won.");
return true;
}
if(hasWinningPattern(oTiles)) {
showMsg("Player 2 (O) won.");
return true;
}
return false;
}
const hasWinningPattern = (tiles) => {
var winningPatterns = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
for(var i = 0; i < winningPatterns.length; i++) {
var pattern = winningPatterns[i];
var win = true;
for(var j = 0; j < pattern.length; j++) {
if(tiles[pattern[j]] !== 1) {
win = false;
}
}
if(win) {
setGameWon(true);
return true;
}
}
return false;
}
return (
<div>
<body onload={() => setup()}>
<button onclick={() => restart()} class="start">Restart</button>
<div class="board">
<div class="row">
<div onclick={() => makeMove(this)} class="tile"></div>
<div onclick={() => makeMove(this)} class="tile"></div>
<div onclick={() => makeMove(this)} class="tile"></div>
</div>
<div class="row">
<div onclick={() => makeMove(this)} class="tile"></div>
<div onclick={() => makeMove(this)} class="tile"></div>
<div onclick={() => makeMove(this)} class="tile"></div>
</div>
<div class="row">
<div onclick={() => makeMove(this)} class="tile"></div>
<div onclick={() => makeMove(this)} class="tile"></div>
<div onclick={() => makeMove(this)} class="tile"></div>
</div>
</div>
<div class="msg" id="msg"></div>
</body>
</div>
)
}
export default TicTacToe
App.js:
import './App.css';
import TicTacToe from './TicTacToe';
function App() {
return (
<div className="App">
<TicTacToe/>
</div>
);
}
export default App;
Thanks in advance!
The issue you are facing because react has a bit different syntax for the Handling Events you will have to use onClick instead onclick.
check this out.
So changing it to camelCase will make it work.
Also instead following the {() => makeMove(this)} you can actually use event.targent.I hope it will get your work done.
Once your event starts working then you can go through your logic.
Related
i'am trying to resolve tutorial of react using states and hooks ,but i'am sure that i'am missing something , i can't obtain content , need your help , I am beginner
i'll join my code
function Game () {
const [history,setHistory]= useState[{squares:Array(9).fill(null)}]
const [xIsNext,setXIsNext]= useState('true')
const [stepNumber,setStepNumber]= useState(0)
const [current,setCurrent]=useState(null)
const [winner,setWinner]=useState(null)
const handleClick = (i) =>{
history = history.slice(setStepNumber(stepNumber +1));
current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]){
return;
}
squares[i]= xIsNext ? 'X':'O';
setHistory(history.concat([{squares:squares}]));
setStepNumber(history.length);
setXIsNext(!xIsNext);
}
const jumpTo = (step) => {
return( setStepNumber(step) &&
setXIsNext(step % 2) === 0
)};
const moves = history.map((step,move) => {
const desc= move ?
'revenir au tour numero'+ move:
'revenir au début de la partie';
return(
<li key={move}>
<button onClick={() => jumpTo(move)}> {desc}
</button>
</li>
);
});
useEffect(() => {
const history = history;
const current = history[stepNumber];
setWinner(calculateWinner(current.squares));
});
let status = null;
if (winner){
status = winner +'a gagné'
}
else {
status = 'Prochain joueur:' + (xIsNext ? 'X':'O')
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => handleClick(i)}/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
i have this error message
Uncaught TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
at Game (index.js:54:1)
import "./styles.css";
import { useCallback, useState } from "react";
function Square({ onClick, value }) {
return (
<button className="square" onClick={onClick}>
{value}
</button>
);
}
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
const Board = ({ squares, onClick }) => {
const renderSquare = (i) => {
return <Square value={squares[i]} onClick={() => onClick(i)} />;
};
return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
const Game = () => {
const [history, setHistory] = useState([
{
squares: Array(9).fill(null)
}
]);
const [stepNumber, setStepNumber] = useState(0);
const [xIsNext, setXIsNext] = useState(true);
const handleClick = useCallback(
(i) => {
const his = history.slice(0, stepNumber + 1);
const current = his[his.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = xIsNext ? "X" : "O";
setHistory(
history.concat([
{
squares: squares
}
])
);
setStepNumber(history.length);
setXIsNext(!xIsNext);
},
[history, setStepNumber, setXIsNext, setHistory, xIsNext, stepNumber]
);
const jumpTo = (step) => {
setStepNumber(step);
setXIsNext(step % 2 === 0);
};
const current = history[stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ? "Go to move #" + move : "Go to game start";
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{desc}</button>
</li>
);
});
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (xIsNext ? "X" : "O");
}
return (
<div className="game">
<div className="game-board">
<Board squares={current.squares} onClick={(i) => handleClick(i)} />
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
};
export default Game;
I was getting this error when I want to play an audio in my website, I tried some things that I found in others questions, but it doesn't work to me. This is the code:
import { useEffect, useRef, useState } from "react";
import MusicPlayer from "../Molecules/MusicPlayer";
import { songList } from "../../tools/songList";
function NavBar(props) {
const [songs, setSongs] = useState([]);
const [isPlaying, setIsPlaying] = useState(false);
const [songOn, setSongOn] = useState(0);
let songPlayer = useRef(null);
useEffect(() => {
setSongs(songList);
}, []);
let pickSong = (index) => {
songPlayer.current.src = songs[index].audio;
};
let playSong = () => {
songPlayer.current.load();
songPlayer.current
.play()
.then((_) => {})
.catch((err) => {
console.info(err);
});
};
let pauseSong = () => {
songPlayer.current.pause();
};
return (
<>
<div className="nav-bar">
<MusicPlayer
songs={songs}
playSong={playSong}
currentSong={songPlayer}
pauseSong={pauseSong}
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
pickSong={pickSong}
setSongOn={setSongOn}
songOn={songOn}
/>
</div>
<audio
src={songs[0] !== null && songs[0] !== undefined ? songs[0].audio : ""}
ref={songPlayer}
></audio>
</>
);
}
export default NavBar;
import React from "react";
import "./MusicPlayer.css";
function MusicPlayer(props) {
const prevSong = () => {
if (props.songs[props.songOn - 1] !== undefined) {
props.pickSong(props.songOn - 1);
props.playStop();
props.setSongOn(props.songOn - 1);
props.setIsPlaying(true);
} else if (props.songs[props.songOn - 1] === undefined) {
props.pickSong(props.songs.length - 1);
props.playStop();
props.setSongOn(props.songs.length - 1);
props.setIsPlaying(true);
}
};
const nextSong = () => {
if (props.songs[props.songOn + 1] !== undefined) {
props.pickSong(props.songOn + 1);
props.playSong();
props.setSongOn(props.songOn + 1);
} else if (props.songs[props.songOn + 1] === undefined) {
props.pickSong(0);
props.playSong();
props.setSongOn(0);
}
};
const playPause = () => {
if (props.currentSong.current.paused) {
props.playSong();
props.setIsPlaying(true);
} else {
props.pauseSong();
props.setIsPlaying(false);
}
};
return (
<div className="player">
<div className="main">
<div className="controls">
<div className="prev-control" onClick={() => prevSong()}>
<i className="fas fa-step-backward"></i>
</div>
<div
className="play-pause-control paused"
onClick={() => playPause()}
>
<i className="fas fa-play"></i>
<i className="fas fa-pause"></i>
</div>
<div className="next-control" onClick={() => nextSong()}>
<i className="fas fa-step-forward"></i>
</div>
</div>
<div className="details">
<p></p>
</div>
<div className="seekbar">
<input type="range" />
</div>
</div>
</div>
);
}
export default MusicPlayer;
Is there a way to play the audio using the react refs? Or would I have to change it? If you want more info you can let me know in the comments
I need import several functions to my component but I have this error:
when the nintCards function is in the component everything works correctly but when I want to import it it gives me that error. In theory it can't read arrFoods so it says undefined but I passed the parameter to it so I don't know why it shows an error.
Thank you very much for your advice, it is of great help to me.
functionsParams.js
/* Refactoring functions */
const macrosIndex = (foodType, arrFoods) => {
const indices = [];
let idx = arrFoods.map((e) => e.type).indexOf(foodType);
while (idx !== -1) {
indices.push(idx);
idx = arrFoods.map((e) => e.type).indexOf(foodType, idx + 1);
}
return indices;
};
const gMacrosIntCards = (macro, arrFoods) => {
let gProtIntCards = [];
return (gProtIntCards = arrFoods.map((item, i) => {
return (item = arrFoods[i][macro]);
}));
};
const addMacros = (nintCards, macroIntCards, arrFoods) => {
let arrAdd = [];
arrAdd = nintCards(arrFoods).map((item, i) => {
return (item = nintCards(arrFoods)[i] * macroIntCards()[i]);
});
return arrAdd.reduce((a, b) => a + b);
};
function addFoodWeight(arrFoods,addOuputsFoods,inputCarb,inputProt, inputLip) {
arrFoods.forEach((item, i) => {
item.foodWeight = item.weight_int * addOuputsFoods(arrFoods, inputCarb,inputProt, inputLip)[i];
});
}
function nintCards(arrFoods) {
// return array
return arrFoods.map((item) => {
return item.n_int_card;
});
}
export {macrosIndex, gMacrosIntCards, addMacros, addFoodWeight, nintCards};
component (arrFoods is pass how to parameter in the second line function addOuputsFoods: const arrOuputsFoods = nintCards(arrFoods));
import React, { useContext } from "react";
import { UserContext } from "../../UserContext.js";
import NumberFormat from "react-number-format";
import { macrosIndex, gMacrosIntCards, addMacros, addFoodWeight, nintCards, } from "../../functions/functionsParams";
const InputMacros = () => {
const {
inputProt,
setInputProt,
inputLip,
setInputLip,
inputCarb,
setInputCarb,
arrFoods,
} = useContext(UserContext);
function updateInputProt(event) {
const inputProtein = event.target.value;
setInputProt(inputProtein);
addFoodWeight(arrFoods,addOuputsFoods,inputCarb,inputProt, inputLip);
}
function updateInputLip(event) {
const inputLipids = event.target.value;
setInputLip(inputLipids);
addFoodWeight(arrFoods,addOuputsFoods,inputCarb,inputProt, inputLip);
}
function updateInputCarb(event) {
const inputCarbohidrats = event.target.value;
setInputCarb(inputCarbohidrats);
addFoodWeight(arrFoods,addOuputsFoods,inputCarb,inputProt, inputLip);
}
/////////////////
/* function addFoodWeight() {
arrFoods.forEach((item, i) => {
item.foodWeight = item.weight_int * addOuputsFoods()[i];
});
} */
/* function nintCards() {
// return array
return arrFoods.map((item) => {
return item.n_int_card;
});
} */
function addOuputsFoods() {
//insert the exchanges of starchyFoods
const arrOuputsFoods = nintCards();
arrOuputsFoods.forEach((i) => {
arrOuputsFoods[starchyFoodsIndex()[i]] =
nintStarchyFoods() / starchyFoodsIndex().length;
});
//insert the exchanges of proteinFoods
arrOuputsFoods.forEach((i) => {
arrOuputsFoods[proteinFoodIndex()[i]] =
nintProtein() / proteinFoodIndex().length;
});
//insert the exchanges of fats
arrOuputsFoods.forEach((i) => {
arrOuputsFoods[lipidsIndex()[i]] = nintLipids() / lipidsIndex().length;
});
return arrOuputsFoods;
}
function starchyFoodsIndex() {
return macrosIndex("starchyFoods", arrFoods);
}
function nintStarchyFoods() {
return (inputCarb - totalHc()) / 14;
}
function totalHc() {
return addMacros(nintCards, gHcIntCards);
}
function proteinFoodIndex() {
return macrosIndex("proteinFoods", arrFoods);
}
function lipidsIndex() {
return macrosIndex("fats", arrFoods);
}
function totalProtein() {
starchyFoodsIndex();
return addMacros(nintCards, gProtIntCards);
}
function totalLipids() {
starchyFoodsIndex();
proteinFoodIndex();
return addMacros(nintCards, gLipIntCards);
}
function nintProtein() {
return (inputProt - totalProtein()) / 7;
}
function nintLipids() {
return (inputLip - totalLipids()) / 5;
}
function gProtIntCards() {
return gMacrosIntCards("prot", arrFoods);
}
function gLipIntCards() {
return gMacrosIntCards("lip", arrFoods);
}
function gHcIntCards() {
return gMacrosIntCards("hc", arrFoods);
}
//////////////////////
const formatProt = () => {
let formatProt = Math.round(inputProt);
/* formatProt=`${formatProt}g` */
return formatProt;
};
const formatLip = () => {
return Math.round(inputLip);
};
const formatCarb = () => {
return Math.round(inputCarb);
};
return (
<form className="col p-3 mt-4">
<div className="form-row d-flex flex-row ">
<div className="col-md-4 mb-3">
<div className="divMacros">
<label>
{" "}
<h4>g Prot</h4>
</label>
<NumberFormat
name="inputProt"
onChange={updateInputProt}
type="number"
className="inputMacros mt-1"
placeholder="Pro g"
/* suffix={'g'} */
/* displayType={'text'} */
value={formatProt()}
></NumberFormat>
</div>
</div>
<div className="col-md-4 mb-3">
<div className="divMacros">
<label>
{" "}
<h4>g Fats </h4>
</label>
<input
name="inputLip"
onChange={updateInputLip}
type="number"
className="inputMacros mt-1"
placeholder="Lip g"
value={formatLip()}
></input>
</div>
</div>
<div className="col-md-4 mb-3">
<div className="divMacros">
<label>
{" "}
<h4>g Carb </h4>
</label>
<input
name="inputCarb"
onChange={updateInputCarb}
type="number"
className="inputMacros mt-1"
placeholder="CH g"
value={formatCarb()}
></input>
</div>
</div>
</div>
</form>
);
};
export default InputMacros;
Thanks for your support guys, I was able to solve it, I had to pass the parameter arrFoods also to the addMacros function & const arrOuputsFoods = nintCards(arrFoods);
I'm implementing the reactjs.org tutorial, which is a tic-tac-toe game, but I'm trying to use hooks instead of classes as this tutorial does.
This is the code I've wrote in a single file and is working fine:
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
function Board(props) {
const renderSquare = (i) => {
return (
<Square
value={props.squares[i]}
onClick={() => props.onClick(i)}
/>
);
}
return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
}
function Game() {
const [clickedSquares, setClickedSquares] = useState(Array(9).fill(null))
const [xIsNext, setXIsNext] = useState(true)
const [moves, setMoves] = useState(0)
const [playsHistory, setPlaysHistory] = useState([{
history: {
squares: Array(9).fill(null)
},
xIsNext: true
}])
const index = 0
const determineWinner = (squares) => {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
const handleClick = (i) => {
const squares = clickedSquares.slice()
if (determineWinner(squares) || squares[i]) return
squares[i] = xIsNext ? 'X' : 'O';
// console.log(xIsNext)
setXIsNext(!xIsNext)
// console.log(xIsNext)
let current = playsHistory
current ={
history:{
squares: squares},
xIsNext: !xIsNext
}
// console.log(current)
setClickedSquares(squares)
setMoves(prevMoves => prevMoves + 1)
setPlaysHistory(prevPlays => ([...prevPlays, current]))
console.log(playsHistory)
}
let winner = determineWinner(clickedSquares)
let status = winner ? `Winner: ${winner}` : `Next player: ${xIsNext ? 'X' : 'O'}`
if(!winner && moves === 9) status = 'Draw'
return (
<div className="game">
<div className="game-board">
<Board
squares={clickedSquares}
onClick={(i) => handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
export default Game
I'm done with the most of the functionalities, however I've been working in a single file. So I've tried to refactor my project dividing its functions in smaller files for each component. I've created three component files:
Square.jsx:
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
export default Square
Board.jsx:
function Board(props) {
const renderSquare = (i) => {
return (
<Square
value={props.squares[i]}
onClick={() => props.onClick(i)}
/>
);
}
return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
}
export default Board;
and Game.jsx:
function Game() {
const [clickedSquares, setClickedSquares] = useState(Array(9).fill(null))
const [xIsNext, setXIsNext] = useState(true)
const [moves, setMoves] = useState(0)
const [playsHistory, setPlaysHistory] = useState([{
history: {
squares: Array(9).fill(null)
},
xIsNext: true
}])
const determineWinner = (squares) => {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
const handleClick = (i) => {
const squares = clickedSquares.slice()
squares[i] = xIsNext ? 'X' : 'O';
// console.log(xIsNext)
setXIsNext(!xIsNext)
// console.log(xIsNext)
let current = playsHistory
current ={
history:{
squares: squares},
xIsNext: !xIsNext
}
// console.log(current)
setClickedSquares(squares)
setMoves(prevMoves => prevMoves + 1)
setPlaysHistory(prevPlays => ([...prevPlays, current]))
console.log(playsHistory)
if (determineWinner(squares) || squares[i]) return
}
let winner = determineWinner(clickedSquares)
let status = winner ? `Winner: ${winner}` : `Next player: ${xIsNext ? 'X' : 'O'}`
if(!winner && moves === 9) status = 'Draw'
return (
<div className="game">
<div className="game-board">
<Board
squares={clickedSquares}
onClick={(i) => handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
export default Game;
Now, I'm receiving TypeError:Cannot read property '0' of undefined error at this point of Board component:
7 | const renderSquare = (i) => {
8 | return (
9 | <Square
> 10 | value={props.squares[i]}
| ^ 11 | onClick={() => props.onClick(i)}
12 | />
13 | );
If I set the same code in a single .jsx file, it works well. I already removed the [i] from props.squares at line 10, however a new error occours TypeError: props.onClick is not a function. I think Board.jsx file doesn't recognizes the parameters I'm passing from Game.jsx file. How should I componentize my project in different files and keep it working?
You have to be very careful while separating the components. Check out this CodeSandbox link. I haven't done any changes in your logic. There is a scope for code improvement but that wasn't in the requirements so I have just separated your file into individual components file.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I am in the process of making my first React-Flask app. I'll probably get murdered for my code as a beginner but here goes.
I tried to make a video to show the strange behavior. It only seems to happen when the child component maps an array of values to render the desired output
The parent state is updated immediately which you can see in the console log.
However the child component only updates when something else of the page is interacted with.
Link to youtube video to show the weirdness:
https://youtu.be/ysFdCiWr51E
in terms of code
The child component is made of two layers:
Upper child layer:
import React, { useState, useEffect } from 'react';
import { ReactComponent as FilterIcon } from '../icons/filter.svg';
import FilterColumnSelector from './filterSelector.js';
export default function FilterDropZone({
filters,
removeFilter,
handleDraggedItem,
namespace,
handleAddFilter
}) {
const handleDragOver = (event) => {
event.preventDefault();
event.stopPropagation();
};
useEffect(() => {
console.log(filters);
}, [filters]);
const handleDrop = (event) => {
event.preventDefault();
event.stopPropagation();
var columnName = event.dataTransfer.getData('text');
console.log(columnName);
if (columnName) {
handleAddFilter(columnName);
event.dataTransfer.clearData();
}
};
return (
<div className="filterDropZone">
<div className="filterIconWrapper">
<FilterIcon className="filterIcon" />
</div>
<div
className="filterDropZoneInnerDiv"
onDragOver={(e) => handleDragOver(e)}
onDrop={(e) => handleDrop(e)}
>
<ul>
{filters.map((filterItem, index, column, type, choices, selectedValues) => (
<FilterColumnSelector
key={index}
index={index}
column={column}
namespace={namespace}
type={type}
choices={choices}
selectedValues={selectedValues}
handleDraggedItem={handleDraggedItem}
{...filterItem}
/>
))}
</ul>
</div>
</div>
);
}
Lower child layer:
import React, { useState, useEffect } from 'react';
export default function FilterColumnSelector({
selectedColumn,
handleDraggedItem,
namespace,
index,
column,
type,
choices,
selectedValues,
//handleDraggedItem,
...rest
}) {
const handleDragStart = (event, column) => {
event.dataTransfer.setData('text', selectedColumn);
// handleDraggedItem(event.target.id);
};
return (
<>
<li key={index} className="draggableColumnList">
<div
id={namespace.concat(column)}
className="draggableColumn"
draggable="true"
onDragStart={(event) => handleDragStart(event, column)}
>
{column}
</div>
</li>
</>
);
}
The parent component:
// JavaScript source code
import React, { useState, useEffect } from 'react';
import './dataExplorer.css';
import AxisDropZone from './axisDraggableArea.js';
import FilterDropZone from './filter/filterDropZone.js';
import DraggableColumnList from './ColumnList/draggableColumnList.js';
import SearchBar from './search/search.js';
import ColourDropZone from './colour/colourDropZone.js';
import SizeDropZone from './size/size.js';
const removeItemFromArray = (item, array) => {
var index = array.indexOf(item);
if (index !== -1) {
array.splice(index, 1);
}
return array;
};
const uniqueValues = (data, column) => {
var unique = [];
var distinct = [];
for (let i = 0; i < data.length; i++) {
if (!unique[data[i][column]]) {
distinct.push(data[i][column]);
unique[data[i][column]] = 1;
}
}
return distinct;
};
export default function DataExplorer({ data, filename }) {
const [columns, setColumns] = useState([]);
const [childToRemove, setChildToRemove] = useState();
const [draggedItem, setDraggedItem] = useState();
const [xaxisColumn, setXaxisColumn] = useState();
const [yaxisColumn, setYaxisColumn] = useState();
const [xaxisModification, setXaxisModification] = useState('');
const [yaxisModification, setYaxisModification] = useState('');
const [searchTerm, setSearchTerm] = useState();
const [colourColumn, setColourColumn] = useState();
const [sizeColumn, setSizeColumn] = useState();
const [filters, setFilters] = useState([]);
useEffect(() => {
if (data) {
if (data.length > 0) {
setColumns(Object.keys(data[0]));
}
}
}, [data]);
const handleDraggedItem = (childId) => {
setDraggedItem(childId);
};
const handleXaxisColumn = (newColumn) => {
setXaxisColumn(newColumn);
};
const handleYaxisColumn = (newColumn) => {
setYaxisColumn(newColumn);
};
const handleColourColumn = (newColumn) => {
setColourColumn(newColumn);
};
const handleSizeColumn = (newColumn) => {
setSizeColumn(newColumn);
};
const handleYaxisModification = (newModification) => {
if (newModification === 'None') {
setYaxisModification();
} else {
setYaxisModification(newModification);
}
};
const handleXaxisModification = (newModification) => {
setXaxisModification(newModification);
};
const handleSearchChange = (newTerm) => {
setSearchTerm(newTerm);
};
const handleRemoveFilter = (columnToRemove) => {
if (columnToRemove) {
}
};
const handleAddFilter = (columnName) => {
let newFilters = filters;
let newFilter = {
column: columnName,
type: '',
choices: ['A', 'B'],
selectedValues: ['A', 'B']
};
if (
!newFilters.some(function (o) {
return o['column'] === columnName;
})
) {
newFilters.push(newFilter);
setFilters(newFilters);
console.log(filters);
}
};
return (
<div className="data-explorer-container">
<div className="Data">
<div className="navigation-container">
<div className="Navigation"></div>
<div className="Search-Filter">
<SearchBar onChange={handleSearchChange} />
</div>
<div className="Draggable-Columns">
<DraggableColumnList
columns={columns}
handleDraggedItem={handleDraggedItem}
searchTerm={searchTerm}
/>
</div>
<div className="Colours">
<ColourDropZone
draggedItem={draggedItem}
handleColourColumn={handleColourColumn}
selectedColumn={colourColumn}
handleDraggedItem={handleDraggedItem}
namespace="colour"
/>
</div>
<div className="Size">
<SizeDropZone
draggedItem={draggedItem}
handleSizeColumn={handleSizeColumn}
selectedColumn={sizeColumn}
handleDraggedItem={handleDraggedItem}
namespace="size"
/>
</div>
<div className="Shapes"></div>
</div>
</div>
<div className="Graph-Area">
<div className="Axis-graph-wrapper">
<div className="Axis-areas">
<div className="axis-filter-wrapper">
<div className="FilterZone">
<FilterDropZone
filters={filters}
handleAddFilter={handleAddFilter}
removeFilter={handleRemoveFilter}
handleDraggedItem={handleDraggedItem}
namespace="filter"
/>
</div>
<div className="AxisZone">
<div className="axis-wrapper">
<div className="XaxisZone">
<AxisDropZone
handleDraggedItem={handleDraggedItem}
draggedItem={draggedItem}
selectedColumn={xaxisColumn}
handleColumn={handleXaxisColumn}
modifications={false}
namespace="xaxis"
handleModification={handleXaxisModification}
axisModification={xaxisModification}
axisLabel="x-axis"
/>
</div>
<div className="YaxisZone">
<AxisDropZone
handleDraggedItem={handleDraggedItem}
draggedItem={draggedItem}
selectedColumn={yaxisColumn}
handleColumn={handleYaxisColumn}
modifications={true}
namespace="yaxis"
handleModification={handleYaxisModification}
axisModification={yaxisModification}
axisLabel="y-axis"
/>
</div>
</div>
</div>
</div>
</div>
<div className="Graphing-area">
<div className="graph-container">
<div className="Yaxis-Label"></div>
<div className="Graph-Title"></div>
<div className="Xaxis-Label"></div>
<div className="Graph"></div>
</div>
</div>
</div>
</div>
</div>
);
}
99% of reactjs questions on SO can be answered with:
Lift state up
Don't mutate state.
As is often the case, the answer to your question is (2) you are mutating state:
const handleAddFilter = (columnName) => {
// this doesn't do anything, newFilters === filters
let newFilters = filters;
let newFilter = {
column: columnName,
type: '',
choices: ['A', 'B'],
selectedValues: ['A', 'B']
};
if (
!newFilters.some(function (o) {
return o['column'] === columnName;
})
) {
// now on this line you are MUTATING state. DON'T MUTATE STATE
newFilters.push(newFilter);
// newFilters === filters, so you aren't actually updating the state here, but you have mutated it
setFilters(newFilters);
console.log(filters);
}
};
Rewritten:
const handleAddFilter = (columnName) => {
// filter already added, ignore
if(filters.some(({column}) => column === columnName)) return;
// add the new filter
let newFilter = {
column: columnName,
type: '',
choices: ['A', 'B'],
selectedValues: ['A', 'B']
};
setFilters([...filters,newFilter]);
};