React component not rerendering based on props change. JSX in async function - javascript

I have a filterTime state which is set to "Daily" as default. It is located at the parent of Friends.js and passed as prop.
Friends.js
const Friends = (props) =>{
const [friendsDisplay, setFriendsDisplay] = useState([]);
let friends = [];
useEffect(()=>{
fetchData();
}, [])
const fetchData = async() => {
try{
let doc = await docRef.get();
let data = doc.data();
for(let i of data.Friends){ //Getting the array of friends' uids
friends.push(i);
}
friends = await Promise.all(friends.map(async UserFriend => {
if(UserFriend !== ""){
let friend = await fetchUserFriendData(UserFriend);
return (<Friend key = {UserFriend}
UserFriend = {friend}
isUser = {false}
src = {``}
filterTime = {props.filterTime}/>
)
}
return null;
}));
let userInfo = <Friend key = {uid}
UserFriend = {data}
isUser = {true}
src = {``}
filterTime = {props.filterTime}/>
friends = friends.concat(userInfo);
sortFriends();
}catch(error){
console.log(error);
}
}
const sortFriends = () =>{
if(friends.length !== 0){ //If User Has Friends Sorts Them in Descending Order
friends.sort((a, b) => {
if(props.filterTime === "Daily"){
return (a.props.UserFriend.XpEarnedToday > b.props.UserFriend.XpEarnedToday ? -1 : 1);
}
if(props.filterTime === "Weekly"){
return (a.props.UserFriend.XpEarnedWeek > b.props.UserFriend.XpEarnedWeek ? -1 : 1);
}
if(props.filterTime === "Monthly"){
return (a.props.UserFriend.XpEarnedMonth > b.props.UserFriend.XpEarnedMonth ? -1 : 1);
}
return -1;
});
setFriendsDisplay(friends);
}
return (
<>
<div className = "friendsContainer">
{friendsDisplay.length !== 0 && //If User Has Friends (ScoreBoard)
<div className = "friends">{friendsDisplay}</div>
}{friendsDisplay.length === 1 && //If User Has No Friends (Add Friend Button)
<>{friendsDisplay}</>
}
</div>
</>
);
}
Friend.js
const Friend = (props) =>{ //filterTime---> Daily Weekly etc. UserFriend--->all properties of friend isUser--->boolean src--->source for picture
const[friendName, setFriendName] = useState(props.UserFriend.Name);
let WindowWidth = windowResizeListener();
return (
<div className = {`friendBackground ${props.isUser ? "friendBackground-User" : ""}`}>
<div className = "friendPorperties">
<p className = {`friendRank ${props.isUser ? "friendRank-User" : ""}`}>1</p>
<p className = {`friendName ${props.isUser ? "friendName-User" : ""}`}>{friendName}</p>
</div>
{props.filterTime === "Daily" && //checks if filter = daily
<p className = {`friendXp ${props.isUser ? "friendXp-User" : ""}`}>{`+${props.UserFriend.XpEarnedToday}Xp`}</p>
}
{props.filterTime === "Weekly" && //checks if filter = weekly
<p className = {`friendXp ${props.isUser ? "friendXp-User" : ""}`}>{`+${props.UserFriend.XpEarnedWeek}Xp`}</p>
}
{props.filterTime === "Monthly" && //checks if filter = monthly
<p className = {`friendXp ${props.isUser ? "friendXp-User" : ""}`}>{`+${props.UserFriend.XpEarnedMonth}Xp`}</p>
}
</div>
);
}
When I use;
useEffect(()=>{
console.log(props.filterTime);
}, [props.filterTime])
In Friends.js it logs the correct filterTime value as I click different filters. Whereas in Friend.js it is stuck in the first value ("Daily") and won't update if I click different filters.
So the problem isn't about filterTime it is about child not rerendering as the filterTime value changes.
React Dev tools also shows prop.filterTime isn't changing for Friend.js.
How can I fix this.

Related

Can't parse excel date to string with xlsx in a util function javascript

I am trying to read an excel document which has a datetime column.Uploading it to the website,instead of date column i get those numbers which represent the time.I know that using xlsx utils sheet to json we can convert the date cell to the format we want,but i am using a big function to parse this document and the only thing i use from xlsx is xlsx.read . in this function i wrote celldate to true but my web app throws the error that i can not use date object as react child and i dont understand how to convert it as string or at least to read objects property. Could you help me please?
excelToJson utility function which i use:
const XLSX = require("xlsx");
const extend = require("node.extend");
const excelToJson = (function () {
let _config = {};
const getCellRow = (cell) => Number(cell.replace(/[A-z]/gi, ""));
const getCellColumn = (cell) => cell.replace(/[0-9]/g, "").toUpperCase();
const getRangeBegin = (cell) => cell.match(/^[^:]*/)[0];
const getRangeEnd = (cell) => cell.match(/[^:]*$/)[0];
function getSheetCellValue(sheetCell) {
if (!sheetCell) {
return undefined;
}
if (sheetCell.t === "z" && _config.sheetStubs) {
return null;
}
return sheetCell.t === "n" || sheetCell.t === "d"
? sheetCell.v
: (sheetCell.w && sheetCell.w.trim && sheetCell.w.trim()) || sheetCell.w;
}
const parseSheet = (sheetData, workbook) => {
const sheetName =
sheetData.constructor === String ? sheetData : sheetData.name;
const sheet = workbook.Sheets[sheetName];
const columnToKey = sheetData.columnToKey || _config.columnToKey;
const range = sheetData.range || _config.range;
const headerRows =
(sheetData.header && sheetData.header.rows) ||
(_config.header && _config.header.rows);
const headerRowToKeys =
(sheetData.header && sheetData.header.rowToKeys) ||
(_config.header && _config.header.rowToKeys);
let strictRangeColumns;
let strictRangeRows;
if (range) {
strictRangeColumns = {
from: getCellColumn(getRangeBegin(range)),
to: getCellColumn(getRangeEnd(range)),
};
strictRangeRows = {
from: getCellRow(getRangeBegin(range)),
to: getCellRow(getRangeEnd(range)),
};
}
let rows = [];
for (let cell in sheet) {
// !ref is not a data to be retrieved || this cell doesn't have a value
if (
cell === "!ref" ||
(sheet[cell].v === undefined &&
!(_config.sheetStubs && sheet[cell].t === "z"))
) {
continue;
}
const row = getCellRow(cell);
const column = getCellColumn(cell);
// Is a Header row
if (headerRows && row <= headerRows) {
continue;
}
// This column is not _configured to be retrieved
if (columnToKey && !(columnToKey[column] || columnToKey["*"])) {
continue;
}
// This cell is out of the _configured range
if (
strictRangeColumns &&
strictRangeRows &&
(column < strictRangeColumns.from ||
column > strictRangeColumns.to ||
row < strictRangeRows.from ||
row > strictRangeRows.to)
) {
continue;
}
const rowData = (rows[row] = rows[row] || {});
let columnData =
columnToKey && (columnToKey[column] || columnToKey["*"])
? columnToKey[column] || columnToKey["*"]
: headerRowToKeys
? `{{${column}${headerRowToKeys}}}`
: column;
let dataVariables = columnData.match(/{{([^}}]+)}}/g);
if (dataVariables) {
dataVariables.forEach((dataVariable) => {
let dataVariableRef = dataVariable.replace(/[\{\}]*/gi, "");
let variableValue;
switch (dataVariableRef) {
case "columnHeader":
dataVariableRef = headerRows
? `${column}${headerRows}`
: `${column + 1}`;
// break;
default:
variableValue = getSheetCellValue(sheet[dataVariableRef]);
}
columnData = columnData.replace(dataVariable, variableValue);
});
}
if (columnData === "") {
continue;
}
rowData[columnData] = getSheetCellValue(sheet[cell]);
if (sheetData.appendData) {
extend(true, rowData, sheetData.appendData);
}
}
// removing first row i.e. 0th rows because first cell itself starts from A1
rows.shift();
// Cleaning empty if required
if (!_config.includeEmptyLines) {
rows = rows.filter((v) => v !== null && v !== undefined);
}
return rows;
};
const convertExcelToJson = function (config = {}) {
_config = config.constructor === String ? JSON.parse(config) : config;
// ignoring empty lines by default
_config.includeEmptyLines = _config.includeEmptyLines || false;
// source has to be defined and should have a value
if (!(_config.source)) {
throw new Error(":: 'source' required for _config :: ");
}
let workbook = XLSX.read(_config.source, {
type: "array",
});
let sheetsToGet =
_config.sheets && _config.sheets.constructor === Array
? _config.sheets
: Object.keys(workbook.Sheets).slice(
0,
(_config && _config.sheets && _config.sheets.numberOfSheetsToGet) ||
undefined
);
let parsedData = {};
sheetsToGet.forEach((sheet) => {
sheet =
sheet.constructor === String
? {
name: sheet,
}
: sheet;
parsedData[sheet.name] = parseSheet(sheet, workbook);
});
return parsedData;
};
return convertExcelToJson;
})();
export default excelToJson;
That is how i use it:
const convertExcelToObject = (file) => {
const reader = new FileReader();
reader.onload = function (event) {
const data = new Uint8Array(event.target.result);
let result = excelToJson({ source: data });
onUploadExcelFile(result.Transactions);
};
reader.readAsArrayBuffer(file);
};
And this is how i map it:
if (!excelData.length) {
return <div className="noFileContainer">No File Uploaded</div>;
}
const table = excelData;
const tableBody = table?.slice(1);
const tableHead = table[0];
const keys = Object.keys(tableHead);
tableBody.forEach(row => {
data.push({
transactionDate: excelDateToJson(row.A),
transactionAccount: row.B,
Category: row.C,
Item: row.D,
Amount: row.E,
transactionType: row.F,
currency: row.G
});
});
return (
<div className="displayData">
<table>
<thead>
<tr>
{keys.map((key) => (
<th>{tableHead[key]}</th>
))}
</tr>
</thead>
<tbody>
{tableBody.map((row) => (
<tr key={row.id}>
{keys.map((key) => (
<td>{row[key]}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};
The excel document comes from asp.net web api as a base64string. Thanks in advance!

React is taking way more time for execution than expected

I have tried to implement drench game. I have attached my code below. The logic is correct and it shouldn't also take so much time for execution given it is a 14x14 board. But after some clicks when the state.covered covers about quarter of the board the execution is taking way long to complete.
import './App.css';
import { useState } from 'react'
function App() {
const colors = ['aliceblue', 'tomato', 'darksalmon', 'thistle', 'olivedrab', 'steelblue']
var dp = [];
for(let i=0;i<14;i++){
let temp = []
for(let j=0;j<14;j++){
temp[j] = colors[Math.floor(Math.random() * 6)]
}
dp.push(temp)
}
var [state, setState] = useState({
boardColors: dp,
covered : [{x:0,y:0}],
})
var changeState = (color) => {
var prevcolor = state.boardColors[0][0]
var q = [...state.covered]
var append_arr = [...state.covered]
function isnotcovered(l,m,xt){
for(let i=0;i<xt.length;i++){
if(xt[i].x===l && xt[i].y===m){
return false;
}
}
return true;
}
var temp2D = [...state.boardColors];
while(q.length){
let top = q.shift()
let l = top.x;
let m = top.y;
if( m-1>0 && temp2D[l][m-1]===prevcolor && isnotcovered(l,m-1,append_arr)){
q.push({x:l,y:m-1})
append_arr.push({x:l,y:m-1})
}
if(l+1<14 && temp2D[l+1][m]===prevcolor && isnotcovered(l+1,m,append_arr)){
q.push({x:l+1,y:m})
append_arr.push({x:l+1,y:m})
}
if(m+1<14 && temp2D[l][m+1]===prevcolor && isnotcovered(l,m+1,append_arr)){
q.push({x:l,y:m+1})
append_arr.push({x:l,y:m+1})
}
if(l-1>0 && temp2D[l-1][m]===prevcolor && isnotcovered(l-1,m,append_arr)){
q.push({x:l-1,y:m})
append_arr.push({x:l-1,y:m})
}
}
setState((state) => ({boardColors : temp2D, covered : [...state.covered, ...append_arr]}) )
setState((state) => updateState(state))
function updateState(state){
function isnotcovered(l,m,xt){
for(let i=0;i<xt.length;i++){
if(xt[i].x===l && xt[i].y===m){
return false;
}
}
return true;
}
var temp2D = [...state.boardColors];
for(let i=0; i<state.covered.length; i++){
temp2D[state.covered[i].x][state.covered[i].y]=color
}
var q = [...state.covered]
var append_arr = [...state.covered]
while(q.length){
let top = q.shift()
let l = top.x;
let m = top.y;
if( m-1>0 && temp2D[l][m-1]===color && isnotcovered(l,m-1,append_arr)){
q.push({x:l,y:m-1})
append_arr.push({x:l,y:m-1})
}
if(l+1<14 && temp2D[l+1][m]===color && isnotcovered(l+1,m,append_arr)){
q.push({x:l+1,y:m})
append_arr.push({x:l+1,y:m})
}
if(m+1<14 && temp2D[l][m+1]===color && isnotcovered(l,m+1,append_arr)){
q.push({x:l,y:m+1})
append_arr.push({x:l,y:m+1})
}
if(l-1>0 && temp2D[l-1][m]===color && isnotcovered(l-1,m,append_arr)){
q.push({x:l-1,y:m})
append_arr.push({x:l-1,y:m})
}
}
return {
boardColors : temp2D,
covered : [...state.covered, ...append_arr],
}
}
}
return (
<>
<Board colors2D={state.boardColors} />
<Controls boardColors={state.boardColors} colors2D={state.boardColors} colors={colors} color='green' changeState={changeState}/>
</>
);
}
function Board(props){
var boardStyle = {
height: '280px',
aspectRatio: '1',
backgroundColor: 'lightgreen'
}
var display = props.colors2D.map((color1D, index1) => (color1D.map((color,index2) => <div key={14*index1 + index2} style={{width:'20px', aspectRatio: '1', backgroundColor: color, float: 'left'}}></div>)))
return(
<div style={boardStyle}>
{display}
</div>
)
}
function Controls(props){
var controlStyle = {
height: '200px',
width: '300px',
backgroundColor: props.color
}
var handleClick = (color) => {
props.changeState(color)
}
var buttons = props.colors.map((color, index) => <button key={index} onClick={() => handleClick(color)}>{color}</button>)
return(
<>
<div style={controlStyle}>
{buttons}
</div>
</>
)
}
export default App;
replace this code in App.js and it should work fine
I think I've found the problem by doing a console.log of your state everytime it changes:
useEffect(() => {
console.log(state);
}, [state]);
Everytime you click on a color button (even if it's the same), the displayed state shows an array as value of the covered key that gets its size multiplied by 4.
This is because you are copying the content of covered boxes in append_arr variable:
var append_arr = [...state.covered]
And when the computing is done you append the content of state.covered and the content of append_arr into state.covered:
covered : [...state.covered, ...append_arr],
This makes it at least double at every click.
You should assign only the result of append_arr as the new covered state:
covered : [...append_arr],
And the reason it actually quadruples is because you are doing this twice, with 2 functions changeState and updateState doing more or less the same thing and called each time.
You are callign setState twice. This can't be useful because the result of the first call will be applied only when the whole function finishes. You should remove one of the occurrences:
/* removing this one
setState((state) => ({boardColors : temp2D, covered : [...state.covered, ...append_arr]}) ) */
setState((state) => updateState(state))

Update array's values from an inside function scope and then return them in Js?

In this Filtered React.js component, I am using the values of an array ("foofy") to apply into a return statement for filtering a map() method. The array should be populated by entries from a Firebase database, therefor the function above filter() and map():
function Filtered () {
let foofy = []
alertMyAnsweredQuizzes()
function alertMyAnsweredQuizzes () {
var user = firebase.auth().currentUser;
if (user !== null) {
const userId = user.uid
let ref = firebase.database().ref('users (uid)/'+ userId);
ref.on("value", function(snapshot) { // it does not get out from here!
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
foofy.push(key)
});
alert(foofy)
// foofy arrays should go outside into the return statement
document.getElementById('myDiv').innerHTML += `==========`
})
}
else {
document.getElementById('myDiv').innerHTML = `sign in to view your answered quizzes`
}
}
return (
list.filter((list) =>
list.name == foofy[0] //should update by the function
|| list.name == foofy[1]
|| list.name == foofy[2]
|| list.name == foofy[3]
|| list.name == foofy[4]
|| list.name == foofy[5]
).map((list) =>
<Link>
{list.name}
</Link>
)
)
}
The thing is that I do not understand how can I get the updated array outside of its own function scope. Does anybody has an idea?
Thanks!
😊
Basically, since a network request is made the code is asynchronous. So I would suggest having a loader to show when the data is fetched and then once the data arrives from Firebase you can remove the loader and show the data. An example implementation of the above might look like this,
function Filtered () {
const [foofy, setFoofy] = React.useState([]);
const [loading, setLoading] = React.useState(true);
alertMyAnsweredQuizzes()
function alertMyAnsweredQuizzes () {
var user = firebase.auth().currentUser;
if (user !== null) {
const userId = user.uid
const ref = firebase.database().ref('users (uid)/'+ userId);
ref.on("value", (snapshot) => {
const keys = [];
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
keys.push(key);
});
setFoofy(keys);
setLoading(false);
})
}
else {
// Code for login.
}
}
if (loading) {
return <h1>Loading....</h1>
}
return (
foofy.filter((list) =>
foofy.name == foofy[0] //should update by the function
|| foofy.name == foofy[1]
|| foofy.name == foofy[2]
|| foofy.name == foofy[3]
|| foofy.name == foofy[4]
|| foofy.name == foofy[5]
).map((list) => (
<Link>
{foofy.name}
</Link>
)
)
}

React Hooks - Select Option value and name in the same method

I have the following situation where i want to get due different values to set the State ( item.id and item.name ) when i select an item in my dropdown. At the moment i can only do for 1 value ( item.id )
How can i do in the same method, is this possible?
This is the code
const [selectedValue, setSelectedValue] = useState("");
const [selectedName, setSelectedName] = useState("");
const selectValue = evt => {
const { value } = evt.target;
setSelectedValue(value);
};
<select value={selectedValue} onChange={selectValue}>
{teamsDetail && teamsDetail.length > 0
? teamsDetail.map(item => (
<option key={`${item.team_id}`} value={item.team_id}>
{item.name}
</option>
))
: null}
</select>
{selectedValue}
{selectedName} ??
The question is how can i now add the same logic also for te name value in in order to display for example the selectedName?
I provide the demo here => https://codesandbox.io/s/select-demo-u1o1k
You can get the name from teamDetail based on id
const selectValue = evt => {
const { value } = evt.target;
const item = teamsDetail.find(item => item.id == value);
setSelectedValue(value);
setSelectedName(item.name);
};
or you could get value using nativeEvent to get option text like
const selectValue = evt => {
const { value } = evt.target;
const index = evt.nativeEvent.target.selectedIndex;
const text = evt.nativeEvent.target[index].text;
setSelectedValue(value);
setSelectedName(text);
};

Shared state in React updates the rest of the components and the current one as well

I have a use case where I have 4 select components all sharing the same state. The problem is when the user selects an option from one select, the other selects should not show that option and so on. That problem is simple and solved. On doing so I am updating the shared state and hence all 4 selects are re-rendered by react and hence I am losing the selected value from the select. See attached GIF.
Can someone point out a solution where I can solve this using minimum states and less re-rendering?
Attaching the whole code below:
const SendTroops = () => {
const [planets, setPlanets] = useState([]);
const [vehicles, setVehicles] = useState([]);
const constructState = (data) => {
const arr = [];
let obj = {};
for (let i = 0; i < 4; i++) {
obj[i] = data;
}
arr.push(obj);
return arr;
};
useEffect(() => {
const getPlanets = async () => {
try {
const response = await callAPI(PLANETS.url, PLANETS.method);
const data = await response.json();
setPlanets(constructState(data));
} catch (err) {
console.log(err);
}
}
const getVehicles = async () => {
try {
const response = await callAPI(VEHICLES.url, VEHICLES.method);
const data = await response.json();
setVehicles(constructState(data));
} catch (err) {
console.log(err);
}
};
getPlanets();
getVehicles();
}, []);
const ShowInputs = (n) => {
const options = [];
for (let i = 0; i < n; i++) {
options.push((
<div className="column" key={i}>
<Select
styles="select"
name={`destination${i}`}
options={(planets && planets[0]) ? planets[0][i] : []}
onSelect={(e) => onDestinationChange(e, i)} />
<Radio
styles="radio"
name={`vehicle${i}`}
options={(vehicles && vehicles[0]) ? vehicles[0][i] : []}
onSelect={(e) => onVehicleChange(e, i)} />
</div>
))
}
return options;
}
const onDestinationChange = (e, index) => {
const selectedName = e.target.value;
const values = Object.values(planets[0]);
let obj = {};
for (let i = 0; i < 4; i++) {
if (i === index) obj[i] = values[i];
else {
obj[i] = values[i].filter((value) => value.name !== selectedName);
}
}
const updatedPlanets = [].concat([obj]);
setPlanets(updatedPlanets);
};
const onVehicleChange = (e) => {
console.log(e.target.value);
};
const onReset = (e) => {
e.preventDefault();
};
return (
< main >
<p className="search-title">Select planets for your Troops to search</p>
<form>
<div className="row">
{ShowInputs(4)}
</div>
<div className="row button-group">
<Link to='/missionreport'>
<Button styles="button" type="submit" text="Find Falcone" />
</Link>
<Button styles="button" type="reset" text="Reset" onPress={onReset} />
</div>
</form>
</main >
);
};
Just remember the index of selected planet in planets list and the index of select element where that planet has been chosen. Then show the selected option only on the select where it's been previously chosen.
That way you must add 2 more fields to your state:
Index of selected planet in planets list (let default be -1 or something negative)
Index of <select> element where it's been chosen (in your case it's the iterator i of the for loop in ShowInputs()).
Then update that fields every time when the user selects something (using onDestinationChange()).

Categories

Resources