Can't clear the inputs after submit - React - javascript

I am trying to clear all the inputs after pressing the submit button. However, every time I click on submit, all the spaces still stay with their input values. The app is basically a sentence guesser where you have to guess the sentence displayed. If it's correct it should display the next button, and display another sentence. The app component is the following:
function App() {
const [isNextVisible, setNextVisible] = useState(false);
const [sentence, setSentence] = useState("");
const [count, setCount] = useState(1);
const [score, setScore] = useState(0);
useEffect(() => {
axios
.get(`###`)
.then((response) => {
let sentence = response.data.data.sentence;
setSentence(sentence);
})
.catch((err) => {
console.log(err);
});
}, [count, sentence]);
let mix = function (str) {
let a = str.split("");
let n = a.length;
for (let j = n - 1; j > 0; j--) {
let x = Math.floor(Math.random() * (j + 1));
let tmp = a[j];
a[j] = a[x];
a[x] = tmp;
}
return a.join("");
};
const shuffle = (str) => {
if (str.length < 2) return str;
let splitted = str.split(" ");
let converted = [];
for (let i = 0; i < splitted.length; i++) {
converted.push(mix(splitted[i]));
}
return converted.join(" ");
};
let shuffled = shuffle(sentence);
let handleSubmit = (e) => {
e.preventDefault();
setCount(count + 1);
setScore(score + 1);
};
return (
<div className="App">
<form onSubmit={handleSubmit} className="main-container">
<h1>{shuffled}</h1>
<p>Guess the sentence! Starting typing</p>
<p>The Yellow Blocks are meant for spaces</p>
<p>Score:{score}</p>
<section className="input-container">
<Input
sentence={sentence}
setNextVisible={setNextVisible}
isNextVisible={isNextVisible}
setScore={setScore}
score={score}
/>
</section>
<section>{isNextVisible ? <button>next</button> : null}</section>
</form>
</div>
);
}```
The input component is this one:
function isValidCharacter(left = null, right = null) {
return left && right && left.toUpperCase() === right.toUpperCase();
}
export default function Input(props) {
const { sentence, setNextVisible } = props;
const refFocus = React.useRef({});
const [value, setValue] = React.useState(Array(sentence.length).fill(""));
const handleChange = (index) => (event) => {
if (isValidCharacter(event.target.value, sentence[index])) {
const nextFocus = index + 1;
if (nextFocus in refFocus.current) {
refFocus.current[nextFocus].focus();
}
}
setValue((prev) => {
const next = prev.slice(0);
next[index] = event.target.value;
return next;
});
};
React.useEffect(() => {
// compare inputs with the provided word
if (value.join("").toLocaleLowerCase() === sentence.toLocaleLowerCase()) {
setNextVisible(true);
} else {
setNextVisible(false);
}
}, [value, setNextVisible, sentence]);
return (
<div style={{ display: "flex" }}>
{sentence.split("").map((character, index) => {
const hasBackgroundGreen =
value[index] === ""
? {}
: isValidCharacter(value[index], character)
? { background: "#4caf50" }
: { background: "#e1e1e1" };
return (
<input
key={index}
ref={(ref) => (refFocus.current[index] = ref)}
type="text"
value={value[index]}
onChange={handleChange(index)}
style={hasBackgroundGreen}
/>
);
})}
</div>
);
}

You do not interact with the values in the form in any way, you yourself set them value = {* value *}, so in order to change them you need:
As an option, add this to the code "Input" component
const {sentence, setNextVisible, score} = props;
useEffect(() => {
setValue(Array(sentence.length).fill(""));
}, [score])

Related

How can I get two non-repeated random objects from an array at first render in React?

I'm making a simple app that will simulate a hand of blackjack. I've got the 'hit' functionality (getting one random card), but I'm struggling with getting the two initial random, non repeated cards on first render. I feel like there must be a more elegant solution to what I have in mind (not to mentions it's not working).
I would appreciate any direction on this, since I'm not sure why filtering and updating the original array isn't working.
Here's the code snippet:
import {useState, useEffect} from 'react'
//import useCards from '../hooks/useCards'
import Card from '../components/Card';
import Total from '../components/Total';
import {deckArray} from '../utils/data'
export default function Home(){
const [dealCards, setDealCards] = useState(false)
const [usersCards, setUsersCards] = useState([])
const [deck, setDeck] = useState(deckArray)
const [isReset, setIsReset] = useState(true)
const [total, setTotal] = useState(0)
const [isStarted, setIsStarted] = useState(false)
useEffect(() => {
if(dealCards===true){
const randomCard = deck[Math.floor(Math.random()*deck.length)];
const newCardsArray = deck.filter(el => el.index !== randomCard.index)
const chosenCardArray = deck.filter(el => el.index === randomCard.index)
const chosenCard = chosenCardArray[0]
setDeck(newCardsArray)
setUsersCards(prevCards => [...prevCards, chosenCard])
console.log(newCardsArray.length)
setDealCards(false)
}
}, [usersCards, dealCards, deck])
useEffect(() => {
if(isReset){
setUsersCards([])
setDeck(deckArray)
setDealCards(false)
setTotal(0)
}
},[isReset])
//function to generate two random cards when user presses 'play'
useEffect(() => {
if(isStarted){
//generate two random cards
const randomCard = deck[Math.floor(Math.random()*deck.length)];
const newCardsArray = deck.filter(el => el.index !== randomCard.index)
const chosenCardArray = deck.filter(el => el.index === randomCard.index)
const chosenCard = chosenCardArray[0]
setDeck(newCardsArray)
setUsersCards(prevCards => [...prevCards, chosenCard])
const randomCard1 = deck[Math.floor(Math.random()*deck.length)];
const newCardsArray1 = deck.filter(el => el.index !== randomCard1.index)
const chosenCardArray1 = deck.filter(el => el.index === randomCard1.index)
const chosenCard1 = chosenCardArray1[1]
setDeck(newCardsArray1)
setUsersCards(prevCards => [...prevCards, chosenCard1])
console.log(newCardsArray1.length)
setDealCards(false)
}
}, [isStarted, deck, dealCards])
return (
<>
<Card usersCards={usersCards} />
{!isStarted && <button onClick={() => setIsStarted(true)}>PLAY</button>}
{isStarted && <>
<Total usersCards={usersCards} total={total} setTotal={setTotal}/>
<button onClick={() => setDealCards(true)}>HIT</button>
<button>STAND</button>
<button onClick={() => setIsReset(true)}>RESET</button>
</>}
</>
)
}
Many thanks for any help!
The code here is overusing useEffect to implement logic that should be done with simple event handlers. Only use useEffect when you're dealing with things that can't be determined before the render, like network calls, or depend on a reference to a DOM element outside of the normal rendering flow, like drawing on a <canvas>. These are side effects because they don't directly pertain to building the current render which the rest of the component body is working towards.
I don't have your utility and component imports, but here's an example that you should be able to adapt to your use case.
// utility library "import"
const cards = (() => {
const shuffle = a => {
a = a.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = ~~(Math.random() * (i + 1));
const x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
};
const frz = (...args) => Object.freeze(...args);
const suits = frz([..."HCSD"]);
const faces = frz([..."AJQK"]);
const pips = frz([...Array(9)].map((_, i) => i + 2));
const ranks = frz([...pips, ...faces]);
const cards = frz(
suits.flatMap(s => ranks.map(r => frz({
rank: r,
suit: s,
str: r + s,
value: isNaN(r) ? (r === "A" ? 11 : 10) : r,
})))
);
const shuffled = () => shuffle(cards);
return {shuffled};
})();
const Game = () => {
const startHandSize = 2;
const [deck, setDeck] = React.useState(cards.shuffled());
const [cardsDealt, setCardsDealt] =
React.useState(startHandSize);
const deal = () => {
setCardsDealt(startHandSize);
setDeck(cards.shuffled());
};
const hit = () => !bust && setCardsDealt(prev => prev + 1);
const cardsInPlay = deck.slice(-cardsDealt);
const total = cardsInPlay.reduce((a, e) => a + e.value, 0);
const bust = total > 21;
return (
<div>
<button onClick={deal}>Deal</button>
<button disabled={bust} onClick={hit}>
Hit
</button>
<div>
{cardsInPlay.map(e => (
<div key={e.str}>{e.str}</div>
))}
</div>
<div>Total: {total}</div>
<div>{bust && "Bust!"}</div>
</div>
);
};
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Game />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>

JS Function designed to return elements to React component not returning properly

I am trying to check a set of data to see if a string has a particular prefix or suffix and then return the string broken up into <span> elements. The function I have written correctly matches the parts of teh string with the relevant morphemes in my data set but doesn't return the spans split up.
Currently it returns something like this:
<span></span>
<span>word</span>
<span></span>
Im not sure what i am doing wrong. Here is the code:
export const morphemeCheck2 = (word) => {
return (
<span>
<span>{prefixCheck(word)}</span>
<span>{suffixCheck(word)}</span>
<span>{baseWord(word)}</span>
</span>
)
}
const prefixCheck = (x) => {
api.getPrefixes().then((data) => {
let prefixes = data
for (let p = 0; p < prefixes.length; p++) {
let prefix = prefixes[p].affix_name
if (x.startsWith(prefix)) {
return (
<span className={'suffix ' + prefix} onMouseOver={() => {}}>
{prefix}
</span>
)
}
}
})
}
//needs refactoring
const baseWord = (x) => {
if (prefixCheck(x) && suffixCheck(x)) {
const prefix = prefixCheck(x)
const suffix = suffixCheck(x)
const prefixLength = prefix.props.children.length
const suffixLength = suffix.props.children.length
const tail = x.length - suffixLength
return x.substring(prefixLength, tail)
} else if (prefixCheck(x)) {
const prefix = prefixCheck(x)
const prefixLength = prefix.props.children.length
return x.substring(prefixLength, x.length)
} else if (suffixCheck(x)) {
const suffix = suffixCheck(x)
const suffixLength = suffix.props.children.length
const tail = x.length - suffixLength
return x.substring(0, tail)
} else {
return x
}
}
const suffixCheck = (x) => {
api.getSuffixes().then((data) => {
let suffixes = data
for (let s = 0; s < suffixes.length; s++) {
let suffix = suffixes[s].affix_name
if (x.endsWith(suffix)) {
console.log(suffix)
return (
<span className={'suffix ' + suffix} onMouseOver={() => {}}>
{suffix}
</span>
)
}
}
})
}
Here is where it is called in the React component:
const AnalysisArea = () => {
[...]
let text = "recoded"
return (
<React.Fragment>
<Card className="analysisframe">
<p className="replicated-text" onClick={handleOpen}>
{decoder.morphemeCheck2(text)}
</p>
</Card>
</React.Fragment>
)
}
You need to return something from prefixCheck and suffixCheck. Inside those two functions you just executed async call and did nothing with the result(no return statement inside function). I advise you to rewrite in async/await manner(because of readability) and, of course, to return desired result from those functions. If you need more help(to rewrite those functions) feel free to reach.
UPDATED WITH EXAMPLE OF REFACTORED FUNCTIONS
Key point is to wait for the response in order to use that response of your async call and do something with it(return something based on api call response).
const suffixCheck = async (x) => {
const data = await api.getSuffixes(); // You need to await in order to get data that you want to use in order to return something
let suffixes = data
for (let s = 0; s < suffixes.length; s++) {
let suffix = suffixes[s].affix_name
if (x.endsWith(suffix)) {
console.log(suffix)
return (
<span className={'suffix ' + suffix} onMouseOver={() => {}}>
{suffix}
</span>
)
}
}
return '';
}
const prefixCheck = async (x) => {
const data = await api.getPrefixes();
let prefixes = data
for (let p = 0; p < prefixes.length; p++) {
let prefix = prefixes[p].affix_name
if (x.startsWith(prefix)) {
return (
<span className={'suffix ' + prefix} onMouseOver={() => {}}>
{prefix}
</span>
)
}
}
return '';
}

How to make an html element always stay inside boundaries?

I'm coding a search algorithms visualizer in react and I'm having a small glitch whenever I move my start or end nodes. When I press down on the mouse and move the cursor over the board, the nodes update accordingly. That is until the cursor goes beyond the boundaries of the grid, leaving a node with the start-node or end-node classname, visually creating two (or more) nodes, as such:
This doesn't affect functionality but it is pretty annoying. Ideally, I'm looking to make it work like it does on this demo: https://pathfindout.com/, where it doesn't matter where the user moves the cursor while dragging the nodes, they always stay inside the limits of the board. This is the code for my Node component so far:
const Node = ({ row, col, isWall, isStart, isEnd, handleState, handleMouseState }) => {
const { nodesMatrix, updateNodes } = handleState;
const { isMouseDown, setIsMouseDown } = handleMouseState;
//This variable helps to prevent the user
//from stacking the 'start' and 'end' nodes
//on top of each other. Instead they jump to
//the next available sibling.
let prevCollision = null;
const handleMouseDown = (e) => {
e.preventDefault();
isStart ? setIsMouseDown(1) : isEnd ? setIsMouseDown(2) : setIsMouseDown(3);
};
const handleMouseUp = (e) => {
setIsMouseDown(0);
const newParent = e.target.id ? e.target : null;
const [newParentRow, newParentCol] = newParent.id.match(/\d+/g);
if (isMouseDown === 1) updateNodes(false, true, parseInt(newParentRow), parseInt(newParentCol));
if (isMouseDown === 2) updateNodes(false, false, parseInt(newParentRow), parseInt(newParentCol));
if (isMouseDown === 3) updateNodes(true, false, parseInt(newParentRow), parseInt(newParentCol));
};
const handleMouseEnter = (e) => {
const newParent = e.target.id ? e.target : null;
const prevParent = e.relatedTarget;
const [newParentRow, newParentCol] = e.target.id.match(/\d+/g);
const nodePointer = nodesMatrix[newParentRow][newParentCol];
//moving start node
if (isMouseDown === 1) {
const collision = isEnd;
if (!collision) {
prevParent.classList.remove('start-node');
newParent.classList.add('start-node');
nodePointer.isStart = true;
} else {
prevCollision = prevParent;
}
};
//moving end node
if (isMouseDown === 2) {
const collision = isStart;
if (!collision) {
prevParent.classList.remove('end-node');
newParent.classList.add('end-node');
nodePointer.isEnd = true;
} else {
prevCollision = prevParent;
}
}
//creating walls
if (isMouseDown === 3) {
if(!isStart && !isEnd) {
if (isWall) {
newParent.classList.remove('wall-node');
nodePointer.isWall = false;
} else {
newParent.classList.add('wall-node');
nodePointer.isWall = true;
};
}
};
};
const handleMouseLeave = (e) => {
const prevParent = e.target.id ? e.target : null;
const [prevRow, prevCol] = prevParent.id.match(/\d+/g);
const nodePointer = nodesMatrix[prevRow][prevCol];
if (isMouseDown === 1) {
if (prevCollision) prevCollision.classList.remove('start-node');
nodePointer.isStart = false;
};
if (isMouseDown === 2) {
if (prevCollision) prevCollision.classList.remove('end-node');
nodePointer.isEnd = false;
};
};
const handleClick = (e) => {
const target = e.target.id ? e.target : null;
const [row, col] = target.id.match(/\d+/g);
const nodePointer = nodesMatrix[row][col];
if (isWall) {
target.classList.remove('wall-node');
nodePointer.isWall = false;
} else {
target.classList.add('wall-node');
nodePointer.isWall = true;
}
};
return (
<div id={`node-${ row }-${ col }`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseUp={handleMouseUp}
onMouseDown={handleMouseDown}
onClick={(isStart || isEnd) ? null : handleClick}
className={
`${ isStart ? 'start-node' : isEnd ? 'end-node' : isWall ? 'wall-node' : '' }
node w-6 h-6 border border-blue-400`} />
)
};
export default Node
Any help is greatly appreciated!
Edit:
This is the parent Grid component if it helps with anything:
const Grid = () => {
const [nodesMatrix, setNodesMatrix] = useState([]);
const [speed, setSpeed] = useState(5);
const [startNodeRow, setStartNodeRow] = useState(6);
const [startNodeCol, setStartNodeCol] = useState(10);
const [endNodeRow, setEndNodeRow] = useState(6);
const [endNodeCol, setEndNodeCol] = useState(17);
// 1 to move 'start' node;
// 2 to move 'end' node;
// 3 to create a wall;
const [isMouseDown, setIsMouseDown] = useState(0);
//Initializes Grid
useEffect(() => {
const cells = [];
const startNodeRow = 6;
const startNodeCol = 10;
const endNodeRow = 6;
const endNodeCol = 17;
for (let row = 0; row < ROWS; row++) {
const currentRow = [];
for (let col = 0; col < COLUMNS; col++) {
const node = {
row,
col,
isWall: false,
isStart: row === startNodeRow && col === startNodeCol,
isEnd: row === endNodeRow && col === endNodeCol,
distance: Infinity,
visited: false,
previousNode: null
};
currentRow.push(node);
};
cells.push(currentRow);
};
setNodesMatrix(cells);
}, []);
useEffect(() => {
clearAllNodesStyles();
}, [isMouseDown]);
const resetMatrix = (matrix) => {
const matrixRef = [...matrix]
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLUMNS; col++) {
const node = matrixRef[row][col];
node.distance = Infinity;
node.previousNode = null;
node.visited = false;
node.isWall = node.isStart || node.isEnd ? false : node.isWall
};
};
return matrixRef
};
const updateNodes = (isWall, isStart, newRow, newCol) => {
const newGrid = resetMatrix(nodesMatrix);
if(isWall) return setNodesMatrix(newGrid);
if (isStart) {
setStartNodeRow(newRow);
setStartNodeCol(newCol);
} else {
setEndNodeRow(newRow);
setEndNodeCol(newCol);
};
setNodesMatrix(newGrid);
};
return (
<div className="flex flex-col self-center">
{nodesMatrix.map((row, rowIndex) => {
return (
<div key={rowIndex} className='flex'>
{row.map((node, index) => {
const { row, col, isWall, isStart, isEnd } = node;
return <Node
key={index}
row={row}
col={col}
isWall={isWall}
isStart={isStart}
isEnd={isEnd}
handleState={{nodesMatrix, updateNodes}}
handleMouseState={{isMouseDown, setIsMouseDown}} />
})}
</div>
)
})
}
<button
onClick={() => visualizeDijkstra(nodesMatrix, { startNodeRow, startNodeCol, endNodeRow, endNodeCol, speed })}
className="...">
Search Path
</button>
</div>
)
};
export default Grid
As I mentioned in comment, The problem is, you are not tracking whether the mouse going out of the Grid or not. You are also not storing the last Node before going out of grid. As a result, when the mouse again enters the Grid, the program doesn't know which one is the last node which it visited. So, it can't remove the class from the last node.
import React, { useState, useEffect } from "react";
import { clearAllNodesStyles, visualizeDijkstra } from "../algorithms/dijkstra";
import Node from "./Node";
const COLUMNS = 60;
const ROWS = 25;
const Grid = () => {
const [nodesMatrix, setNodesMatrix] = useState([]);
const [speed, setSpeed] = useState(5);
const [startNodeRow, setStartNodeRow] = useState(6);
const [startNodeCol, setStartNodeCol] = useState(10);
const [endNodeRow, setEndNodeRow] = useState(6);
const [endNodeCol, setEndNodeCol] = useState(17);
// 1 to move 'start' node;
// 2 to move 'end' node;
// 3 to create a wall;
const [isMouseDown, setIsMouseDown] = useState(0);
//Initializes Grid
useEffect(() => {
const cells = [];
const startNodeRow = 6;
const startNodeCol = 10;
const endNodeRow = 6;
const endNodeCol = 17;
for (let row = 0; row < ROWS; row++) {
const currentRow = [];
for (let col = 0; col < COLUMNS; col++) {
const node = {
row,
col,
isWall: false,
isStart: row === startNodeRow && col === startNodeCol,
isEnd: row === endNodeRow && col === endNodeCol,
distance: Infinity,
visited: false,
previousNode: null,
};
currentRow.push(node);
}
cells.push(currentRow);
}
setNodesMatrix(cells);
}, []);
useEffect(() => {
clearAllNodesStyles();
}, [isMouseDown]);
const resetMatrix = (matrix) => {
const copyCat = [...matrix];
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLUMNS; col++) {
const node = copyCat[row][col];
const newNode = {
...node,
distance: Infinity,
previousNode: null,
visited: false,
isWall: node.isStart || node.isEnd ? false : node.isWall,
};
copyCat[row][col] = newNode;
}
}
return copyCat;
};
const updateNodes = (isWall, isStart, newRow, newCol) => {
const newGrid = resetMatrix(nodesMatrix);
if (isWall) return setNodesMatrix(newGrid);
if (isStart) {
setStartNodeRow(newRow);
setStartNodeCol(newCol);
} else {
setEndNodeRow(newRow);
setEndNodeCol(newCol);
}
setNodesMatrix(newGrid);
};
const handleTouchBoundaryLine = (e) => {
const prevParent = e.target.id ? e.target : null;
if (isMouseDown === 1 || isMouseDown === 2) {
if (prevParent) {
const [prevRow, prevCol] = prevParent.id.match(/\d+/g);
const nodePointer = nodesMatrix[prevRow][prevCol];
if (isMouseDown === 1) nodePointer.isStart = true;
if (isMouseDown === 2) nodePointer.isEnd = true;
let clickEvent = document.createEvent("MouseEvents");
clickEvent.initEvent("mouseup", true, true);
prevParent.dispatchEvent(clickEvent);
}
}
};
return (
<div className="flex flex-col self-center">
<div id="grid-boundary" onMouseLeave={handleTouchBoundaryLine}>
{nodesMatrix.map((row, rowIndex) => {
return (
<div key={rowIndex} className="flex">
{row.map((node, index) => {
const { row, col, isWall, isStart, isEnd } = node;
return (
<Node
key={index}
row={row}
col={col}
isWall={isWall}
isStart={isStart}
isEnd={isEnd}
handleState={{ nodesMatrix, updateNodes }}
handleMouseState={{ isMouseDown, setIsMouseDown }}
/>
);
})}
</div>
);
})}
</div>
<button
onClick={() =>
visualizeDijkstra(nodesMatrix, {
startNodeRow,
startNodeCol,
endNodeRow,
endNodeCol,
speed,
})
}
className="h-20 w-full text-white hover:bg-blue-800 bg-blue-700 place-self-end"
>
Search Path
</button>
</div>
);
};
export default Grid;
I have added a boundary line/div around the grid. So, whenever the mouse leaves that div, it will dispatch mouseup event on the previous visited Node and mark it as the start/end Node.

Pagination does not work when filtering. How can i fix?

i have one datatable. Datatable has result and pagination. Result works fine, but when filtering, pagination moves according to result. This is normal page.
Filtering but still 9 page. And result 10.
now result 20 but page 5. Because in normal case, when you make 20, it becomes 5 pages. How can i fix?
const [currentPage, setCurrentPage] = useState(1);
const [select, setSelect] = useState(10);
function handleChange(e) {
setSelect(e.target.value);
}
const indexOfLast = currentPage * select;
const indexOfFirst = indexOfLast - select;
const splitData = rows.slice(indexOfFirst, indexOfLast);
const totalPages = Math.ceil(rows.length / select);
const renderHeadingRow = (item) => {
return <td>{item}</td>;
};
const renderRow = (item, index) => {
console.log(splitData[index])
return (
<tr key={index}>
{splitData[index]
?.filter((val) => {
if (searchWord == "") {
return val;
}
else if(item[1].toLowerCase().includes(searchWord.toLowerCase())){
return val
}
else if(item[2].toLowerCase().includes(searchWord.toLowerCase())){
return val
}
else if(item[5].toLowerCase().includes(searchWord.toLowerCase())){
return val
}
})
.map((data, no) => {
return <td>{splitData[index][no]}</td>;
})}
</tr>
);
};
const theadData = <tr>{heading.map(renderHeadingRow)}</tr>;
const tbodyData = splitData.map(renderRow);
You can try to use useEffect to have a side-effect for row filtering. All your displayed data and total page calculation need to rely on filtered rows instead of original rows.
const [currentPage, setCurrentPage] = useState(1);
const [select, setSelect] = useState(10);
const [filteredRows, setFilteredRows] = useState(rows || [])
function handleChange(e) {
setSelect(e.target.value);
}
React.useEffect(() => {
const updatedCurrentPage = 1
const updatedRows = rows.filter((item) => {
if (searchWord == "") {
return item;
}
else if(item[1].toLowerCase().includes(searchWord.toLowerCase())){
return item;
}
else if(item[2].toLowerCase().includes(searchWord.toLowerCase())){
return item;
}
else if(item[5].toLowerCase().includes(searchWord.toLowerCase())){
return item;
}
})
const totalPages = Math.ceil(updatedRows.length / select);
setFilteredRows(updatedRows) //set filtered rows when `searchWord` changes
setCurrentPage(1) //reset current page to 1 if applying filters
}, [searchWord])
const renderHeadingRow = (item) => {
return <td>{item}</td>;
};
const renderRow = (item, index) => {
return (
<tr key={index}>
{
item.map((data) => {
return <td>{data}</td>;
})}
</tr>
);
};
const indexOfLast = currentPage * select;
const indexOfFirst = indexOfLast - select;
const splitData = filteredRows.slice(indexOfFirst, indexOfLast); //use `filteredRows` for display
const totalPages = Math.ceil(filteredRows.length / select); //use `filteredRows` to calculate total pages
const theadData = <tr>{heading.map(renderHeadingRow)}</tr>;
const tbodyData = displayedRows.map(renderRow);

Read a text file in Javascript and count the number of each word

I am leaving this answer here because it took me about an hour+ to solve and I am hoping to help anyone else looking for a similar answer.
The question has a few parts that are all answered in separate SO posts:
Read a text file in javascript
Parse the text to get a space delimited string, excluding special characters, tabs, new lines, etc.
Count the number of each word
Display a list in descending order
Starting with an input that accepts a file and a function that will eventually sort our string, ezString:
return (
<div>
<input type="file" onChange={e => showFile(e)}/>
{ezString ? getSortedArr() : null}
</div>
);
and a function to turn that file into text (with a useState var ezString)
const [ezString, setEzString] = useState(null)
const showFile = async (e) => {
e.preventDefault()
const reader = new FileReader()
reader.onload = async (e) => {
const file = e.target.result
const goodString = file.replaceAll(/\s\s+/g, ' ')
.replaceAll(/(\r\n|\r|\n)/g, ' ')
.replaceAll(/[^a-zA-Z ]/g, "").toLowerCase()
setEzString(goodString);
};
reader.readAsText(e.target.files[0])
}
and a sorting function
const getSortedArr = () => {
let wordArray = ezString.split(' ').filter(n => n)
let wordCount = {};
for (let word of wordArray) {
if (wordCount[word]) {
wordCount[word] = wordCount[word] + 1
} else {
wordCount[word] = 1
}
}
let sortedArr = Object.entries(wordCount).sort((a, b) => b[1] - a[1])
return sortedArr ? sortedArr.map(arr => {
return (
<div key={arr[0]}>
<p style={{fontSize: 16}}>{arr[0]}: {arr[1]}</p>
</div>)
}) : null
}
With these parts we have the full component:
import React, {useState} from 'react'
const WordCounter = () => {
const [ezString, setEzString] = useState(null)
const showFile = async (e) => {
e.preventDefault()
const reader = new FileReader()
reader.onload = async (e) => {
const file = e.target.result
const goodString = file.replaceAll(/\s\s+/g, ' ')
.replaceAll(/(\r\n|\r|\n)/g, ' ')
.replaceAll(/[^a-zA-Z ]/g, "").toLowerCase()
setEzString(goodString);
};
reader.readAsText(e.target.files[0])
}
const getSortedArr = () => {
let wordArray = ezString.split(' ').filter(n => n)
let wordCount = {};
for (let word of wordArray) {
if (wordCount[word]) {
wordCount[word] = wordCount[word] + 1
} else {
wordCount[word] = 1
}
}
let sortedArr = Object.entries(wordCount).sort((a, b) => b[1] - a[1])
return sortedArr ? sortedArr.map(arr => {
return (
<div key={arr[0]}>
<p style={{fontSize: 16}}>{arr[0]}: {arr[1]}</p>
</div>)
}) : null
}
return (
<div className="App">
<input type="file" onChange={e => showFile(e)}/>
{ezString ? getSortedArr() : null}
</div>
);
}
export default WordCounter;
Areas for improvement:
I'm terrible with regex, thus the terrible regex and the need to filter empty strings out of the wordArray

Categories