Why is the onclick event running 2 different functions - javascript

I am trying to run a function by clicking Modify2 or Edit2 in the code.
It runs all the time the function defined with onClick and then showCard again
List section loads
Select an entry of the list, it will run showCard and loads the item.
Clicking on Edit2 or Modify2 triggers showConn and right away showCard again
It should just trigger showConn !
And why is it running showCard right after showConn?
Thanks for any suggestions.
The Code:
import React, { useState, useEffect } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
function App() {
const [list, setList] = useState(true);
const [card, setCard] = useState(false);
const [netconn, setNetconn] = useState(false);
const [networks, setNetworks] = useState([]);
const [network, setNetwork] = useState({});
useEffect(() => {
fetch(window.location.protocol+"//"+window.location.hostname+":3001/networks/list")
.then((response) => response.json())
.then((responseJson) => {
setNetworks(responseJson.data);
});
}, []);
let showCard = (id) => {
console.log( "Show Card");
fetch(window.location.protocol+`//`+window.location.hostname+`:3001/networks/${id}`)
.then((response) => response.json())
.then((responseJson) => {
setNetwork(responseJson.data);
setList(false);
setNetconn(false);
setCard(true);
});
};
let showConn = (id) => {
console.log( "Show Connection");
setList(false);
setCard(false);
setNetconn(true);
};
let showList = () => {
setCard(false);
setNetconn(false);
setList(true);
};
const handleSubmit = (event) => {
event.preventDefault();
}
const handleGetconn = (event,id) => {
event.preventDefault();
alert(id);
showConn(id)
}
return (
<div className="container">
{netconn ? (
<div className="row">
<div className="col-auto">
<div
onClick={() => showCard(network._id)}
className="btn btn-primary">Submit</div>
<div
onClick={() => showList()}
className="btn btn-default">Cancel</div>
</div>
</div>
) : null}
{list ? (
<div className="list-group">
{networks.map((network) => (
<li
key={network._id}
onClick={() => showCard(network._id)}
className="list-group-item list-group-item-action">
{network.name}
</li>
))}
</div>
) : null}
{card ? (
<div className="row">
<div className="col-auto">
<div
onClick={(e) => handleGetconn(e,network._id)}
className="btn btn-success">
Modify2
</div>
<div onClick={() => showConn(network._id)} className="btn btn-primary">
Edit2
</div>
<div onClick={() => showList()} className="btn btn-default">
Back
</div>
</div>
</div>
) : null}
</div>
);
}
export default App;
UPDATE the code and added () => to onclick tags where it was missing

You are calling the function showCard on every render here:
onClick={showCard(network._id)}
In the code excerpt above, the onClick prop value is the return value of the function call which is called on every render.
The <li> element has the onClick prop set correctly.
onClick={() => showCard(network._id)}
Here, the onClick prop value is a reference to a function which is called on click event.

Related

How to correctly delete a dynamically added div panel from an array in React

