I have the following which is changing the values, but it's always one step behind. For example, when you click on the "paid: false" for a customer, it changes to true but the app doesn't rerender and you have to update another thing on the app in order to see the change. Is there a simple way to fix this in React? I don't know how to research what I'm looking for so a point in the right direction will help a lot.
const [receipt, setReceipt] = useState(receiptData);
// const [currentReceipt, setCurrentReceipt] = useState({});
// For some reason I do not know yet, everything is working but this and onSubmitFromApp are one step behind.
const handlePaid = (index) => {
for (let receiptPaid in receiptData) {
if (receiptPaid === index) {
receiptPaid.paid = !receiptPaid.paid;
console.log(receiptPaid);
}
}
setReceipt(receiptData);
}
Link to full code: https://codesandbox.io/s/korilla-receipts-starter-forked-01xz0?file=/src/App.js:206-675
Your approach is kind of weird and involves mutations. You're better off doing something like this (I removed the form stuff cos that's unrelated):
// App.js
export default function App() {
const [receipts, setReceipts] = useState(receiptData);
// Map over the current state, not the imported array
const handlePaid = (id) => {
setReceipts(
receipts.map((receipt) =>
receipt.id === id ? { ...receipt, paid: !receipt.paid } : receipt
)
);
};
return (
<div className="App">
<Receipts receiptsArr={receipts} handlePaid={handlePaid} />
</div>
);
}
// Receipts.js
const Receipts = (props) => {
const receiptMap = props.receiptsArr.map((receipt, index) => {
return (
<Receipt
...
handlePaid={() => props.handlePaid(receipt.id)}
/>
);
});
return <div className="container">{receiptMap}</div>;
};
// Receipt.js
const Receipt = (props) => {
return (
...
<span onClick={props.handlePaid} >Paid: {props.paid ? 'true' : 'false'}</span>
</div>
)
}
You can check out the sandbox here
Related
The following is the code for my component
import React from 'react'
import Wrapper from '../../assets/css/LandingPageSmallerSectionCss/faqSectioncss'
// import FaqComponent from '../../components/faqComponent'
import faqs from '../../utils/faqs'
const FaqSection = () => {
const ref = React.createRef();
const toggleHandler = (e) => {
e.preventDefault()
ref.current.classList.toggle('.show-more')
}
return (
<Wrapper>
<div className="faq-container">
<h1 className="faqs-header" style={{ color: "rgb(40, 102, 129)" }}>FAQs</h1>
<div className="list-container">
<ul>
{faqs.map((faq) => {
const { id, question, answer } = faq
return (
<li key={id}>
<p>{question}</p>
<button className="toggle-more-btn" onClick={toggleHandler}></button>
<p className="faq-text" ref={ref}>{answer}</p>
</li>
)
})}
</ul>
</div>
</div>
</Wrapper>
)
}
export default FaqSection
I can implement the code for toggling in pure javascript and plain html but i'm having trouble translating it to react since react does not have queryselectorAll hence i'm having trouble,the following is the code for the pure javascript
let listContent= document.querySelectorAll('.faq-text')
let buttonContent = document.querySelectorAll('.toggle-more-btn')
const listArray=Array.from(listContent)
const buttonArray=Array.from(buttonContent)
//display both array list
// console.log(listArray)
// console.log(buttonArray)
//check if individual examples buttons are coherent with each other
// if (buttonArray[0] && listArray[0]) {
// buttonArray[0].addEventListener('click',(e)=>{
// listArray[0].classList.toggle('poopoo')
// })
// }
// if (buttonArray[1] && listArray[1]) {
// buttonArray[1].addEventListener('click',(e)=>{
// listArray[1].classList.toggle('poopoo')
// })
// }
//loop through all buttons in the list and if both the index of the button and list match
// for (let i = 0; i < buttonArray.length; i++) {
// if (buttonArray[i]&&listArray[i]) {
// buttonArray[i].addEventListener('click',e=>{
// listArray[i].classList.toggle('poopoo')
// })}
// }
// create funcion that works with individual array indexes
//experimental
const buttonPress=(button,list)=>{
for (let i = 0; i < button.length; i++) {
if (button[i]&&list[i]) {
button[i].addEventListener('click',e=>{
list[i].classList.toggle('show-more')
button[i].classList.toggle('rotate-btn')
})
}
}
}
buttonPress(buttonArray,listArray)
I've spent hours on this.
If anyone could answer me i'd be greatfull,kindly?
It is a couple things so far:
Handler function is not named the same as what is called.
The DOT on your css class .show-more! It took awhile (and a console.log to find this).
I don't think you want a ref in the first place.
Here is what I might do using some hooks. I'm faking first load with the useEffect, with empty array arg.
Then, I'm going to set the changed faq entry in a clone of the list "faqs", and then spread my temp array into the setFaqs. This is using the useState hook, which generally you give a variable name and then a setter function name (and for these built-ins React takes care of actually setting up in the background - including the default I have an empty array).
import { useEffect, useState } from "react";
import "./styles.css";
const mockServerReturn = [
{ id: 0, question: "Foo?", answer: "Bar!" },
{ id: 1, question: "Baz?", answer: "Bar!" },
{ id: 2, question: "Bar?", answer: "Bar!" }
];
const FaqSection = () => {
const [faqs, setFaqs] = useState([]);
useEffect(() => setFaqs(mockServerReturn), []);
const toggleHandler = (e) => {
e.preventDefault();
const temp = [...faqs];
temp[e.target.id].chosen = !temp[e.target.id].chosen;
setFaqs([...temp]);
};
return (
<>
<div className="faq-container">
<h1 className="faqs-header">FAQs</h1>
<div className="list-container">
<ul>
{faqs.map((faq) => {
const { id, question, answer, chosen } = faq;
return (
<li key={id}>
<p>{question}</p>
<button
className="toggle-more-btn"
onClick={toggleHandler}
id={id}
>
I choose {id}
</button>
<p className={`faq-text${chosen ? " show-more" : ""}`}>
{id}:{answer}:{chosen?.toString()}
</p>
</li>
);
})}
</ul>
</div>
</div>
</>
);
};
export default function App() {
return (
<div className="App">
<FaqSection />
</div>
);
}
Here is the relevant code:
const Members = () => {
// array of each video in selected grade
const videosMap = (videos) => {
return videos.map((video) => (
<VideoCard
key={video.id}
thumbnail={video.thumbnail}
title={video.title}
description={video.description}
onClick={() => {
handleVideoClick();
}}
/>
));
};
// updates state of shown videos & page heading
const handleGradeButtonClick = (videos, heading) => {
setShowVideos(videosMap(videos));
setVideosHeading(heading);
};
const handleVideoClick = () => {
console.log("test");
};
// controls state of which grade's videos to show
const [showVideos, setShowVideos] = useState(videosMap(kinder_videos));
// controls states heading to display depending on selected grade
const [videosHeading, setVideosHeading] = useState("Kindergarten");
const [showVideoDetails, setShowVideoDetails] = useState(null);
The handleVideoClick is the function that is not working when I click on one of the mapped VideoCard components.
Here is the full code if you want to see that:
https://github.com/dblinkhorn/steam-lab/blob/main/src/components/pages/Members.js
When I look in React DevTools at one of the VideoCard components, it shows the following:
onClick: *f* onClick() {}
If I don't wrap it in an arrow function it does execute, but on component load instead of on click. I have a feeling it has something to do with my use of .map to render this component, but haven't been able to figure it out.
Thanks for any help!
There's no problem with your mapping method, you just need to pass the onClick method as a prop to your VideoCard component :
On your VideoCard component do this :
const VideoCard = (props) => {
const { thumbnail, description, title, onClick } = props;
return (
<div className="video-card__container" onClick={onClick}>
<div className="video-card__thumbnail">
<img src={thumbnail} />
</div>
<div className="video-card__description">
<div className="video-card__title">
<h3>{title}</h3>
</div>
<div className="video-card__text">{description}</div>
</div>
</div>
);
};
export default VideoCard;
Just like the title says I'm passing down pokemon data and rickandmorty data. I also happen to be using the tailwind select menu for react thats pretty long. Is there a better way to do it than conditionally map through the data? I know I can do this
{pokemons ? (
{pokemons?.map((pokemon, idx) => (
**30 line long code for the select menu**
))}
) : (
{rickAndMorty?.map((character, idx) => (
**Another 30 long line code for the select menu**
))}
)}
Is this the only way to do it or is there a cleaner way? Any help would be greatly appreciated. Thanks!
I suggest to try and separate any duplicated code out into some generic component, like:
const GenericSelectItem = (props)=>{
return (<>{/* props.itemValues */}</>);
};
const GenericSelectList = (props)=>{
const { selectItems } = props;
return (<SelectList>
{ selectItems.map( selectItem => <GenericSelectItem selectItem={ selectItem } /> ) }
</SelectList>);
};
const Example = (props)=>{
const itemsToDisplay = pokemons || rickAndMorty;
return (<>
{ !itemsToDisplay ? null : <GenericSelectList selectItems={ itemsToDisplay } /> }
</>);
};
In case the SelectItems are very different, add specific components, like:
const PokemonItem = (props)=>{
return (<GenericSelectItem>{/* pokemon specific variations */}</GenericSelectItem>);
};
const RickAndMortyItem = (props)=>{
return (<GenericSelectItem>{/* rickAndMorty specific variations */}</GenericSelectItem>);
};
im trying to build a list you can add and delete components from.
Adding works but when i try to delete an item, every item that comes after that also gets deleted.
I found that the length of the use state array i use varies depending on which item i click delete on.
const Alerting = () => {
const [Alerts, setAlert] = useState([]);
const AddAlertingChoice = () => {
const addedAlerts = Alerts => [...Alerts, <AlertingCoinChoice coins={coins} id={new Date().getUTCMilliseconds()}];
setAlert(addedAlerts);
}
const DeleteAlertingChoice = id =>{
console.log("alerts "+Alerts.length) //This length always displays the item index-1?
const removedArr = [...Alerts].filter(alert => alert.props.id != id)
setAlert(removedArr)
}
return (
<>
<AlertingContainer>
<CoinChoiceContainer>
{Alerts.map((item, i) => (
item
))}
</CoinChoiceContainer>
<AddAlertButton onClick={AddAlertingChoice}>+</AddAlertButton>
</AlertingContainer>
</>
)
The items
const AlertingCoinChoice = ({coins, id, DeleteAlertingChoice}) => {
return (
<>
<AlertingCoin>
<CoinSelect id={'SelectCoin'}>
<OptionCoin value='' disabled selected>Select your option</OptionCoin>
</CoinSelect>
<ThresholdInput id={'LowerThresholdInput'} type='number'
pattern='^-?[0-9]\d*\.?\d*$'/>
<ThresholdInput id={'UpperThresholdInput'} type='number'
pattern='^-?[0-9]\d*\.?\d*$'/>
<SaveButton id={'AlertSaveAndEdit'} onClick={ClickSaveButton}>Save</SaveButton>
<DeleteAlertButton onClick={() => {DeleteAlertingChoice(id)}}>X</DeleteAlertButton>
</AlertingCoin>
</>
)
why cant it just delete the item i pass with the id parameter?
It sounds like you're only passing down the DeleteAlertingChoice when initially putting the new <AlertingCoinChoice into state, so when it gets called, the length is the length it was when that component was created, and not the length the current state is.
This also causes the problem that the DeleteAlertingChoice that a component closes over will only have the Alerts it closes over from the time when that one component was created - it won't have the data from further alerts.
These problems are all caused by one thing: the fact that you put the components into state. Don't put components into state, instead transform state into components only when rendering.
const Alerting = () => {
const [alerts, setAlerts] = useState([]);
const AddAlertingChoice = () => {
setAlerts([
...alerts,
{ coins, id: Date.now() }
]);
}
const DeleteAlertingChoice = id => {
console.log("alerts " + alerts.length);
setAlerts(alerts.filter(alert => alert.id !== id));
}
return (
<AlertingContainer>
<CoinChoiceContainer>
{Alerts.map((alertData) => (
<AlertingCoinChoice {...alertData} DeleteAlertingChoice={DeleteAlertingChoice} />
))}
</CoinChoiceContainer>
<AddAlertButton onClick={AddAlertingChoice}>+</AddAlertButton>
</AlertingContainer>
);
};
You also don't need fragments <> </> when you're already only rendering a single top-level JSX item.
I have a list of items (I get the items with a GET-call - I didn't add it here, because I think it's irrelevant). When I delete an item, the list should be updated/ re-rendered.
To do this I use the useEffect-hook with a second parameter (productData).
Problem:
I have to refresh the page manually in order to see the new list of items. I don't understand why: I use useEffect with the second parameter. Could someone point me in the right direction what is wrong? Thanks a lot!
Here is my code:
export default function MemberSavedProducts() {
const [productData, setProductData] = useState([]);
const [successMessage, setSuccessMessage] = useState();
const [errorMessage, setErrorMessage] = useState();
useEffect(() => {}, [productData]);
const deleteProduct = async(prod) => {
try {
if (window.confirm("Are you sure you want to delete this product?")) {
const {
data
} = await fetchContext.authAxios.delete(
`savedProducts/${prod}`
);
setProductData(
productData.filter((prod) => prod !== data.deletedItem.Id)
);
setSuccessMessage(data.message);
}
} catch (err) {
const {
data
} = err.response;
setErrorMessage(data.message);
}
};
return (
<CardLarge>
<div className={styles.productWrapper}>
{successMessage && <SuccessMessage text={successMessage} />}
{errorMessage && <ErrorMessage text={errorMessage} />}
{productData.map((prod) => {
return (
<div
key={prod.id}
>
<ProductItem
prod={prod}
onClick={() => {
getInfo(prod.information);
}}
/>
<button
onClick={() => {deleteProduct(prod.Id)}}
>
Delete
</button>
</div>
);
})}
</div>
</CardLarge>
);
}
Discussed this in the comments, posting this answer for completeness.
Are you sure the filter function works? It seems the refresh works because the GET response returns the right array. I think it should be productData.filter((prod) => prod.Id !== data.deletedItem.Id));. Because in your code you are comparing an object to a string.
Or you can use the passed parameter prod instead of the response maybe like this productData.filter((p) => p.Id !== prod));
Also a small clarification: useEffect does not cause a rerender, changing state does trigger a rerender. useEffect is just a listener/callback that triggers on change of the declared dependencies.