save all content clicked into state initial as array - javascript

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

Related

Update the render every time the props object is updated

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.

Getting Null when trying to query the path to my button element

Issue:
I want to return the button Element within my document that matches the specified selector, in this case ".comment-body__interaction--delete" but keep getting a return of null every time I console.log the variable that contains the return element.
Background Info
The HTML element I'm Trying to target has been inserted into the document via innerHTML.
All my scripts are at the bottom of the index.html page
I'm using querySelector at the bottom of the js document.
I know my class name is correct because I can style it via CSS.
my code
// LOCATION VARIABLES ***
const conversation = document.querySelector('.conversation-container-posted');
const form = document.querySelector('form');
console.log(form);
// Array THAT HOLDS ALL MY COMMENT OBJECTS
let objectsArray;
// VARIABLE THAT HOLDS MY HTML TEMPLATE
const template = (singleCommentObj) => {
return `
<article class="comment-container">
<figure class="comment-container__picture">
<img class="comment-container__picture-img" src="${singleCommentObj.image}" alt="profile picture" />
</figure>
<div class="comment-body">
<h3 class="comment-body__name">${singleCommentObj.name}</h3>
<div class="comment-body__date">${singleCommentObj.date}</div>
<article class="comment-body__comment"><p>${singleCommentObj.comment}</p></article>
<div class="comment-body__interaction">
<div class="comment-body__interaction--likes">Likes</div>
<button id="${singleCommentObj.id}" class="comment-body__interaction--delete">Delete</button>
</div>
</div>
</article>
<hr class="comment-container__divider"/>
`;
};
const displayComment = (object) => {
let staticComments = object
.sort((a, b) => b.timestamp - a.timestamp)
.map((values) => {
values.image = 'https://loremflickr.com/48/48';
values.date = moment.unix(values.timestamp / 1000).fromNow();
return template(values);
})
.join('');
conversation.innerHTML = staticComments;
};
// Gets AN ARRAY OF OBJECTS FROM THE api AND ASSIGNS IT TO objectsArray
// CALLS displayComment WITH objectsArray AS A PARAMETER TO INSERT ITS CONTENT INTO THE DOM
axios
.get('https://project-1-api.herokuapp.com/comments?api_key=7d8d085e-486e-42dc-b836-58009cbfa68f')
.then((response) => {
objectsArray = response.data;
displayComment(objectsArray);
})
.catch((error) => {
console.log(error);
});
form.addEventListener('submit', (e) => {
e.preventDefault();
let fluidObject = new FormData(e.target);
fluidObject = Object.fromEntries(fluidObject);
axios
.post('https://project-1-api.herokuapp.com/comments?api_key=7d8d085e-486e-42dc-b836-58009cbfa68f&content-type=application/json', {
name: fluidObject.name,
comment: fluidObject.comment,
})
.then((response) => {
objectsArray.push(response.data);
displayComment(objectsArray);
})
.catch((error) => {
console.log(error);
});
});
// DELETE
const a = document.querySelector('.comment-body__interaction--delete');
console.log(a);
This console.log(a) returns NULL
The code that creates the said element, displayComment is in an asynchronous actions callback.
You have to wait for the action to complete before you try to access the element.
In other words const a = document.querySelector('.comment-body__interaction--delete'); executes before your request was successful and the elements were created.

Map function on object containing mutliple arrays

I have a props object, with a user comment array, and the userId array inside. Originally i only had the user comment array, and so i used the map function to style each comment individually. Now that i have two arrays inside my props object, is there a way to use the map function to style both the users comment and his id at the same time? Here is my attempt at it but it doesnt work:
import React from 'react'
import faker from 'faker'
const UserComment = (props)=> {
var commentData = props.map(props=> {
return(<StyleComment comment = {props.comment} key = {props.comment} author = {props.userIds} />)})
return(null)//commentData)
}
const StyleComment = (props) => {
// get time of comment
return(
<div className = 'comment'>
<a href="/" className= "avatar">
<img alt ="avatar" src= {faker.image.avatar()}/>
</a>
<div className = 'name'>
<a href ="/" className = "author">
{props.author}
</a>
<span className="metadata"> Today at 1.00pm </span>
<div className= 'content'>
<div className = 'text'>{props.comment}</div>
</div>
</div>
</div>
)
}
Here is the parent where the props are defined:
<UserComment comment = {this.state.usersComment} userIds = {this.props.userIds}/>
and here is a console.log of an example output for the props object:
You need to pass complete object to UserComment component,
<UserComment comment={this.state.usersComment} />
Then you can iterate like this,
const UserComment = (props)=> {
console.log(props.comment);
return props.comment.comment.map((comment,index) => {
return <StyleComment key={comment} comment={comment} author={props.comment.userIds[index]}/>
});
}
Demo
Note: Current array iteration and mapping is based on index, but you must have some relation between comment and userIds array to correctly map the data.

Error passing function onClick in react