I am trying to add a delete function for my ConnectedProducts.js page, but can't seem to pass the correct ID to my Modal which handles the delete function. The delete function should delete a div panel (which are saved in an array) from said page, and works by clicking a button on the page which opens up a Modal asking for confirmation before deleting.
I am not exactly sure how I even get the correct ID from my array since the divs are added to the array with the .map function. The delete function seems to atleast somewhat work since it takes an index number and deletes the specified indexed div. (Shortened the following code to a minimal working example)
ConnectedProducts.js:
import React from "react";
import "../Overview.css";
import Approve from "../icons/icon-dc_alert-success-filled.svg";
import Denied from "../icons/icon-dc_callout_hint.svg";
import Close from "../icons/icon-dc_close.svg";
import Add from "../icons/icon-dc_add.svg";
import ModalAddProduct from "../components/ModalAddProduct.jsx";
import { Link } from "react-router-dom";
import ModalDelete from "../components/ModalDelete.jsx";
import ModalProductSettings from "../components/ModalProductSettings.jsx";
const ConnectedProducts = () => {
const [stateComment, setStateComment] = React.useState("");
const [open, setOpen] = React.useState(false);
const [openDelete, setOpenDelete] = React.useState(false);
const [openSettings, setOpenSettings] = React.useState(false);
const [states, setStates] = React.useState([]);
const handleComment = (e) => {
setStateComment(e.target.value);
};
function getIndex() {
// What exactly do I need to return here? If I return 1 for an example I need to add
// two panels, and only the second panel would get deleted while the first one stays no matter what
// panel I want to delete
return 1;
}
return (
<div id="wrapper">
{open && <ModalAddProduct setOpen={setOpen} setStates={setStates} />}
{openDelete && <ModalDelete setOpen={setOpenDelete} setStates={setStates} states={states} index={getIndex()} />}
{openSettings && <ModalProductSettings setOpen={setOpenSettings} states={states} setStates={setStates} index={getIndex()} />}
<div class="component-headline">
<h1 style={{ textAlign: "center" }}>
[APPLICATION NAME] - Connected Products:
</h1>
</div>
<div class="center-product-content">
<div id="center_connect">
//Here the divs get added to the "states" array after clicking Add on the Modal
{states.map((state, index) => {
return (
<div key={index}>
{state.stateSelect !== "Select Product Type" && state.stateSelect !== "" && (
<div class="app-box" key={index}>
<img
class="image"
alt="Logo"
src="https://st.depositphotos.com/1968353/2535/i/600/depositphotos_25357041-stock-photo-close-up-of-machine-gears.jpg"
/>
<div class="box-content">
<h3 class="product-h3"> {state.state} </h3>
<textarea
class="product-textarea"
placeholder="Short description of Product; max. 280 characters"
readOnly={true}
onChange={(e) => handleComment(e)}
value={state.stateComment}
>
{state.stateComment}
</textarea>
<h3 class="product-h3-second"> Configuration </h3>
<div class="approve-img">
<img
class="product-image"
alt="Icon"
src={Approve}
></img>
</div>
<div
class="button-content new"
id="product-delete-button"
>
<img
class="close_dyn"
alt="Delete"
src={Close}
onClick={() => setOpenDelete(true)}
></img>
</div>
<div class="module-button center_button_dyn">
<button
type="button"
class="btn btn-secondary"
onClick={() => setOpenSettings(true)}
></button>
<div class="button-animation"></div>
<div class="button-content">
<span class="content-text">Configure</span>
</div>
</div>
</div>
</div>
)}
</div>
);
})}
</div>
<div
style={{ cursor: "pointer" }}
class="app-box"
onClick={() => setOpen(true)}
>
<img
class="image"
src={Add}
alt="Add"
style={{ height: "150px", position: "relative", left: "550px" }}
/>
<p
style={{ fontSize: "larger", position: "relative", left: "600px" }}
>
Add Connected Product
</p>
</div>
<div class="module-button" style={{ left: "1340px" }}>
<Link to="/overview">
<button type="button" class="btn btn-secondary"></button>
</Link>
<div class="button-animation"></div>
<div class="button-content">
<span class="content-text">Done</span>
</div>
</div>
</div>
</div>
);
};
export default ConnectedProducts;
ModalDelete.jsx:
import React from "react";
const ModalDelete = ({ setOpen, setOpenSettings, index, states, setStates }) => {
React.useEffect(() => {
function handleEscapeKey(event) {
if (event.code === "Escape") {
setOpen(false);
}
}
document.addEventListener("keydown", handleEscapeKey);
return () => document.removeEventListener("keydown", handleEscapeKey);
});
//Delete Function
const deleteProduct = (index) => {
const copy = [...states];
copy.splice(index, 1);
setStates(copy);
console.log(index)
setOpen(false);
setOpenSettings(false);
}
return(
//HTML shortened up to the responsible button click for the delete function
<button
type="button"
class="btn btn-light btn"
onClick={() => deleteProduct(index)}
></button>)
export default ModalDelete;
ModalAddProduct.jsx: (adds the panel to ConnectedProducts.js)
import React from "react";
const ModalAddProduct = ({ setOpen, setStates }) => {
const [ModalState, setModalState] = React.useState("");
const [ModalStateComment, setModalStateComment] = React.useState("");
const [ModalSelect, setModalSelect] = React.useState("");
React.useEffect(() => {
function handleEscapeKey(event) {
if (event.code === "Escape") {
setOpen(false);
}
}
document.addEventListener("keydown", handleEscapeKey);
return () => document.removeEventListener("keydown", handleEscapeKey);
});
const handleComment = (e) => {
setModalStateComment(e.target.value);
};
const handleChange = (e) => {
setModalState(e.target.value);
};
const handleSelect = (e) => {
setModalSelect(e.target.value);
}
//Function to add new panels
const addNewProduct = () => {
setOpen(false);
setStates( oldArray => [...oldArray, {
state: ModalState,
stateComment: ModalStateComment,
stateSelect: ModalSelect,
}])
};
...
export default ModalAddProduct;
What am I missing to dynamically delete any selected panel? I tried so many different approaches to this, but can't seem to find a working solution.
import uuid from "uuid/v4";
const addNewProduct = () => {
setOpen(false);
setStates( oldArray => [...oldArray, {
state: ModalState,
stateComment: ModalStateComment,
stateSelect: ModalSelect,
id:uuid()
}])
};
//this will add id to each added item
const deleteProduct = (id) => {
"Set Your state"((prevItem) => {
return prevItem.filter((item) => item.id != id);
});
};
//In delete, function it filters the state and check whether the id is there or not
//let me know if it works

react- click and event affects only one item on a list

i am new to react and i am trying to mark one poem from a db.json file as read but clicking one affects all of the articles. how do i select only one at a time.
import React , {useState, useEffect} from "react";
function Poem() {
const [poemShow, setPoemShow] = useState([])
const PoemData = "http://localhost:8004/poems"
// to load the Poem
useEffect(() => {
fetch(PoemData)
.then((res) => res.json())
.then((data) => {setPoemShow(data)})
// .then((data) => console.log(data))
}, []);
const [unread, setUnread] = useState(false)
function markRead(event) {
console.log({unread, setUnread})
setUnread(!unread)
// console.log( `${item.id} marked`)
}
return (
<div>
{poemShow.map((item) => (
<div key={item.id}>
<h3>{item.title}</h3>
<p>{item.content}</p>
<strong>{item.author}</strong><br/>
{/* <button onClick={markRead}>{unread ? "Mark as unread" : "Mark as read" }</button> */}
<button onClick={(event) => markRead(event.target.id)}>{unread ? "Mark as unread" : "Mark as read" }</button>
<hr/>
</div>
))}
</div>
);
}
export default Poem;
You could store an object instead, that maps an itemId to a boolean (true or false), meaning if the item with the given id is read or not:
const [readState, setReadState] = useState({})
Have an event handler like this (it's a function creating a function, that will be used as the event handler)
const toggleReadState = (itemId) => () => setReadState({ ...redState, [itemId]: !redState[itemId] })
And then render the list like this:
poemShow.map((item) => (
<div key={item.id}>
{/* ... */}
<button onClick={toggleReadState(item.id)}>{redState[item.id] ? 'Mark as unread' : 'Mark as read'}</button>
</div>
))
When you are clicking on button, you are instantly changing all undread values from true to false and vice versa:
setUnread(!unread)
This might do the trick, because you need to filter out entry selected entry.
setUnread(unread.filter(el => el.id === event))

How to modify react button "More"?

I have the following React component:
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const onClickButtonChange = () => {
let cardMore = document.querySelector(".card_more");
let cardMain = document.querySelector(".card_main");
cardMore.style.display = "block";
cardMain.style.display = "none";
};
return (
<div>
{data ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
<button onClick={onClickButtonChange}>More</button>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
My implementation of the More button needs to display additional features (the card_more block). Right now this function only works on the very first element. I understand that in React this can most likely be done more correctly, but I don’t know how, so I use CSS styles.
P.S Edited:
I tried to use React features, maybe someone can tell me or does it make sense?
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const handleMore = async () => {
if (show === true) {
setShow(false);
} else if (show === false || !data) {
const r = await fetch(url);
const newData = await r.json();
setData(newData);
setShow(true);
}
};
return (
<div>
{data && show ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
<button onClick={handleMore}>More</button>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
Youre right, this isn't the way you should do it in React. But your problem in your onClickButtonChange-Function is that youre only getting one element with document.querySelector(".card_more") and everytime you call it you get the same element back (No matter on which card you call it)
What you need to do is: Identify the single component elements. Thats most likely solved by passing a id/key value down via props and then putting this id on a parent-element (e.g. div.card) and you give it an id:
<div className="card card_main" id={props.keyvalue}>
....
</div>
And then in your onClickButtonChange-Function you call:
let cardMore = document.querySelector(`#${props.keyvalue} .card_more`);
...
This should give you the right element.

How to call useEffect again on button click in react?

I am beginner in react.
I am making a small project. How to add Product in cart and I am stuck at re Rendering useEffect.
so what I want is to re Render the useEffect on button click.
how do I do that?
Here Is my Cart Component
import React, { useContext, useEffect, useState,useCallback } from "react";
import { UserContext } from "./Context";
import { useHistory } from "react-router";
import cartImage from "../assets/man-shopping.png";
import Axios from "axios";
const Cart = () => {
const { user, serUser } = useContext(UserContext);
const history = useHistory();
const [product, setProduct] = useState([]);
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
setProduct(
product.filter((item) => {
return item.Id !== res.data.Id;
})
);
});
};
useEffect(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user]);
return (
<div
className="d-flex align-items-center justify-content-center"
style={{ height: "90vh" }}
>
{user.role === undefined ? (
<div>
<button
className="btn btn-lg btn-primary bg-green"
onClick={() => {
history.push("/login");
}}
>
Please Login
</button>
</div>
) : (
<div>
{product.length === 0 ? (
<figure className="figure">
<img
src={cartImage}
alt="cart"
style={{ width: "100px", height: "100px" }}
/>
<figcaption className="figure-caption text-xs-right">
No Product Added
</figcaption>
</figure>
) : (
<div className="d-flex">
{product.map((item) => {
return (
<div className="card">
<img
src={new Buffer.from(item.pimg).toString("ascii")}
className="img-fluid crd-img"
/>
<div className="card-body">
<h5 className="card-title">{item.pname}</h5>
<p className="card-text">{item.pprice}</p>
<button
className="btn btn-primary"
onClick={() => removeFromCart(item)}
>
Remove
</button>
</div>
</div>
);
})}
</div>
)}
</div>
)}
</div>
);
};
export default Cart;
so I made the removeFromCart function as useEffect dependency so it works fine
but its calls the backend again and again.
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
setProduct(
product.filter((item) => {
return item.Id !== res.data.Id;
})
);
});
};
useEffect(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user,removeFromCart]);
Is there any other way to re render useEffect
Put axios.get in a function.
const getProduct = useCallback(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user]);
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
getProduct();
});
};
useEffect(() => {
getProduct();
}, [getProduct]);
What you need is not useEffect. Use a 'state' to store the items and add or delete items using setState when 'state' gets updated react will automatically re-render. And you can use the updated state to save in your database. Also update state before calling axios
You can add a boolean state like this :
const [isClicked, setIsCliked] = useState(false);
And toogle the boolean value whenever the button is clicked
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
setProduct(
product.filter((item) => {
return item.Id !== res.data.Id;
})
);
setIsCliked(bool => !bool)
});
};
And your useEffect may now look like this :
useEffect(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user.id,isClicked]);
There must be a better way but this should work

