I would like to set the selectedSubscriber value with first item from subscriberOptions array (subscriberOptions[0].value). What is the best way to do it?
const defaultFormInput = {
subscriberOptions: [],
selectedSubscriber: "",
subscriberOptionsIsLoading: true,
};
const [formInput, setFormInput] = useState(defaultFormInput);
useEffect(() => {
//load subscribers from API
async function loadSubscribers() {
const response = await fetch("https://myapi/subscribers");
const body = await response.json();
setFormInput({
...formInput,
subscriberOptions: body.map((x) => ({ value: x.id, name: x.name })),
subscriberOptionsIsLoading: false,
});
// not working
//setFormInput({
// ...formInput,
// selectedSubscriber: formInput.subscriberOptions[0].value,
//});
}
loadSubscribers();
}, []);
JSX
<Select
disabled={formInput.subscriberOptionsIsLoading}
value={formInput.selectedSubscriber}
name="selectedSubscriber"
onChange={handleChange}
>
{formInput.subscriberOptions &&
formInput.subscriberOptions.map((item) => {
return (
<MenuItem key={item.value} value={item.name}>
{item.name}
</MenuItem>
);
})}
</Select>
you need to map your data and set at the same state, it should work.
const defaultFormInput = {
subscriberOptions: [],
selectedSubscriber: "",
subscriberOptionsIsLoading: true,
};
const [formInput, setFormInput] = useState(defaultFormInput);
useEffect(() => {
//load subscribers from API
async function loadSubscribers() {
const response = await fetch("https://myapi/subscribers");
const body = await response.json();
const mappedData = (body || []).map((x) => ({ value: x.id, name: x.name }));
const [defaultSelect] = mappedData || [];
setFormInput({
...formInput,
subscriberOptions: mappedData,
selectedSubscriber: defaultSelect.value,
subscriberOptionsIsLoading: false,
});
}
loadSubscribers();
}, []);
Related
I have these codes trying to implement a crypto return calculator that takes in a coin ID, a buy and sell date and the coin amount.
My issue is that the API is being fetched after every input and by the time I reach the sell date, my API limit has been reached.
Is there a way to prevent this from happening?
Here are the codes below.
export default function App() {
const [trade, setTrade] = useState({
sellData: {},
buyData: {},
gains: 0
});
const coinList = [
{ id: 0, name: "bitcoin" },
{ id: 1, name: "ethereum" },
{ id: 2, name: "tezos" },
{ id: 3, name: "cardano" }
];
const [buyDate, setBuyDate] = useState("");
const [sellDate, setSellDate] = useState("");
const [volume, setVolume] = useState(0);
const [coin, setCoin] = useState("");
const coingeckoUrl = (coin, date) => {
return `https://api.coingecko.com/api/v3/coins/${coin}/history?date=${date}&localization=false`;
};
const calcGains = () => {
setTrade({
...trade,
gains:
(trade.sellData.market_data?.current_price.usd -
trade.buyData.market_data?.current_price.usd) *
volume
});
};
const coingeckoFetch = async (buy, coin, date) => {
fetch(coingeckoUrl(coin, date)).then((response) =>
response.json().then((jsonData) => {
if (buy) {
setTrade({ ...trade, buyData: jsonData });
} else {
setTrade({ ...trade, sellData: jsonData });
}
})
);
};
const handleBuyChange = (e) => {
let val = e.target.value;
setBuyDate(val);
coingeckoFetch(true, coin, val);
};
const handleSellChange = (e) => {
let val = e.target.value;
setSellDate(val);
coingeckoFetch(false, coin, val);
};
const handleCoinChange = (e) => {
let val = e.target.value;
setCoin(val);
coingeckoFetch(null, coin, val);
};
return (
<div className="App">
<select defaultValue={coin} onChange={(val) => handleCoinChange(val)}>
{coinList.map((item) => (
<option key={item.id}>{item.name}</option>
))}
</select>
<input
placeholder="Insert Buy Date"
defaultValue={buyDate}
onChange={(val) => handleBuyChange(val)}
/>
<h3> {trade.buyData.market_data?.current_price.usd} USD</h3>
<input
placeholder="Insert Sell Date"
defaultValue={sellDate}
onChange={(val) => handleSellChange(val)}
/>
<h3> {trade.sellData.market_data?.current_price.usd} USD</h3>
<input
placeholder="Insert Amount of Tokens"
value={volume}
onChange={(e) => setVolume(e.target.value)}
/>
<h3>{volume}</h3>
<button onClick={calcGains}> Calculate </button>
<h3>{trade.gains} USD</h3>
</div>
);
}
Thank you for your help.
Summary
if you are just don't want to request too many times.
Maybe you can add a state like
const [fetchNow, setFetchNow] = useState(false);
And in your handleChange() functions, do not carry out the coingeckoFetch(). But execute the setFetchNow()
like this
const handleBuyChange = (e) => {
let val = e.target.value;
setBuyDate(val);
// or your buy condition
if (val) {
setFetchNow(true)
}
};
const handleSellChange = (e) => {
let val = e.target.value;
setSellDate(val);
// or your sell condition
if (val) {
setFetchNow(true)
}
};
const handleCoinChange = (e) => {
let val = e.target.value;
setCoin(val);
// or your coin condition
if (val) {
setFetchNow(true)
}
};
Then add a useEffect(), and use a conditional if statement to determine that whether the coingeckoFetch() be carry out or not
useEffect(()=>{
if (fetchNow) {
coingeckoFetch(true, coin, val);
}
}, [fetchNow]);
Other
Or just simply add conditional if statement in your coingeckoFetch()
like
const coingeckoFetch = async (buy, coin, date) => {
// conditional if statement
if (buy !== "MyCondition") {
return;
}
else if (coin !== "MyCondition") {
return;
}
else if (date !== "MyCondition") {
return;
}
fetch(coingeckoUrl(coin, date)).then((response) =>
response.json().then((jsonData) => {
if (buy) {
setTrade({ ...trade, buyData: jsonData });
} else {
setTrade({ ...trade, sellData: jsonData });
}
})
);
};
I get not wrapped in act error while testing my component. Has someone any idea how to solve it? I've already tried many things like wrapping findByTestAttr in waitFor but it didn't work. And btw my test doesn't fail, assertion is correct, i only get these warnings. I wonder if it's actually my fault or it's jest fault that this error shows at all?
// CurrencyConverter.tsx
const HAVE = 'have';
const RECEIVE = 'receive';
const CurrencyConverter: React.FC = () => {
const [haveInputValues, setHaveInputValues] = React.useState<CurrencyInputValues>({ currency: Currencies.PLN, value: 100 });
const [receiveInputValues, setReceiveInputValues] = React.useState<CurrencyInputValues>({ currency: Currencies.USD, value: '' });
const [inputsSwapped, setInputsSwapped] = React.useState<boolean>(false);
const [rate, setRate] = React.useState<number>(0.0);
const [status, setStatus] = React.useState<Status>({ wasChangedByUser: true, last: HAVE });
const iconsStyle = useStyles();
const getNewCurrencies = (currency: Currencies, type: string) => {
const isHaveType = type === HAVE;
const notChangedCurrency = isHaveType ? receiveInputValues.currency : haveInputValues.currency;
let newHaveCurrency: Currencies, newReceiveCurrency: Currencies;
if (currency === Currencies.PLN && notChangedCurrency === Currencies.PLN) {
newHaveCurrency = isHaveType ? Currencies.PLN : receiveInputValues.currency;
newReceiveCurrency = isHaveType ? haveInputValues.currency : Currencies.PLN;
} else {
newHaveCurrency = isHaveType ? currency : Currencies.PLN;
newReceiveCurrency = isHaveType ? Currencies.PLN : currency;
}
return { newHaveCurrency, newReceiveCurrency };
};
const changeCurrency = (currency: Currencies, type: string) => {
const { newHaveCurrency, newReceiveCurrency } = getNewCurrencies(currency, type);
setReceiveInputValues((currState) => ({ ...currState, currency: newReceiveCurrency }));
setHaveInputValues((currState) => ({ ...currState, currency: newHaveCurrency }));
setStatus({ last: type, wasChangedByUser: true });
};
const changeValue = (value: number | '', type: string) => {
const setter = type === HAVE ? setHaveInputValues : setReceiveInputValues;
setter((currState) => ({ ...currState, value }));
setStatus({ last: type, wasChangedByUser: true });
};
const swapInputs = () => {
setHaveInputValues(receiveInputValues);
setReceiveInputValues(haveInputValues);
setInputsSwapped((currState) => !currState);
setStatus({ last: status.last === HAVE ? HAVE : RECEIVE, wasChangedByUser: true });
};
React.useEffect(() => {
if (!status.wasChangedByUser) return;
const getPropsToCompare = () => {
const isHaveStatus = status.last === HAVE;
const value = isHaveStatus ? haveInputValues.value : receiveInputValues.value;
const fromCurrency = isHaveStatus ? haveInputValues.currency : receiveInputValues.currency;
const toCurrency = isHaveStatus ? receiveInputValues.currency : haveInputValues.currency;
return { value, fromCurrency, toCurrency };
};
const getNewComparisonData = async () => {
const { value, fromCurrency, toCurrency } = getPropsToCompare();
if (value) return await axios.get<CurrencyComparison>(`${Endpoints.COMAPRE_CURRENCIES}/${value}/${fromCurrency}/${toCurrency}/`);
const onEmptyInputData = {
data: {
result: {
exchangeAmount: 0,
exchangeRate: rate,
},
},
};
return onEmptyInputData;
};
const updateComparison = async () => {
const { data } = await getNewComparisonData();
setRate(+data.result.exchangeRate);
const setter = status.last === HAVE ? setReceiveInputValues : setHaveInputValues;
setter((currState: CurrencyInputValues) => ({ ...currState, value: +data.result.exchangeAmount }));
};
updateComparison();
setStatus((currState) => ({ ...currState, wasChangedByUser: false }));
}, [status.wasChangedByUser]);
return (
<div className={classes.currencyConverter}>
<div className={classes.inputsWithConnector}>
<CurrencyInput label={HAVE} values={{ ...haveInputValues, changeCurrency, changeValue }} />
<div className={classes.inputsConnector}>
<div className={classes.connectorLine}></div>
<SwapHorizIcon className={classnames(iconsStyle.swap, inputsSwapped && iconsStyle.swapRotated)} onClick={swapInputs} />
</div>
<CurrencyInput label={RECEIVE} values={{ ...receiveInputValues, changeCurrency, changeValue }} />
</div>
<p className={classes.rate}>
Current rate:{' '}
<span className={classes.rateValue} data-test='currency-rate'>
{rate}
</span>
</p>
</div>
);
};
export default CurrencyConverter;
// test
const setup = () => {
return mount(<CurrencyConverter />);
};
describe('<CurrencyConverter />', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
moxios.install(axiosInstance);
});
afterEach(() => {
moxios.uninstall(axiosInstance);
});
it('displays value in receive input and correct rate on page init', (done) => {
wrapper = setup();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request
.respondWith({
status: 200,
response: {
result: {
exchangeRate: '4',
exchangeAmount: '25',
},
},
})
.then(async () => {
wrapper.update();
const receiveValueInput = findByTestAttr(wrapper, 'receive-value-input');
const rate = findByTestAttr(wrapper, 'currency-rate');
expect(rate.text()).toEqual('4');
expect(receiveValueInput.prop('value')).toEqual(25);
done();
});
});
});
});
// error
console.error
Warning: An update to CurrencyConverter inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at CurrencyConverter (C:\Users\jacek\Desktop\moje-strony\CurrencyCenter\src\components\home\infoCard\currencyConverter\CurrencyConverter.tsx:47:55)
at WrapperComponent (C:\Users\jacek\Desktop\moje-strony\CurrencyCenter\node_modules\#wojtekmaj\enzyme-adapter-utils\src\createMountWrapper.jsx:46:26)
127 | const { data } = await getNewComparisonData();
128 |
> 129 | setRate(+data.result.exchangeRate);
| ^
130 |
131 | const setter = status.last === HAVE ? setReceiveInputValues : setHaveInputValues;
132 | setter((currState: CurrencyInputValues) => ({ ...currState, value: +data.result.exchangeAmount }));
at printWarning (node_modules/react-dom/cjs/react-dom.development.js:67:30)
at error (node_modules/react-dom/cjs/react-dom.development.js:43:5)
at warnIfNotCurrentlyActingUpdatesInDEV (node_modules/react-dom/cjs/react-dom.development.js:24064:9)
at setRate (node_modules/react-dom/cjs/react-dom.development.js:16135:9)
at _callee2$ (src/components/home/infoCard/currencyConverter/CurrencyConverter.tsx:129:7)
at tryCatch (node_modules/regenerator-runtime/runtime.js:63:40)
at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:294:22)
at Generator.next (node_modules/regenerator-runtime/runtime.js:119:21)
console.error
Warning: An update to CurrencyConverter inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at CurrencyConverter (C:\Users\jacek\Desktop\moje-strony\CurrencyCenter\src\components\home\infoCard\currencyConverter\CurrencyConverter.tsx:47:55)
at WrapperComponent (C:\Users\jacek\Desktop\moje-strony\CurrencyCenter\node_modules\#wojtekmaj\enzyme-adapter-utils\src\createMountWrapper.jsx:46:26)
130 |
131 | const setter = status.last === HAVE ? setReceiveInputValues : setHaveInputValues;
Ok, it seems like I solved this problem by wrapping request.respondWith with waitFor. This entire act error is very annoying :/
it('displays value in receive input and correct rate on page init', (done) => {
wrapper = setup();
moxios.wait(async () => {
const request = moxios.requests.mostRecent();
await waitFor(() => {
request
.respondWith({
status: 200,
response: {
result: {
exchangeRate: '4',
exchangeAmount: '25',
},
},
})
.then(() => {
wrapper.update();
const receiveValueInput = findByTestAttr(wrapper, 'receive-value-input');
const rate = findByTestAttr(wrapper, 'currency-rate');
expect(rate.text()).toEqual('4');
expect(receiveValueInput.prop('value')).toEqual(25);
done();
});
});
});
});
I'm new to React and I created a small admin panel where you can add, edit, remove products. I would like to display 3 products from API when someone opens the app the first time and don't have edited products yet, but this data only shows if I manually refresh the page. I only want to display that if edited product is false, but initially I set edited products to false yet somehow it's not displaying, though I see the data as well as edited is set to false in the console.
Demo
https://react-storeadminpanel.herokuapp.com/
Here is the related code:
const Products = () => {
const {products, setProducts, setAllProducts, allProducts, editedItems, setEditedItems} = useProduct();
useEffect(() => {
async function fetchProducts() {
const res = await axios.get('https://a.nacapi.com/LimeGreen/products/').catch(err => console.log(err));
if(res) {
setProducts(res.data)
setEditedItems(false);
if(allProducts.length === 0 && editedItems === false) setAllProducts(products);
if(allProducts.length === 0 && editedItems === true) setAllProducts(allProducts);
if(allProducts.length > 0) setAllProducts([...allProducts]);
}
return res;
}
fetchProducts();
}, []);
return (
<Wrapper classname="wrapper">
<h1>All Products</h1>
<Cards>
{!!allProducts.length && (
allProducts.map(product => (
<ProductCard name={product.name} description={product.Description} price={product.Price} discount={product.Discount} key={product.uuid}/>
))
)}
</Cards>
</Wrapper>
)
}
The context, where I use LocalStorage
export const ProductContext = React.createContext();
export function useProduct() {
return useContext(ProductContext);
}
export function ProductProvider({children}) {
const [products, setProducts] = useLocalStorage('Api Data', []);
const [addedProduct, setAddedProduct] = useLocalStorage('Added Item', []);
const [allProducts, setAllProducts] = useLocalStorage('All Products', []);
const [editedItems, setEditedItems ] = useLocalStorage('Edited', false);
const [isAdded, setIsAdded] = useState(false);
const value = {
products,
setProducts,
addedProduct,
setAddedProduct,
allProducts,
setAllProducts,
editedItems,
setEditedItems,
isAdded,
setIsAdded,
}
return (
<ProductContext.Provider value={value}>
{children}
</ProductContext.Provider>
)
}
And Code where I set edit products to true
const ProductEdit = () => {
const {allProducts, setAllProducts, setEditedItems} = useProduct();
const [editProductId, setEditProductId] = useState(null);
const [editForm, setEditForm] = useState({
name: "",
Description: "",
Price: "",
Discount: "",
})
const saveEditHandler = (e) => {
e.preventDefault();
const fieldName = e.target.getAttribute("name");
const fieldValue = e.target.value;
const newForm = {...editForm};
newForm[fieldName] = fieldValue;
setEditForm(newForm);
}
const editHandler = (e, product) => {
e.preventDefault();
setEditProductId(product.uuid);
const formValues = {
name: product.Name,
Description: product.Description,
Price: product.Price,
Discount: product.Discount
}
setEditForm(formValues);
}
const submitEditsHandler = (e) => {
e.preventDefault();
const editedProduct = {
name: editForm.Name,
Description: editForm.Description,
Price: editForm.Price,
Discount: editForm.Discount,
uuid: editProductId
}
const newProducts = [...allProducts];
const index = allProducts.findIndex((product) => product.uuid === editProductId);
newProducts[index] = editedProduct;
setAllProducts(newProducts);
setEditedItems(true);
setEditProductId(null);
}
const cancelHandler = () => {
setEditProductId(null);
}
const deleteHandler = (productId) => {
const newProducts = [...allProducts];
const index = allProducts.findIndex((product) => product.uuid === productId);
newProducts.splice(index, 1);
setAllProducts(newProducts);
setEditedItems(true);
};
const csvLinkRef = useRef(null)
const exportFile = () => {
let data= {data:'info'}
//Async Action
dispatch(
getFileData(data,
()=>{
csvLinkRef.current.link.click()
}
))
}
<Button onClick={exportFile}> //run exportFile on click
<CSVLink
filename={'file.csv'}
data={someData}
ref={csvLinkRef}//set ref here
>
Export
</CSVLink>
</Button>
//Async API call using redux thunk
export const getFileData = (data,cb) => async dispatch => {
try{
const res = await callApi('ever.php',data,'POST')// make api call
if(res.status==="00"){
dispatch({
type:GET_ALL_DATA,
data: res.data,
})
}
}catch(error){
console.log(error)
}finally{
cb()
}
}
My issue is csvLinkRef.current.link.click() fires non-stop after the API call. How can I make it fire just once? Is there a way I can 'unset' the ref? Please help.(I'm using react-csv library).
The getFileData action API call is successful and the data is in redux state.
if csvLinkRef.current.link.click fired only once, everything would be fine
I had the same problem. Now it is solved. You can try it...
function CsvExample({ manufacturerList }: IdLinkCreateProps) {
const httpRequests = new HttpRequests()
const csvInstance = useRef<any | null>(null);
const [alertOpt, setAlertOpt] = useState({ isOpen: false, message: '', variant: '' })
const [manufacturer, setManufacturer] = useState<IManufacturerItem | null>(null)
const [quantity, setQuantity] = useState<number>(0)
const [idList, setIdList] = useState<{ idList: string }[]>([])
const header = [{ label: "Digital-Links", key: "idLink" }];
const createIdLink = async () => {
let data: IIdLinkCreate = { manufacturer: manufacturer!, quantity: quantity! }
try {
const result = await httpRequests.idLinkCreate(data)
setSuccessOptions(result)
} catch (error) {
console.log('error on getting trusted manufacturer list: ', error);
}
return
}
const setSuccessOptions = (result: IBackendResult) => {
if (result.success) {
setIdList(result.data);
} else {
setAlertOpt({ isOpen: true, message: 'FAILED', variant: 'danger' })
console.log('error on updating data: ', result.error);
}
}
useEffect(() => {
if (idList && idList.length > 0 && csvInstance?.current?.link) {
csvInstance.current.link.click();
}
}, [idList]);
return (
<div className='id_link_wrapper'>
<AppHeader />
<Alert className="alert_error" variant={alertOpt.variant} show={alertOpt.isOpen} >
{alertOpt.message}
</Alert>
<IdLinkTitlePart />
<IdLinkInputPart
manufacturerList={manufacturerList}
setManufacturer={setManufacturer}
setQuantity={setQuantity}
quantity={quantity}
/>
<Button
onClick={createIdLink}
className='button_element'> CSV EXPORTIEREN
</Button>
<CSVLink
asyncOnClick={true}
headers={header}
data={idList!}
filename="bsedata.csv"
data-interception='off'
ref={csvInstance}
/>
</div>
)
}
export default CsvExample
I can see my array in state, but I don't know why elements of array doesn't display on the app interface.
const [members, setMembers] = useState([])
useEffect( () => {
getMembers();
}, [props.event])
const getMembers = () => {
let new_members = [];
console.log(props.event)
props.event && props.event.uczestnicy.map(member => {
member.get().then(doc => {
let new_member;
new_member = {
...doc.data(),
id: doc.id
}
new_members.push(new_member)
})
setMembers(new_members)
})
console.log(new_members)
console.log(members)
}
[...]
{members && members.map(member => {
console.log('mem',member)
return(
<div key={member.id}>
{member.nick}
</div>
)
})}
So I can see this array in Components using React Developer Tools, but even console.log doesn't see it in the moment of performing.
And console.log(new_members) and console.log(members) result :
Your member values are fetch asynchronously, so its ideal if you set state only after all the values are resolved. For this you can use a Promise.all
const getMembers = async () => {
let new_members = [];
console.log(props.event)
if(props.event) {
const val = await Promise.all(props.event.uczestnicy.map(member => {
return member.get().then(doc => {
let new_member;
new_member = {
...doc.data(),
id: doc.id
}
return new_member
})
});
setMembers(values);
console.log(values);
}
}