I have two files. A list Component and a Single Item Component. In my app, the user can select multiples items. Then I create an state element in "list" "items" and my idea is that when the user make click on the item button, the list element notify to List Component and save the item in Items array from "list".
I have the next code
List.jsx:
registrarItems(data,item){
console.log(data,"aqui 1 con",item);
let items = this.state.itemsAgregados.slice();
if(!items.indexOf(data.id_producto)){
console.log("no se encuentra");
items.push(id);
this.setState({
'itemsAgregados':items
});
}else{
console.log("ya existe");
item.removerSeleccion();
}
console.log("registrando items",id);
}
render() {
return (
<div className="content-app">
<Navbar data={this.menu}/>
<div className="container lista-productos">
{
this.state.productos.map((producto, index) => {
return (
<Item data={producto}
registro = {this.registrarItems}
key={producto.id_producto}/>
);
})
}
</div>
</div>
);
}
And Item.jsx:
render() {
let props = this.props;
let img = JSON.parse(props.data.imagen);
let imgPath = Rutas.apiStatic + 'img/productos/' + props.data.id_producto + '/' + img.sm;
props.data.items = this;
return (
<div className="row item-listado">
<div className="col-xs-3">
<img src={imgPath} className="img-circle img-item"/>
</div>
<div className="col-xs-7">
<Link to={Rutas.producto + props.data.identificador}>
<h3 className="titulo">{props.data.titulo}</h3>
<span className="price">$ {props.data.precio}</span>
</Link>
</div>
<div className="col-xs-2 text-right">
<ul className="list-unstyled list-acciones">
<li>
<a href="#" onClick={()=>props.registro(props.data,this)} className={this.state.classAdd}>
<i className="fa fa-plus"></i>
</a>
</li>
</ul>
</div>
</div>
)
}
As you can see, I pass the "registrarItems" method as a param to Item and there, i add this as onClick event in the tag from item. But I need pass the "data" and the own "item" element to the onclick function. The first, for save the element clicked in the Items array, or remove it (if it already exists) because the button may have a toggle function. But in my "console.log" both params passed on the onClick method with the arrow function shows as "undefined".
I saw some examples and i don't get my error. can anybody helpme? thanks.
The final solve for this was simple. I resolved it with something similar as Free-soul said on his comment.
First, I passed the List Component as a param to item. Below my code in List's render method:
{
this.state.productos.map((producto, index) => {
this.items[producto.id_producto] = producto;
return (
<Item data={producto}
parent = {this}
key={producto.id_producto}/>
);
})
}
Then I get the parent param in componentDidMount method and later I call the validarItem function directly from the List method and I pass the params that i need.
Here my Item code:
onClickPlus(id,data) {
//{Rutas.listas + 'productos/' + props.data.id_producto //Url para pasar uno solo
this.setState({
classAdd:'selected'
})
if(!this.state.parent.validarItem(this.state.data)){
this.removerSeleccion()
}
if(this.state.seleccionMultiple){
}
}
removerSeleccion(){
this.setState({classAdd:'normal'})
}
componentDidMount(){
this.setState({
parent: this.props.parent,
data : this.props.data
})
}
render() {
return (
// more code
<a href="#" onClick={() => this.onClickPlus(parent)} className={this.state.classAdd}>
<i className="fa fa-plus"></i>
</a>
//more render code...
)
}
I don't know if this is the best practice, but works for me.

I think this is another React/JS context issue, how do I extract this variable?

I have an array[] of tracks that I receive from an API.
I pass it to a map function which will return a track for every track in tracks. I want to export a variable (Song) specific to that track to be be processed in my event handler as such. The only thing thats not working is the scope of song. I cant set the state of song in my map function or the component goes into an infinite rerender loop.
handleEnter(){
//I want to get the song into this context and play it here
this.props.mouseEnter();
}
handleLeave(){
//same for pausing
this.props.mouseLeave();
}
createTrack(track){
var song = new Audio([track.preview_url]);
return (
<div className="image" key={track.id}>
<img
className="img-circle"
src={track.album.images[0].url}
onMouseEnter={this.handleEnter.bind(this)}
onMouseLeave={this.handleLeave.bind(this)}
/>
<p className="showMe"><span>{track.name}</span></p>
</div>
);
}
getTracks(){
if(this.props.tracks) {
console.log(this.props.tracks);
return (
<div>{this.props.tracks.map(track => this.createTrack(track))}</div>
);
}
}
componentWillMount(){
this.props.fetchMessage();
}
render(){
return(
<div>{this.getTracks()}</div>
)
}
if you want to use .bind, you can send it to handleEnter and handleLeave.
handleEnter( trackID ) {
// trackID available here
}
createTrack(track){
var song = new Audio([track.preview_url]);
return (
<div className="image" key={track.id}>
<img
className="img-circle"
src={track.album.images[0].url}
onMouseEnter={this.handleEnter.bind( this, track.id )}
onMouseLeave={this.handleLeave.bind( this, track.id )}
/>
<p className="showMe"><span>{track.name}</span></p>
</div>
);
}
It's typically best practice to not use .bind in react since it creates a new function on every render. Rather, you should create a <Track /> component, pass it the track, then pass handleEnter and handleLeave as props.
const track = ( props ) => {
let { track, handleEnter, handleLeave } = props;
const onMouseEnter = () {
handleEnter( track.id );
}
const onMouseLeave = () {
handleLeave( track.id );
}
return (
<div className="image" key={track.id}>
<img
className="img-circle"
src={track.album.images[0].url}
onMouseEnter={ onMouseEnter }
onMouseLeave={ onMouseLeave }
/>
<p className="showMe">
<span>{track.name}</span>
</p>
</div>
);
};
then in your render, you'd map like you're doing and output <Track /> pure components instead of full-on components
Have a look at this. Hopefully it will solve your problem.
handleEnter(track, e){
// you will get the full track object and use the data
this.props.mouseEnter();
}
handleLeave(track, e){
// you will get the full track object and use the data
this.props.mouseLeave();
}
componentWillMount(){
this.props.fetchMessage();
}
render(){
const createTrack = (track, index) => {
var song = new Audio([track.preview_url]);
return (
<div className="image" key={'track-'+ index}>
<img
className="img-circle"
src={track.album.images[0].url}
onMouseEnter={this.handleEnter.bind(this, track)}
onMouseLeave={this.handleLeave.bind(this,track)}
/>
<p className="showMe"><span>{track.name}</span></p>
</div>
);
}
return(
<div>{this.props.tracks ? this.props.tracks.map(createTrack) : null }</div>
)
}

Categories