I am making an expense tracker. I am receiving an object of data in the income component as props. I want to render it only when the object is updated / new data is passed to the object, I want the new data is rendered along with the previous data. But every time I pass the first data, the component returns 2 empty div. How can I stop getting 2 empty div's at first render.
const Income = ({incomeData}) => {
const [totalAmount, setTotalAmount] = useState(0)
const [displayData, setDisplayData] = useState([])
useEffect(()=>{
if(incomeData.amount) setTotalAmount(totalAmount + incomeData.amount)
setDisplayData((prevData)=> [...prevData, incomeData])
}, [incomeData])
return (
<div className={style.container}>
<div className={totalAmount > 0 ? style.main : style.mainBlock}>
<h1>Total Income: {totalAmount}</h1>
{displayData.map((data, idx)=>{
const {total, type, date, amount, income, id} = data
return (
<div key={id} className={style.incomeBox}>
<div className={style.incomeDetail}>
<p>{income}</p>
<div>
<span>{amount}</span>
<span>{date}</span>
</div>
</div>
<button id={id}>
<AiFillDelete />
</button>
</div>
)
})}
</div>
</div>
)
}
export default Income
I am expecting the 2 empty data not to be displayed in the income list.
It sounds like you’re passing in empty objects for previousData and incomeData. If that’s the case then what’s rendered initially will be empty values b/c there’s no initial data.
Your options are to either pass in data on the initial render, or hide elements if data is empty. As you have a number of divs, this is just a guess at what you want to hide.
return (
<div className={style.container}>
<div className={totalAmount > 0 ? style.main : style.mainBlock}>
<h1>Total Income: {totalAmount}</h1>
{displayData[0].amount && displayData[1].amount && displayData.map((data, idx)=>{
const {total, type, date, amount, income, id} = data
return (
<div key={id} className={style.incomeBox}>
<div className={style.incomeDetail}>
<p>{income}</p>
<div>
<span>{amount}</span>
<span>{date}</span>
</div>
</div>
<button id={id}>
<AiFillDelete />
</button>
</div>
)
})}
</div>
</div>
)
The idea being that with a conditional check before the mapping of data you can either hide or display based on whether data is initially provided.
Related
i use usestate to create saveImages and setSaveImages, this initial as array, i want to each time i call the function, the content is different, so each time must to push to the array that info, but instead of do push, only replace the 1 position of array, doesnt add new position with different info. I don't know if I explain myself
const galery = useSelector((state) => state.search);
const dispatch = useDispatch();
const [saveImages, setSaveImages] = useState([]);
function savePhoto(e) {
e.preventDefault();
const { target } = e;
const content = target.photo.src;
setSaveImages(content)
console.log(content)
localStorage.setItem("img", saveImages);
dispatch(actionAddFavorite(content));
}
return(
<section className="w-full gap-0 sm:columns-2 md:columns-3 xl:columns-4 2xl:columns-5 3xl:columns-6">
{galery.map((item, index) => {
return (
<form onSubmit={savePhoto} key={index} className="relative">
<button className="bg-gray absolute left-5 top-3 shadow-md">
Guardar
</button>
<img
name="photo"
className="object-cover p-2"
src={item.urls.regular}
alt={item.alt_description}
/>
</form>
);
})}
</section>
)
You set your saveImages to contain "content", but what you want is to add "content" to existing saveImages array. Here is how you can do this:
setSaveImages(oldImages => {
return [...oldImages, content];
});
And here you can learn everything you need to know about state in react
This is the standard mapping for blog posts in Gatsby and they show all the blog posts with a title, the date, and the post.
const { blogs } = data.strapi
const { id } = data.strapi.blogs
return (
<Layout>
<article>
<header>
<h1> {blogs.title} </h1>
<p> {blog.description} </p>
</header>
<hr />
</article>
</Layout>
)
};
export default BlogPost;
but how can I change the look that appears in the first blog post with the font to the right and color red; the second blog post title to the left and the color blue and so on?
I made an if conditional but I can't make it work
let even=blogs.filter((a,i)=>i%2===1); // 1 3 5
let odd = blogs.filter((a,i)=>i%2===0); // 0 2 4
<div>
{even.map((blog, i) => {
return <h1 className="text-danger">{blog.title}</h1>
})}
</div>
// Output : title[0], title[2], title[4]; all red
//Expected Output: title[0] (red), title[1] (blue), title[2] (red), title[3] (blue) title[4] (red), title[5] (blue);
Assuming you're using CSS to style your blog, you can just conditionally add the class name to style each element at the same time you loop through them to print it.
<div>
{blogs.map((blog, i) => {
return (
<h1 className={`text-danger ${i % 2 === 1 ? "isEven" : "isOdd"}`}>
{blog.title}
</h1>
);
})}
</div>
I have seen similar questions here, but these haven't been helpful so far.
I have a component that has an array state:
eventData: []
Some logic watches for events and pushes the objects to the state array:
eventData.unshift(result.args);
this.setState({ eventData });;
unshift() here is used to push the new elements to the top of the array.
What I want to achieve is rendering the content of the state array. I have written a conditional that checks for a certain state, and based on that state decides what to output.
let allRecords;
if (this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
} else if (!this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
if (this.state.account === this.state.eventData[i].paramOne) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
}
}
Problems that I have with this piece of code:
The code always renders the very last value of eventData state object.
I would like to limit the rendered elements to always show not more than 20 objects (the last 20 records of the state array).
paramTwo is a bool, and according to its value I expect to see either Win or Loose, but the field is empty (I get the bool value via the console.log, so I know the value is there)
Is this even the most effective way of achieving the needed? I was also thinking of mapping through the elements, but decided to stick with a for loop instead.
I would appreciate your help with this.
A few things :
First, as the comments above already pointed out, changing state without using setState goes against the way React works, the simplest solution to fix this would be to do the following :
this.setState(prevState => ({
eventData: [...prevState.eventData, result.args]
}));
The problem with your code here. Is that the arrow function was never called :
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}
}
This function can be reduced to the following (after applying the deconstructing seen in the below code) :
<span>{paramTwo ? 'Win' : 'Lose'}</span>
Next up, removing repetitions in your function by mapping it. By setting conditions at the right place and using ternaries, you can reduce your code to the following and directly include it the the JSX part of your render function :
render(){
return(
<div> //Could also be a fragment or anything
{this.state.allRecords || this.state.account === this.state.eventData[i].paramOne &&
this.state.eventData.map(({ paramOne, paramTwo, paramThree, paramFour, paramFive, paramSix }, i) =>
<div className="event-result-table-container" key={i}> //Don't forget the key like I just did before editing
<div className="result-cell">
{paramOne}
</div>
<div className="result-cell">
<span>{paramTwo ? 'Win' : 'Lose'}</span>
</div>
<div className="result-cell">
{paramThree.c[0]}
</div>
<div className="result-cell">
{paramFour.c[0]}
</div>
<div className="result-cell">
{paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{paramSix.c[0]}
</div>
</div>
)
}
</div>
)
}
Finally, to only get the 20 first elements of your array, use slice :
this.state.eventData.slice(0, 20).map(/* CODE ABOVE */)
EDIT :
Sorry, I made a mistake when understanding the condition you used in your rendering, here is the fixed version of the beginning of the code :
{this.state.allRecords &&
this.state.eventData.filter(data => this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
Here, we are using filter to only use your array elements respecting a given condition.
EDIT 2 :
I just made another mistake, hopefully the last one. This should ahve the correct logic :
this.state.eventData.filter(data => this.state.allRecords || this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
If this.state.allRecords is defined, it takes everything, and if not, it checks your condition.
I cleaned up and refactored your code a bit. I wrote a common function for the repetitive logic and passing the looped object to the common function to render it.
Use Map instead of forloops. You really need to check this this.state.account === this.state.eventObj.paramOne statement. This could be the reason why you see only one item on screen.
Please share some dummy data and the logic behind unshift part(never do it directly on state object), we'll fix it.
getRecord = (eventObj) => (
<React.Fragment>
<div className="result-cell">
{eventObj.paramOne}
</div>
<div className="result-cell">
{eventObj.paramTwo ? <span>Win</span> : <span>Loose</span>}
</div>
<div className="result-cell">
{eventObj.paramThree.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFour.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{eventObj.paramSix.c[0]}
</div>
</React.Fragment>
)
render() {
let allRecords;
if (this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => this.getRecord(eventObj)}</div>;
} else if (!this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => {
if (this.state.account === this.state.eventObj.paramOne) {
return this.getRecord(eventObj);
}
return null;
})}</div>;
}
return (<div className="event-result-table-container">{allRecords}</div>);
}
I was wondering if it is possible to programmatically assign and get refs in React. Suppose I wanted to go through a loop creating elements, giving them refs that consist of a name + an index. I know I can assign them like that using strings. However, the only way I know how to access refs consists of using this.refs.refname which, as far as I know, precludes me from doing something like this.refs.{refname + index}. Is there any way I can do something like this? The source code below should hopefully give you an idea of what I'm asking.
render = () => (<div className='row signature-group'>
<div className='col-md-1 col-xs-2'>
<b>{this.props.signerDescription}</b>
</div>
<div className='col-md-4 col-xs-7'>
{this.props.signers.map((signer, index) => <div className='text-with-line' key={index} ref={"sig" + index}>{signer}</div>)}
</div>
<div className='col-md-2 col-xs-3'>
{this.props.signers.map((signer, index) => {
return (index > 0 && this/*.refs.sig+index.value == whateverValue*/) ?
(<div className='text-with-line-long-name' key={index}>Date</div>) :
(<div className='text-with-line' key={index}>Date</div>);
})}
</div>
</div>)
Also, I've heard that using strings to assign refs is considered legacy. Is there any way to programmatically assign refs in a more up-to-date fashion?
Yes, you can use a ref callback to achieve this. The function passed as the ref attribute value will be passed the DOM node of the component once, after it is rendered:
applyRef = (index, ref) => {
this[`sig${index}`] = ref;
};
render = () => (
<div className="row signature-group">
<div className="col-md-1 col-xs-2">
<b>{this.props.signerDescription}</b>
</div>
<div className="col-md-4 col-xs-7">
{this.props.signers.map((signer, index) => (
<div className="text-with-line" key={index} ref={this.applyRef.bind(this, index)}>
{signer}
</div>
))}
</div>
<div className="col-md-2 col-xs-3">
{this.props.signers.map((signer, index) => {
return index > 0 && this[`sig${index}`].clientHeight > 0 ? (
<div className="text-with-line-long-name" key={index}>
Date
</div>
) : (
<div className="text-with-line" key={index}>
Date
</div>
);
})}
</div>
</div>
);
You can use bracket notation to create a new property on your class component (this) and then you access it with the same name (this.sig1, this.sig2).
String refs are deprecated and should no longer be used. Your refs are now applied directly to the component instance (this).
This is I'm mapping an array successfully..
const faculty = props.faculty;
{(faculty.science_department || []).map(science_dep => (
<div className="row">
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
))}
But what if the array is empty? How can I account for null values/empty states? How can I check within this map function? Ie: show
<p>There are no faculty members within this category</p>
But what if the array is empty? How can I account for null
values/empty states? How can I check within this map function? Ie: show
<p>There are no faculty members within this category</p>
Given those requirements I would first filter the array, then render it with the map if it contains anything, otherwise render the placeholder message:
const {faculty} = this.props;
const science_department = (faculty.science_department || []).filter(dep => !!dep);
{
science_department.length ? science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>)
:
<p>There are no faculty members within this category</p>
}
PS: What is warning.id? The key should be a field of the science_dep with a unique value.
Assuming you want to do this within JSX syntax, then you can use the ternary operator:
const faculty = props.faculty;
{
faculty.science_department.length !== 0 ?
faculty.science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
))
:
<p>There are no faculty members within this category</p>
}
{(faculty.science_department || []).length ? faculty.science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
)) : (science_dep => (
<div className="row" key={warning.id}>
<p>There are no faculty members within this category</p>
</div>)()}