In weights.js I have:
const Weights = ({ data, country, listItemUser, gear }) => {
const newarr = [];
data?.map(el => {
el?.gearHiking?.map(els => {
newarr?.push(els)
})
})
const newarr2 = [];
data?.map(el => {
el?.foodHiking?.map(els => {
newarr2?.push(els)
})
})
const getcamper4x4wei = useMemo(() => {
let initialValue = 0
if (country?.label == "4x4 TOWING CAMPER") {
const gettata = data.filter(el => el.packingLocation == "4x4" ? true : false)
return gettata?.reduce(
(previousValue, currentValue) => previousValue + Number(currentValue.Weight) * Number(currentValue.Quantity)
, initialValue
)
}
}, [data]);
}
export default Weights
and then I import this file in app.js as:
import Weights from './weights'
In app.js how do I access the const of getcamper4x4wei?
Add a return statement to your Weight function:
return getcamper4x4wei;
Then, in app.js, you can access its value - but be aware that Weight now returns a state variable that can change at runtime, so you should use it in useEffect:
// in app.js
useEffect(()=>console.log("Weight changed: ", Weight), [Weight]);
Related
What I am trying to do is to update the reset the countdown after changing the status.
There are three status that i am fetching from API .. future, live and expired
If API is returning future with a timestamp, this timestamp is the start_time of the auction, but if the status is live then the timestamp is the end_time of the auction.
So in the following code I am calling api in useEffect to fetch initial data pass to the Countdown and it works, but on 1st complete in handleRenderer i am checking its status and updating the auctionStatus while useEffect is checking the updates to recall API for new timestamp .. so far its working and 2nd timestamp showed up but it is stopped ... means not counting down time for 2nd time.
import React, { useEffect } from 'react';
import { atom, useAtom } from 'jotai';
import { startTimeAtom, auctionStatusAtom } from '../../atoms';
import { toLocalDateTime } from '../../utility';
import Countdown from 'react-countdown';
import { getCurrentAuctionStatus } from '../../services/api';
async function getAuctionStatus() {
let response = await getCurrentAuctionStatus(WpaReactUi.auction_id);
return await response.payload();
}
const Counter = () => {
// component states
const [startTime, setStartTime] = useAtom(startTimeAtom);
const [auctionStatus, setAuctionStatus] = useAtom(auctionStatusAtom);
useEffect(() => {
getAuctionStatus().then((response) => {
setAuctionStatus(response.status);
setStartTime(toLocalDateTime(response.end_time, WpaReactUi.time_zone));
});
}, [auctionStatus]);
//
const handleRenderer = ({ completed, formatted }) => {
if (completed) {
console.log("auction status now is:", auctionStatus);
setTimeout(() => {
if (auctionStatus === 'future') {
getAuctionStatus().then((response) => {
setAuctionStatus(response.status);
});
}
}, 2000)
}
return Object.keys(formatted).map((key) => {
return (
<div key={`${key}`} className={`countDown bordered ${key}-box`}>
<span className={`num item ${key}`}>{formatted[key]}</span>
<span>{key}</span>
</div>
);
});
};
console.log('starttime now:', startTime);
return (
startTime && (
<div className="bidAuctionCounterContainer">
<div className="bidAuctionCounterInner">
<Countdown
key={auctionStatus}
autoStart={true}
id="bidAuctioncounter"
date={startTime}
intervalDelay={0}
precision={3}
renderer={handleRenderer}
/>
</div>
</div>
)
);
};
export default Counter;
You use auctionStatus as a dependency for useEffect.
And when response.status is the same, the auctionStatus doesn't change, so your useEffect won't be called again.
For answering your comment on how to resolve the issue..
I am not sure of your logic but I'll explain by this simple example.
export function App() {
// set state to 'live' by default
const [auctionStatus, setAuctionStatus] = React.useState("live")
React.useEffect(() => {
console.log('hello')
changeState()
}, [auctionStatus])
function changeState() {
// This line won't result in calling your useEffect
// setAuctionStatus("live") // 'hello' will be printed one time only.
// You need to use a state value that won't be similar to the previous one.
setAuctionStatus("inactive") // useEffect will be called and 'hello' will be printed twice.
}
}
You can simply use a flag instead that will keep on changing from true to false like this:
const [flag, setFlag] = React.useState(true)
useEffect(() => {
// ..
}, [flag])
// And in handleRenderer
getAuctionStatus().then((response) => {
setFlag(!flag);
});
Have a look at the following useCountdown hook:
https://codepen.io/AdamMorsi/pen/eYMpxOQ
const DEFAULT_TIME_IN_SECONDS = 60;
const useCountdown = ({ initialCounter, callback }) => {
const _initialCounter = initialCounter ?? DEFAULT_TIME_IN_SECONDS,
[resume, setResume] = useState(0),
[counter, setCounter] = useState(_initialCounter),
initial = useRef(_initialCounter),
intervalRef = useRef(null),
[isPause, setIsPause] = useState(false),
isStopBtnDisabled = counter === 0,
isPauseBtnDisabled = isPause || counter === 0,
isResumeBtnDisabled = !isPause;
const stopCounter = useCallback(() => {
clearInterval(intervalRef.current);
setCounter(0);
setIsPause(false);
}, []);
const startCounter = useCallback(
(seconds = initial.current) => {
intervalRef.current = setInterval(() => {
const newCounter = seconds--;
if (newCounter >= 0) {
setCounter(newCounter);
callback && callback(newCounter);
} else {
stopCounter();
}
}, 1000);
},
[stopCounter]
);
const pauseCounter = () => {
setResume(counter);
setIsPause(true);
clearInterval(intervalRef.current);
};
const resumeCounter = () => {
setResume(0);
setIsPause(false);
};
const resetCounter = useCallback(() => {
if (intervalRef.current) {
stopCounter();
}
setCounter(initial.current);
startCounter(initial.current - 1);
}, [startCounter, stopCounter]);
useEffect(() => {
resetCounter();
}, [resetCounter]);
useEffect(() => {
return () => {
stopCounter();
};
}, [stopCounter]);
return [
counter,
resetCounter,
stopCounter,
pauseCounter,
resumeCounter,
isStopBtnDisabled,
isPauseBtnDisabled,
isResumeBtnDisabled,
];
};
I have this component:
const updateUrl = (url: string) => history.replaceState(null, '', url);
// TODO: Rename this one to account transactions ATT: #dmuneras
const AccountStatement: FC = () => {
const location = useLocation();
const navigate = useNavigate();
const { virtual_account_number: accountNumber, '*': transactionPath } =
useParams();
const [pagination, setPagination] = useState<PaginatorProps>();
const [goingToInvidualTransaction, setGoingToInvidualTransaction] =
useState<boolean>(false);
const SINGLE_TRANSACTION_PATH_PREFIX = 'transactions/';
// TODO: This one feels fragile, just respecting what I found, but, we could
// investigate if we can jsut rely on the normal routing. ATT. #dmuneras
const transactionId = transactionPath?.replace(
SINGLE_TRANSACTION_PATH_PREFIX,
''
);
const isFirst = useIsFirstRender();
useEffect(() => {
setGoingToInvidualTransaction(!!transactionId);
}, [isFirst]);
const {
state,
queryParams,
dispatch,
reset,
setCursorAfter,
setCursorBefore
} = useLocalState({
cursorAfter: transactionId,
includeCursor: !!transactionId
});
const {
filters,
queryParams: globalQueryParams,
setDateRange
} = useGlobalFilters();
useUpdateEffect(() => {
updateUrl(
`${location.pathname}?${prepareSearchParams(location.search, {
...queryParams,
...globalQueryParams
}).toString()}`
);
}, [transactionId, queryParams]);
useUpdateEffect(() => dispatch(reset()), [globalQueryParams]);
const account_number = accountNumber;
const requestParams = accountsStateToParams({
account_number,
...state,
...filters
});
const { data, isFetching, error, isSuccess } =
useFetchAccountStatementQuery(requestParams);
const virtualAccountTransactions = data && data.data ? data.data : [];
const nextPage = () => {
dispatch(setCursorAfter(data.meta.cursor_next));
};
const prevPage = () => {
dispatch(setCursorBefore(data.meta.cursor_prev));
};
const onRowClick = (_event: React.MouseEvent<HTMLElement>, rowData: any) => {
if (rowData.reference) {
if (rowData.id == transactionId) {
navigate('.');
} else {
const queryParams = prepareSearchParams('', {
reference: rowData.reference,
type: rowData.entry_type,
...globalQueryParams
});
navigate(
`${SINGLE_TRANSACTION_PATH_PREFIX}${rowData.id}?${queryParams}`
);
}
}
};
const checkIfDisabled = (rowData: TransactionData): boolean => {
return !rowData.reference;
};
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [data?.meta]);
const showTransactionsTable: boolean =
Array.isArray(virtualAccountTransactions) && isSuccess && data?.data;
const onTransactionSourceLoaded = (
transactionSourceData: PayoutDetailData
) => {
const isIncludedInPage: boolean = virtualAccountTransactions.some(
(transaction: TransactionData) => {
if (transactionId) {
return transaction.id === parseInt(transactionId, 10);
}
return false;
}
);
if (!goingToInvidualTransaction || isIncludedInPage) {
return;
}
const fromDate = dayjs(transactionSourceData.timestamp);
const toDate = fromDate.clone().add(30, 'day');
setDateRange({
type: 'custom',
to: toDate.format(dateFormat),
from: fromDate.format(dateFormat)
});
setGoingToInvidualTransaction(false);
};
const fromDate = requestParams.created_after || dayjs().format('YYYY-MM-DD');
const toDate = requestParams.created_before || dayjs().format('YYYY-MM-DD');
const routes = [
{
index: true,
element: (
<BalanceWidget
virtualAccountNumber={account_number}
fromDate={fromDate}
toDate={toDate}
/>
)
},
{
path: `${SINGLE_TRANSACTION_PATH_PREFIX}:transaction_id`,
element: (
<TransactionDetails
onTransactionSourceLoaded={onTransactionSourceLoaded}
/>
)
}
];
return (........
I get this error: Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
The useEffect where the issue is, it is this one:
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [data?.meta]);
Considering previous answers, would the solution be to make sure I return a new object each time? But I am not sure what would be the best approach. Any clues ?
did you want the useEffect to start every changes of 'data?.meta' ?
Without reading all the code, I believe the data.meta object changes on every render. There is a way to change the useEffect to narrow done its execution conditions:
useEffect(() => {
if (data?.meta) {
setPagination({
showPrev: data.meta.has_previous_page,
showNext: data.meta.has_next_page
});
}
}, [!data?.meta, data?.meta?.has_previous_page, data?.meta?.has_next_page]);
Please note the ! before data.?.meta which makes the hook test only for presence or absence of the object, since your code doesn't need more than that information.
When I am trying to get data from an array using filter and find, it's not getting filtered also the _ids are the same when I cross-checked the array, also useState is also not updating
1. How should I filter one element from an array, Am I doing this right?
2. useState is not working, not updating data
I am getting every data from context (c1)
sd is returning array of single object, so to get one first index I am returning sd[0]
const ReadTemplate = (props) => {
const c1 = useContext(PostsContext);
const [first, myData] = useState({});
const first_load_func = () => {
const id = props.match.params.id;
const sd = c1.data.filter((c1) => id === c1._id);
const business_props = c1.business_data.filter((c1) => id === c1._id);
const startups_props = c1.startups_data.filter((c1) => id === c1._id);
const tech_props = c1.tech_data.filter((c1) => id === c1._id);
const sports_props = c1.sports_data.filter((c1) => id === c1._id);
if (sd) {
return sd[0];
} else if (business_props) {
return business_props[0];
} else if (startups_props) {
return startups_props[0];
} else if (tech_props) {
return tech_props[0];
} else if (sports_props) {
return sports_props[0];
} else {
return <MyAlert />;
}
};
const func = (data) => {
if (data) {
setTimeout(() => {
myData(data);
}, 1000);
console.log('ye first hai');
console.log(first._id);
console.log('ye data hai');
console.log(data);
} else {
console.log('No');
}
};
useEffect(() => {
first_load_func();
func(first_load_func());
}, [first]);
return (
<>
<PostDesign props={first} />
</>
);
};
export default ReadTemplate;
My guess from your code is that you should assign the filtered data when the component is rendered, not when first changes:
useEffect(() => {
func(first_load_func());
}, []);
It may be useful to convert ids toString() before comparing them:
const sd = c1.data.filter((c1) => id.toString() === c1._id.toString());
I need to use a quantity for my cart, I'm learning react, but I get this warning: Warning: Received NaN for the children attribute. If this is expected, cast the value to a string.
import { createContext, useState, useContext, useEffect } from "react";
export const CartContext = createContext([]);
export const CartProvider = ({children}) => {
const [cart, setCart] = useState([]);
//const cantidadProductos = cart.length;
const [cartQuantity, setCartQuantity] = useState(0);
useEffect(() => {
const getQuantity = () => {
let cantidadTotalProductos = 0;
cart.forEach((orden)=>{
cantidadTotalProductos += Number(orden.cantidadTotalProductos);
});
setCartQuantity(cantidadTotalProductos);
}
getQuantity();
}, [cart]);
const addItem = (item, cantidad) => {
const newItem = { item, cantidad };
const itemEsta = cart.find((order) => order.item.id === item.id);
if(itemEsta){
const actualizarCarrito = cart.map((order) => {
if(order.item.id === item.id){
return {...order, cantidad: cantidad + order.cantidad};
}else{
return order;
}
});
setCart(actualizarCarrito);
}else{
setCart((prevState) => [...prevState, newItem]);
}
};
const removeItem = (id) => {
setCart((prev) => prev.filter((element) => element.item.id !== id));
};
const clearAll = () => {
setCart([]);
};
return (
<CartContext.Provider value={{ cart, addItem, removeItem, clearAll, cartQuantity }}>
{children}
</CartContext.Provider>
);
};
export const useCart = () => useContext(CartContext);
And then in the component:
import './Carrito.css';
import { useCart } from "../context/CartContext";
function Carrito () {
const { cartQuantity } = useCart();
return <div className="editarCarrito">
<p className="carritoCantidad">{ cartQuantity }</p>
</div>
}
export default Carrito;
Issue
The code is doing an arithmetic operation with a non-number type, and once this occurs the result is Nan and by definition any further arithmetic operations also always result in NaN.
useEffect(() => {
const getQuantity = () => {
let cantidadTotalProductos = 0;
cart.forEach((orden)=>{
cantidadTotalProductos += Number(orden.cantidadTotalProductos); // <-- cantidadTotalProductos is undefined
});
setCartQuantity(cantidadTotalProductos);
}
getQuantity();
}, [cart]);
const addItem = (item, cantidad) => {
const newItem = { item, cantidad }; // <-- no cantidadTotalProductos property
const itemEsta = cart.find((order) => order.item.id === item.id);
if (itemEsta) {
const actualizarCarrito = cart.map((order) => {
if (order.item.id === item.id) {
return { ...order, cantidad: cantidad + order.cantidad };
} else {
return order;
}
});
setCart(actualizarCarrito);
} else {
setCart((prevState) => [...prevState, newItem]);
}
};
Solution
I think cantidad is the quantity value you are wanting to sum over. Provide a fallback value in case Number(cantidad) isn't a number.
useEffect(() => {
const cantidadTotalProductos = cart.reduce(
(total, { cantidad }) => total + (Number(cantidad) || 0),
0
);
setCartQuantity(cantidadTotalProductos);
}, [cart]);
Suggestion
The cart item total quantity is what is considered derived state since it's easily computable from the actual cart state, and really shouldn't also be stored in state. Compute it in the render result as part of the context value.
Identify The Minimal (but complete) Representation of UI State
Let’s go through each one and figure out which one is state. Ask three
questions about each piece of data:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
Example:
const cartQuantity = cart.reduce(
(total, { cantidad }) => total + (Number(cantidad) || 0),
0
);
return (
<CartContext.Provider
value={{ cart, addItem, removeItem, clearAll, cartQuantity }}
>
{children}
</CartContext.Provider>
);
My goal is to use the button on the page to open a websocket connection, subscribe to the ticker feed, then update each list item's price based on the ID of the list item. Currently I have a list that maps through the initial API call's response and saves each object's ID to an array, which in turn is used to build a <li> for each ID. This creates 96 list items. I have also gotten the price to update live via a <p> element in each <li>.
I am having trouble targeting the price for just the matching row ID to the incoming data object's ID so that only that matching row is re-rendered when it gets a match. Below is my code:
ProductRow.js
import React from 'react';
export default function ProductRow(props) {
return <li key={props.id}><p>{ props.id }</p><p>{props.price}</p></li>;
}
WatchList.js
import React, { useState, useEffect, useRef } from "react";
import { Button } from 'react-bootstrap';
import ProductRow from "./ProductRow";
export default function WatchList() {
const [currencies, setcurrencies] = useState([]);
const product_ids = currencies.map((cur) => cur.id);
const [price, setprice] = useState("0.00");
const [isToggle, setToggle] = useState();
const ws = useRef(null);
let first = useRef(false);
const url = "https://api.pro.coinbase.com";
useEffect(() => {
ws.current = new WebSocket("wss://ws-feed.pro.coinbase.com");
let pairs = [];
const apiCall = async () => {
await fetch(url + "/products")
.then((res) => res.json())
.then((data) => (pairs = data));
let filtered = pairs.filter((pair) => {
if (pair.quote_currency === "USD") {
return pair;
}
});
filtered = filtered.sort((a, b) => {
if (a.base_currency < b.base_currency) {
return -1;
}
if (a.base_currency > b.base_currency) {
return 1;
}
return 0;
});
setcurrencies(filtered);
first.current = true;
};
apiCall();
}, []);
useEffect(() => {
ws.current.onmessage = (e) => {
if (!first.current) {
return;
}
let data = JSON.parse(e.data);
if (data.type !== "ticker") {
return;
}
setprice(data.price);
console.log(data.product_id, price);
};
}, [price]);
const handleToggleClick = (e) => {
if (!isToggle) {
let msg = {
type: "subscribe",
product_ids: product_ids,
channels: ["ticker"]
};
let jsonMsg = JSON.stringify(msg);
ws.current.send(jsonMsg);
setToggle(true);
console.log('Toggled On');
}
else {
let unsubMsg = {
type: "unsubscribe",
product_ids: product_ids,
channels: ["ticker"]
};
let unsub = JSON.stringify(unsubMsg);
ws.current.send(unsub);
setToggle(false);
console.log('Toggled Off');
}
};
return (
<div className="container">
<Button onClick={handleToggleClick}><p className="mb-0">Toggle</p></Button>
<ul>
{currencies.map((cur) => {
return <ProductRow id={cur.id} price={price}></ProductRow>
})}
</ul>
</div>
);
}
App.js
import React from "react";
import WatchList from "./components/Watchlist";
import "./scss/App.scss";
export default class App extends React.Component {
render() {
return (
<WatchList></WatchList>
)
}
}
Initialize the price state to be an empty object i.e. {}. We'll refer the price values by the the product_id on getting the response from websocket
// Initialize to empty object
const [price, setprice] = useState({});
...
// Refer and update/add the price by the product_id
useEffect(() => {
ws.current.onmessage = (e) => {
if (!first.current) {
return;
}
let data = JSON.parse(e.data);
if (data.type !== "ticker") {
return;
}
// setprice(data.price)
setprice(prev => ({ ...prev, [data.product_id]: data.price}));
console.log(data.product_id, price);
};
}, [price]);
Render your ProductRows as
<ul>
{currencies.map((cur) => {
return <ProductRow key={cur.id} id={cur.id} price={price[cur.id]}></ProductRow>
})}
</ul>
You don't have to manage anykind of sorting or searching for the relevant prices for products.