In my React hooks I defined two functions for setting variables:
setProjectMiddleCode
and setProjectToolCode.
I hope to call this two method in my react hooks to avoid duplicate code.
I would like to do it like this:
//variable define
let data;
let index = res.data.indexOf(res.code.value);
//call dynamic
if(some state ==='A'){
data= "setProjectMiddleCode"
}else{
data = "setProjectToolCode"
}
if (index < 0) {
this[data](res.data.concat(res.code.value));
} else {
this[data](res.data.filter((_, i) => i !== index));
}
My current code:
const [projectMiddleCode, setProjectMiddleCode] = useState([]);
const [projectToolCode, setProjectToolCode] = useState([]);
const ProjectWrite = memo(({}) => {
let component;
const dispatch = useDispatch();
const [projectMiddleCode, setProjectMiddleCode] = useState([]);
const [projectToolCode, setProjectToolCode] = useState([]);
const callbackFromChild = useCallback(
res => () => {
let index = res.data.indexOf(res.code.value);
if (res.codeName === 'PROJECT_MIDDLE_CODE') {
if (index < 0) {
setProjectMiddleCode(res.data.concat(res.code.value));
} else {
setProjectMiddleCode(res.data.filter((_, i) => i !== index));
}
} else if (res.codeName === 'TOOL_LIST') {
if (index < 0) {
setProjectToolCode(res.data.concat(res.code.value));
} else {
setProjectToolCode(res.data.filter((_, i) => i !== index));
}
}
},
[]
);
One way to do this is to create a map of res.codeName to your functions:
const { codeName, code, data } = res;
const index = data.indexOf(code.value);
const funcMap = {
PROJECT_MIDDLE_CODE: setProjectMiddleCode,
TOOL_LIST: setProjectToolCode
}
const newData = index < 0 ? data.concat(code.value) : data.filter((_, i) => i !== index);
const func = funcMap[codeName];
func(newData);
Related
I have this function - to manage grid and to create it:
export default function CustomizableComponents({ direction }) {
const [rows, setRows] = useState(ComponentDidMount);
const [sortColumns, setSortColumns] = useState([]);
const [selectedRows, setSelectedRows] = useState(() => new Set());
const sortedRows = useMemo(() => {
if (sortColumns.length === 0) return rows;
return [...rows].sort((a, b) => {
for (const sort of sortColumns) {
const comparator = getComparator(sort.columnKey);
const compResult = comparator(a, b);
if (compResult !== 0) {
return sort.direction === 'ASC' ? compResult : -compResult;
}
}
return 0;
});
}, [rows, sortColumns]);
return (
<DataGrid
className="fill-grid"
columns={columns}
rows={sortedRows}
sortColumns={sortColumns}
onSortColumnsChange={setSortColumns}
selectedRows={selectedRows}
onSelectedRowsChange={setSelectedRows}
renderers={{ sortStatus, checkboxFormatter }}
direction={direction}
/>
);
}
And, I have this function (to get the data from server):
async function ComponentDidMount() {
// GET request using fetch with async/await
const response = await fetch('https://localhost:7198/api/FilesDetails/GetListDetails');
const data = await response.json();
for (let i = 1; i < 10; i++) {
rows.push({
id: data[i].name,
name: data[i].name,
startTime: data[i].name,
endTime: data[i].name
});
}
return rows;
}
It reaches the function, and fills the rows,
But the datagrid remains empty and does not fill.
Where am I wrong?
Thanks
I've set up a new project with React, Redux (using toolkit). I've got a button that needs to be disabled if the user does not have enough of a particular resource. I've confirmed that the state is being updated properly and the reducers are applying to state properly, but I am unable to get the button to disable when that resource falls below the supplied price.
I've tried duplicating state from redux using a useState hook, but setting the state within canAfford() still doesn't disable the button. I'm at a bit of a loss, and feel like I'm just missing something about redux state and rendering.
Here's the button component I'm working with:
function BuyBtn({ technology, label, resourceType, price, requirements = []}: IBuyBtn) {
const units = useSelector((state: any) => state.units);
const tech = useSelector((state: any) => state.tech);
const resources = useSelector((state: any) => state.resources);
const dispatch = useDispatch();
let disabled = false;
let unlocked = true;
useEffect(() => {
disabled = !canAfford()
}, [resources])
const canAfford = (): boolean => {
console.log('Units:', units);
console.log("Checking affordability");
if (resourceType.length != price.length) {
throw `BuyBtn Error: price length is ${price.length} but resource length is ${resourceType.length}.`;
}
resourceType.forEach((res, i) => {
const resPrice = price[i];
if (resources[res] < resPrice) {
return false;
}
});
return true;
};
const meetsRequirements = (): boolean => {
if (requirements.length === 0) {
return true;
}
requirements.forEach((req) => {
if (!tech[req]) {
return false;
}
});
return true;
};
const buyThing = () => {
if (canAfford() && meetsRequirements()) {
resourceType.forEach((res, i) => {
const resPrice = price[i];
dispatch(SubtractResource(res, resPrice));
});
dispatch(UnlockTech(technology, true))
}
};
if (meetsRequirements() && canAfford()) {
return (
<button onClick={buyThing} disabled={disabled}>{label}</button>
);
} else {
return null;
}
}
export default BuyBtn;
Instead of using disabled as variable make it State which will trigger re-render:
function BuyBtn({ technology, label, resourceType, price, requirements = []}: IBuyBtn) {
const units = useSelector((state: any) => state.units);
const tech = useSelector((state: any) => state.tech);
const resources = useSelector((state: any) => state.resources);
const dispatch = useDispatch();
const [disabled, setDisabled] = React.useState(false);
let unlocked = true;
const canAfford = (): boolean => {
console.log('Units:', units);
console.log("Checking affordability");
if (resourceType.length != price.length) {
throw `BuyBtn Error: price length is ${price.length} but resource length is ${resourceType.length}.`;
}
let isAffordable = true
resourceType.forEach((res, i) => {
const resPrice = price[i];
if (resources[res] < resPrice) {
isAffordable = false;
}
});
return isAffordable;
};
useEffect(async() => {
const value = await canAfford();
setDisabled(!value);
}, [resources])
const meetsRequirements = (): boolean => {
if (requirements.length === 0) {
return true;
}
let isMeetingRequirements = true;
requirements.forEach((req) => {
if (!tech[req]) {
isMeetingRequirements = false;
}
});
return isMeetingRequirements;
};
const buyThing = () => {
if (canAfford() && meetsRequirements()) {
resourceType.forEach((res, i) => {
const resPrice = price[i];
dispatch(SubtractResource(res, resPrice));
});
dispatch(UnlockTech(technology, true))
}
};
if (meetsRequirements() && canAfford()) {
return (
<button onClick={buyThing} disabled={disabled}>{label}</button>
);
} else {
return null;
}
}
export default BuyBtn;
For this project I am currently working on, I need to highlight the button that was clicked on each layer/row. However, the way I have right now it highlights every button that was clicked.
I need something like this:
correct highlighted path
But then when I click on the same row, it does not remove the highlight from the button that I pressed before. How can I update and reset the state of the previous button that was clicked? I tried to use the useRef hook for this but I haven't been successful so far.
wrong highlighted path
EDIT: Added code
This is the code that I have for the component of each row in the website:
function StackRow({ data, partition, level, index, onClick, getInfo, isApp }) {
const classes = useStyles({ level: level });
const rowRef = useRef();
const handleSelectedButtons = (flag, setFlag, btnRef) => {
console.log(rowRef);
};
return (
<Card
key={partition + '_' + index + '_' + level}
className={classes.card}
id={level}
ref={rowRef}
>
{data.map((field) => {
return (
<StackItem
key={partition + '_' + field[0] + '_' + level}
data={field[0]}
info={field[1]}
level={level}
onClick={onClick}
getInfo={getInfo}
isApp={isApp}
handleSelectedButtons={handleSelectedButtons}
rowRef={rowRef}
/>
);
})}
</Card>
);
}
And this is the code I have for each button of the row:
function StackItem({
data,
info,
level,
onClick,
getInfo,
isApp,
handleSelectedButtons,
}) {
const [flag, setFlag] = useState(false);
const btnRef = useRef();
const styleProps = {
backgroundColor: flag ? '#06d6a0' : level % 2 === 0 ? '#22223b' : '#335c67',
};
const classes = useStyles(styleProps);
return (
<Button
ref={btnRef}
isselected={flag.toString()}
key={data}
className={classes.button}
variant="outlined"
onClick={(event) => {
onClick(event, setFlag, btnRef);
handleSelectedButtons(flag, setFlag, btnRef);
getInfo(info, level, isApp);
}}
disableElevation={true}
>
{data}
</Button>
);
}
There are some useless variables and states there because I have been trying all sort of stuff to do this.
EDIT: Added data sample & project structure
Data looks like:
{
application: {
cmake: {
info: str,
versions: {
version_no: {
application: {...}
}
}
},
gcc: {...},
git: {...},
intel: {...},
.
.
.
}
}
The structure of the project is like:
App
L Stack
L StackRow
L StackItem
Where App is the entire application, Stack is the container for everything in the images apart from the search box, StackRow matches one row of the Stack, and StackItem is one item/button from the StackRow.
EDIT: Added Stack component
function Stack({ data, partition, getInfo }) {
const [level, setLevel] = useState(0);
const [cards, setCards] = useState([]);
const [isApp, setIsApp] = useState(true);
const [selected, setSelected] = useState([]);
const [prevLevel, setPrevLevel] = useState(-1);
const cardsRef = useRef();
const handleClick = (event, setFlag, btnRef) => {
let rows = cardsRef.current.childNodes;
let currBtn = event.target.innerText;
let curr;
for (let i = 0; i < rows.length; i++) {
let rowItems = rows[i].childNodes;
for (let j = 0; j < rowItems.length; j++) {
if (currBtn === rowItems[j].textContent) {
curr = rowItems[j].parentElement;
}
}
}
let id;
for (let i = 0; i < rows.length; i++) {
if (curr.textContent === rows[i].textContent) {
id = i;
}
}
if (level === id) {
if (id % 2 === 0) {
setIsApp(true);
if (selected.length === 0) {
setSelected([...selected, data[currBtn].versions]);
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(level + 1);
} else {
let newSelected = selected.slice(0, id);
if (id % 2 === 0) {
setIsApp(true);
if (newSelected.length === 0) {
setSelected([...newSelected, data[currBtn].versions]);
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(id + 1);
}
setFlag(true);
};
useEffect(() => {
let fields = [];
let lastSelected = selected[selected.length - 1];
if (level % 2 !== 0) {
fields = Object.keys(lastSelected).map((key) => {
let path = lastSelected[key].path;
let module = lastSelected[key].module_name;
let info = 'module: ' + module + ' path: ' + path;
return [key, info];
});
} else {
if (selected.length !== 0)
fields = Object.keys(lastSelected).map((key) => {
let info = lastSelected[key].info;
return [key, info];
});
}
if (fields.length > 0) {
if (level > prevLevel) {
setCards((prevState) => [...prevState, fields]);
} else {
setCards((prevState) => [
...prevState.slice(0, selected.length),
fields,
]);
}
}
}, [selected, level, prevLevel]);
useEffect(() => {
let fields = Object.keys(data).map((key) => {
let info = data[key].info;
return [key, info];
});
setCards([fields]);
setLevel(0);
}, [data]);
useEffect(() => {
setLevel(0);
setPrevLevel(-1);
setSelected([]);
}, [partition]);
if (cards) {
return (
<div ref={cardsRef}>
{cards.map((card, index) => (
<StackRow
data={card}
partition={partition}
level={index}
index={cards.indexOf(card)}
onClick={handleClick}
getInfo={getInfo}
isApp={isApp}
/>
))}
</div>
);
} else return null;
}
EDIT: Added data sample
{
cmake: {
info: "A cross-platform, open-source build system. CMake is a family of tools designed to build, test and package software.",
versions: {
"3.17.3": {
child: {},
module_name: "cmake/3.17.3",
path: "/opt/apps/nfs/spack/var/spack/environments/matador/modules/linux-centos8-x86_64/Core/cmake/3.17.3.lua",
version_no: "3.17.3"
}
}
},
gcc: {
info: "...",
versions: {
"8.4.0": {
child: {
cmake: {...},
cuda: {...},
cudnn: {...},
openmpi: {...},
.
.
.
},
module_name: "...",
path: "...",
version_no: "..."
}
"9.3.0": {...},
"10.1.0": {...}
}
}
}
I'm not sure how to convert this section to fit into a useEffect. I can't pull the prevProps conditional out since it should only run within the loop. I dont think I can just add the props to dependency array either, as I need to do something else whenever selectedCurrency does not change.
public componentDidUpdate(prevProps: Readonly<Props>): void {
if (api != null) {
const MyData: {}[] = [];
api.forEach(el=> {
if (!el.data) {
return;
}
if (selectedCurrency === "") {
el.setDataValue("val1", "-");
el.setDataValue("val2", "-");
el.setDataValue("val3", "-");
el.setDataValue("val4", "-");
} else {
const originalCcy = el.data.Currency;
const exchangeRate = referenceCurrencies
.filter(x => x.originalCurrency === originalCcy)
.flatMap(value => value.conversionCurrencies)
.find(value => value.currency === selectedCurrency);
const middleMarketRate = exchangeRate ? exchangeRate.middleMarketRate : 1;
el.setDataValue("val2", el.data.val2 * middleMarketRate);
el.setDataValue(
"val3",
el.data.val3 * middleMarketRate
);
el.setDataValue("val1", middleMarketRate);
if (
prevProps.dateFrom === dateFrom &&
prevProps.dateTo === dateTo &&
prevProps.selectedCurrency !== selectedCurrency
) {
const dateToMoment = moment(dateTo);
const dateFromMoment = moment(dateFrom);
const totalValBalCols = dateToMoment.diff(dateFromMoment, "days");
for (let i = 0; i <= totalValBalCols; i += 1) {
const dayDiff = i;
const currentDate = moment(dateFrom)
.add(dayDiff, "days")
.format("DD-MMM-YYYY");
el.setDataValue(
"refCol",
el.data[currentDate]
);
}
} else {
MyData.push(el.data);
}
}
});
}
}
You can use useRef() to hold the previous props, with a 2nd useEffect() to update preProps.current after you check if anything changed.
Example (not tested):
const prevProps = useRef();
useEffect(() => {
// skip the effect since this is the initial render
if(!prevProps.current) return;
api?.forEach(el => {
if (!el.data) return;
if (selectedCurrency === "") {
// Do something
return;
}
if (
prevProps.current.dateFrom === dateFrom &&
prevProps.current.dateTo === dateTo &&
prevProps.current.selectedCurrency !== selectedCurrency
) {
//Do something
return;
}
//Do something else
});
}, [api, dateFrom, dateTo, selectedCurrency]);
useEffect(() => { prevProps.current = props; })
i am struggling in clearInterval function for typescript in react, don't know why it is not clearing interval, for that i have defined variable let interval_counter; and this variable i using as this interval_counter = setInterval(interval,1000); , but when i used clearInterval(interval_counter);, it is not working for me, here i have put my whole code, can anyone please look my code and help me to resolve this issue ? any help will be really appreciated.
interface Props {
gameId: string;
}
let interval_counter;
export const GameInner: React.FC<Props> = (
{
gameId,
}
) => {
const gameData = useDataStore(GameDataStore);
const userData = useDataStore(UserDataStore);
const chatData = useDataStore(ChatDataStore);
const params = useParams<{ throwaway?: string }>();
const history = useHistory();
const [updateShowTimer, setUpdateShowTimer] = React.useState('02:00');
const [isCalled, setIsCalled] = React.useState<any>('0');
let setSeconds = 30;
//const interval_counter = React.useRef<any>();
//React.createRef<any>();
const {
dateCreated,
started,
chooserGuid,
ownerGuid,
spectators,
pendingPlayers,
players,
settings,
kickedPlayers
} = gameData.game ?? {};
const {
playerGuid
} = userData;
const iWasKicked = !!kickedPlayers?.[playerGuid];
const amInGame = playerGuid in (players ?? {});
const skipPlayer = (game_string_id: any, target_turn: any, chooserGuid: any) => {
return GameDataStore.skipPlayer(game_string_id, target_turn, chooserGuid);
}
const interval = () => {
let timer = setSeconds, minutes, seconds;
let chooserGuid = localStorage.getItem('chooserGuid');
let game_string_id = localStorage.getItem('game_id');
let target_turn = localStorage.getItem('target_turn');
let is_called = localStorage.getItem('is_called');
if (typeof timer !== undefined && timer != null) {
minutes = parseInt(timer / 60 as any, 10);
seconds = parseInt(timer % 60 as any, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
//console.log("test");
console.log(minutes + ":" + seconds);
setUpdateShowTimer(minutes+":"+seconds);
if (timer == 0) {
if(gameData?.game?.roundStarted) {
} else {
skipPlayer(game_string_id, target_turn, chooserGuid);
}
clearInterval(interval_counter);
}
if (--timer < 0) {
clearInterval(interval_counter);
}
setSeconds -= 1;
}
}
const startTimer = () => {
console.log("called again");
interval_counter = setInterval(interval,1000);
}
const isOwner = ownerGuid === userData.playerGuid;
const isChooser = playerGuid === chooserGuid;
const amSpectating = playerGuid in { ...(spectators ?? {}), ...(pendingPlayers ?? {}) };
const playerGuids = Object.keys(players ?? {});
const roundsToWin = getTrueRoundsToWin(gameData.game as ClientGameItem);
const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);
const inviteLink = (settings?.inviteLink?.length ?? 0) > 25
? `${settings?.inviteLink?.substr(0, 25)}...`
: settings?.inviteLink;
const meKicked = kickedPlayers?.[playerGuid];
const tablet = useMediaQuery('(max-width:1200px)');
const canChat = (amInGame || amSpectating) && moment(dateCreated).isAfter(moment(new Date(1589260798170)));
const chatBarExpanded = chatData.sidebarOpen && !tablet && canChat;
/**********************************************/
if(gameData?.game?.players && gameData?.game?.id) {
let game_id = gameData.game.id;
let all_players = gameData.game.players;
let all_player_id = Object.keys(all_players);
let filteredAry = all_player_id.filter(e => e !== userData.playerGuid);
console.log("user player guid:"+userData.playerGuid);
console.log("guid:"+chooserGuid);
console.log("all players:"+all_player_id);
console.log("new array:"+filteredAry);
let target_item = filteredAry.find((_, i, ar) => Math.random() < 1 / (ar.length - i));
if(typeof target_item !== undefined && target_item!=null) {
localStorage.setItem('target_turn',target_item);
}
localStorage.setItem('is_started','0');
if(typeof game_id !== undefined && game_id!=null) {
localStorage.setItem('game_id',game_id);
}
if(typeof chooserGuid !== undefined && chooserGuid!=null) {
localStorage.setItem('chooserGuid',chooserGuid);
}
if(isChooser) {
if(isCalled == '0') {
setIsCalled("1");
startTimer();
}
} else {
clearInterval(interval_counter);
}
if(gameData?.game?.roundStarted) {
console.log("round is started");
clearInterval(interval_counter);
}
}
/********************************************/
return (
<div style={{ maxWidth: chatBarExpanded ? "calc(100% - 320px)" : "100%" }}>
</div>
);
};
I believe your problem will be solved when you store your interval also in state.
const [interval, setInterval] = useState(null as NodeJS.Timeout | null);