How can I collapse an accordion from a child component in react

I am creating a page to update product details on an e-commerce site I am building using NextJS, and I have the image upload section nested inside an accordion on the individual item page. Once images have been uploaded, I would like to clear the upload form and close the accordion. It is closing the accordion I am having trouble with.
ImageUploadAccordion.js:
import React, {useRef} from 'react';
import {Accordion} from 'react-bootstrap'
import ImageUpload from './ImageUpload'
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.click();
}
return (
<Accordion ref={accordionRef} defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
ImageUpload.js:
import React, {useState} from 'react';
import { useRouter } from 'next/router'
export default function ImageUpload({ item, toggle }) {
const router = useRouter()
const [images, setImages] = useState([])
const [imageURLS, setImageURLS] = useState([])
const [tags, setTags] = useState([])
const [theInputKey, setTheInputKey] = useState('')
const uploadImageToClient = (event) => {
if (event.target.files && event.target.files[0]) {
setImages((imageList) => [...imageList, {"index": images.length, "data": event.target.files[0]}]);
setImageURLS((urlList) => [
...urlList,
URL.createObjectURL(event.target.files[0])
]);
}
let randomString = Math.random().toString(36);
setTheInputKey(randomString)
};
const uploadTagToClient = (e) => {
if (event.target.value) {
const name = e.target.getAttribute("name")
// const i = event.target.value;
// document.getElementById("image-upload")
setTags((tagList) => [...tagList, {"name": name, "tag": e.target.value}]);
}
};
const removeImage = (name) => {
// debug
alert(`Trying to remove image index ${name}`)
let newImages = []
let newTags = []
setImages(images.filter(image => image.data.name !== name));
setTags(tags.filter(tag => tag.name !== name));
}
const uploadToServer = async (e) => {
const body = new FormData()
images.map((file, index) => {
body.append(`file${index}`, file.data);
});
// Use the filenames as keys then we can retrieve server side once we have the images
tags.map((tag, index) => {
body.append(tag.name, tag.tag)
})
const response = await fetch("/api/file", {
method: "POST",
"Content-Type": "multipart/form-data",
body
})
var message = await response.json();
alert(message['message'])
setImages([])
setTags([])
toggle()
};
const openImageUploadDialogue = () =>{
document.getElementById("image-upload").click()
}
return (
<div className="container">
<input style={{display:'none'}} accept="image/*" id="image-upload" type="file" key={theInputKey || '' } className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
<button className="btn btn-outline-success-inverse" onClick={openImageUploadDialogue} >
Add Image
</button>
<hr className = "text-pink"/>
<div className="row">
<div className="col d-flex flex-wrap">
{images.map((file, index) => {
return (
<div className="div p-1" key={file.data.name}>
<p className="text-pink">{file.data.name}</p>
<p>Tag</p>
<input type="text" name={file.data.name} id={`${file.data.name}`} onChange={uploadTagToClient} />
<img src={imageURLS[index]} height="200" width="150" />
<div className="btn btn-outline-success-inverse" onClick={ () =>removeImage(file.data.name)}>Remove Image</div>
</div>
);
})}
</div>
<button
className="btn btn-outline-success-inverse"
type="submit"
onClick={uploadToServer}
>
Upload Images
</button>
</div>
</div>
);
}
I tried by creating a reference to the accordion using useRef, and a function which uses this reference to activate the click event, which I passed to the ImageUpload component, according to another answer to a similar question, but it doesn't seem to work and I'm unsure as to why.
Any help always appreciated :-)
I believe you have the wrong target as the ref, update it to target the button that is automatically generated to wrap the header content.
<h2 class="accordion-header"><button type="button" aria-expanded="true" class="accordion-button"><span class="btn btn-outline-success">Upload Images</span></button></h2>
Rudimentary example:
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.querySelector('button').click();
}
return (
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header ref={accordionRef}>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}

Categories

Resources