React JS. remove function from button after button is pressed - javascript

How to unmount. Return does not work. I need to be able to press button and after function completes then the function will be remove from the button
const handleSubmit = () => {
ipcRenderer.send(
"EDITOR",
[ type, typeValue],
"editor_response"
);
onClose(selectedValue);
};
return <div>
<Button onClick={handleSubmit} color="primary">
Ok
</Button>
</div>

Probably the cleanest solution is a state variable that determines whether the handler should be active or not.
const MyComp = () => {
const [pressed, setPressed] = useState(false;
const handleSubmit = () => {
setPressed(true);
ipcRenderer.send(
"EDITOR",
[ type, typeValue],
"editor_response"
);
onClose(selectedValue);
};
const noop = () => {};
return <div>
<Button onClick={!pressed ? handleSubmit : noop} color="primary">
Ok
</Button>
</div>;
};

Related

Call Function out of React Component

I want to update a shopping cart when I click on a Product. But I don't know, how to call the function in another component.
This is, where the function is written and the state of the cart is hold.
export const Cart = () => {
const [userId, setUserId] = useState(7);
const [cart, setCart] = useState([]);
const [outfitName, setOutfitName] = useState("");
const sendOutfit = () => {
axios.post(`{url}/postOutfit`, {
userId,
outfitName,
cart,
});
};
function addToCart(id) {
cart.push(id);
setCart(cart);
}
...
}
Here I want to call the addToCart function.
import { Cart } from "../../sites/Cart/Cart";
...
<div className="product-name">
<button
className="button is-small is-outlined is-primary"
onClick={() =>
Cart.addToCart(product.id) & changeButtonText
}
>
{buttonText}
</button>
</div>
When I try to execute this, I get the following error message:
Do you have any suggestion for me?
Thank you very much!
You can not do this like that. Below I wrote simple example and here is nice article I suggest to read it first: Components and Props
const AddToCartButton = ({ setCart }) => {
return (
<button
onClick={() => {
setCart("item");
}}
></button>
);
};
const Cart = () => {
const [cart, setCart] = React.useState([]);
return <AddToCartButton setCart={setCart} />;
};
Methods in React return JSX values ​​and in this way it is not correct to export a method, if you want to export the method of addToCart to a another component you need to send this method as propeties or using state management for example as follows:
export const Cart = () => {
const [userId, setUserId] = useState(7);
const [cart, setCart] = useState([]);
const [outfitName, setOutfitName] = useState("");
const sendOutfit = () => {
axios.post(`{url}/postOutfit`, {
userId,
outfitName,
cart,
});
};
function addToCart(id) {
cart.push(id);
setCart(cart);
}
return <AnotherComponent addCartFunc={addToCart} />
}
Then you can use this method in the host component as follows:
export const Cart = ({addCartFunc}) => {
return (
<div className="product-name">
<button
className="button is-small is-outlined is-primary"
onClick={() =>
addCartFunc(product.id) & changeButtonText
}
>
{buttonText}
</button>
</div>
)
}

React Firebase deleting the wrong document id

I've been trying to make a delete operation on a firebase database using Reactjs. I've got a bug with my function grabbing the wrong id from firebase.
I have a button that calls a handleOpen function which opens a Modal.
Modal operations:
// Grabs the right id
const [open, setOpen] = useState(false);
const handleOpen = (id) => {
console.log(id);
setOpen(true);
};
const handleClose = () => setOpen(false);
I've got a button that calls a handleDelete function which grabs the document id reference and deletes the document reference.
handleDelete function:
const handleDelete = (id) => {
const docRef = projectFirestore.collection("News").doc(id);
docRef.delete();
console.log("Deleted post data from id: " + id);
handleClose();
};
The Problem
From what I've been watching the handleDelete function grabs the last id from the mapped array of posts, it doesn't pass the id of the current document to the modal.
The problem only happens when I pass the function inside the modal. When I pass the function outside of the modal it works just fine.
The Objective
Grabbing document id, passing it to the modal and deleting the respective document.
Here's the full code:
import React, { useState } from "react";
import { projectFirestore } from "../../../../firebase/config";
import { useCollectionData } from "react-firebase-hooks/firestore";
import Layout from "../../../../hoc/Layout/Layout";
import { Link } from "react-router-dom";
import { Button, Box, Modal } from "#mui/material";
const DeletePost = () => {
const docRef = projectFirestore.collection("News");
const query = docRef.orderBy("createdAt", "desc");
const [posts] = useCollectionData(query, { idField: "id" });
// Modal operations
const [open, setOpen] = useState(false);
const handleOpen = (id) => {
setOpen(true);
};
const handleClose = () => setOpen(false);
const handleDelete = (id) => {
const docRef = projectFirestore.collection("News").doc(id);
docRef.delete();
console.log("Deleted post data from id: " + id);
handleClose();
};
return (
<Layout>
<ul>
{posts &&
posts.map((post) => {
const data = post.createdAt.toDate();
const day = data.getUTCDate();
const month = data.getUTCMonth();
const year = data.getUTCFullYear();
return (
<li key={post.id}>
<div>
<h3>{post.id}</h3>
<img
src={post.url}
alt={post.title}
/>
<p>
<b>
{day}/{month}/{year}
</b>{" "}
{post.description}
</p>
</div>
<div>
<Link to="/edit-post">
<Button>
Edit Post
</Button>
</Link>
<Button onClick={() => handleOpen()}>
Delete Post
</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="Delete"
aria-describedby="Delete Post"
>
<Box>
<div>
<h4>
Are you sure you want to delete {post.title}?
</h4>
</div>
<div>
<Button
onClick={() => {
debugger;
handleDelete(post.id);
}}
>
Yes
</Button>
<Button onClick={handleClose}>
No
</Button>
</div>
</Box>
</Modal>
</div>
</li>
);
})}
</ul>
</Layout>
);
};
export default DeletePost;
You could define a state variable that keeps tracks of the currently editing ID:
const [selectedId, setSelectedId] = useState(-1);
Then edit your handleOpen and handleClose functions:
const handleOpen = (id) => {
setSelectedId(id);
setOpen(true);
};
const handleClose = () => {
setSelectedId(-1);
setOpen(false);
};
In the handleDelete function, if an ID is selected, delete that one:
const handleDelete = (id) => {
const docRef = projectFirestore.collection('News').doc(selectedId !== -1 ? selectedId : id);
docRef.delete();
console.log('Deleted post data from id: ' + id);
handleClose();
};
Finally, you will need to update the handleOpen method in the JSX by adding the id parameter:
<Button onClick={() => handleOpen(post.id)}>
Delete Post
</Button>

Functional Components: Accessing Shared States

How can I alter the state of the hook isSignup per click of a button and return it to Auth.js using functional components?
I can't figure out how to wrap the return statement inside the button. I keep getting _onClick is not a function error.
Note: Homepage.js and Auth.js are functional components and they are using shared states.
// Homepage.js
const Homepage = () => {
const [isSignup, setIsSignup] = useState(false);
const handleClick = (e) => {
if (e) {
return <Auth isSignup={true}></Auth>
}
return <Auth isSignup={false}></Auth>
}
return (
<div>
<Button component={Link} to="auth" onClick={() => handleClick(false)}>
login
</Button>
<Button component={Link} to="auth" onClick={() => handleClick(true)}>
signup
</Button>
</div>
}
// Auht.js
const Auth = (props) => {
if(props.isSignup) {
// display signup form....
}
onClick is expecting a function, so,
onClick={handleClick}
The signature of handleClick is like this,
const handleClick = (event : React.ChangeEvent<any>) => {}
From the event, you can get the button id or something to determine the action.

React - How can I fire an Event listener just once?

Let's say I have a button that only fires just once then the listener is removed.
I've done this with vanilla Javascript
const element = document.querySelector('#MyElement');
element.addEventListener('click', handleClick, {once: true});
I don't know how can I access this property with React synthetic events since this is putted directly in the component
<button id="MyElement" onClick={handleClick}>Click me!</button>
Thanks
You can use useRef to do the same. Demo link is here
import React, { useEffect, useRef } from "react";
export default function App() {
const ref = useRef();
useEffect(() => {
if (ref.current) {
ref.current.addEventListener("click", () => console.log('Clicked only once'), { once: true });
}
}, []);
return (
<div>
<button ref={ref}>Click on me (Once)</button>
</div>
);
}
You can use a variable in state, probably a boolean for this
function App {
const [buttonClicked, setButtonClicked] = useState(false);
const handleClick = () => {
setButtonClicked(true);
// continue with the function
}
return <button onClick = {
buttonClicked ? () => {} : handleClick
} > Click < /button>
}
Or you could just return from the handleClick
function App {
const [buttonClicked, setButtonClicked] = useState(false);
const handleClick = () => {
if(buttonClicked){
return; //simply return
}
setButtonClicked(true);
// continue with the function
}
return <button onClick={handleClick}> Click </button>
}
I would prefer the second option.
Flip the state once the element has triggered its event :
handleClick() {
this.setState({
hasTriggered: true
})
}
render() {
return (
<div>
{
!this.state.hasTriggered ?
<MyElement onClick={this.handleClick.bind(this)} /> : null
}
</div>
)
}
const [avoidExtraCall, setAvoidExtraCall] = useState(false);
const handleClick = () => {
if(!avoidExtraCall){
//Do what you want here then
setAvoidExtraCall(true);
//This if statement will only be executed once
}
}
return (
<button id="MyElement" onClick={handleClick}>
Click me!
</button>
);

MUI - How to open Dialog imperatively/programmatically

Normally this is how you use MUI Dialog. The code below is taken from the docs:
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open Dialog
</Button>
<Dialog open={open} onClose={handleClose}>
{...}
</Dialog>
</div>
);
}
But I want it to create the Dialog imperatively, sort of like fire and forget. I do not want to embed the Dialog component in other components whenever I need to create them. Ideally I'd want to call it like this
createDialog(<>
<h1>My Dialog</h1>
<span>My dialog content</span>
<button onClick={() => closeDialog()}>Close</button>
</>)
So my component definition'd look like this
const createDialog = () => {
// ???
}
const closeDialog = () => {
// ???
}
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => setOpen(true);
const handleClose = () => {
createDialog(<>
<h1>My Dialog</h1>
<span>My dialog content</span>
<button onClick={() => closeDialog()}>Close</button>
</>)
};
return (
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open Dialog
</Button>
);
}
You can reuse dialogs using React's Provider pattern. The official React document has explained in good detail so I won't cover it again here.
First create a custom Provider component in this case I'll call DialogProvider. This component will manage a list of Dialogs in local state.
const DialogContext = React.createContext();
export default function DialogProvider({ children }) {
const [dialogs, setDialogs] = React.useState([]);
return (
<DialogContext.Provider {...}>
{children}
</DialogContext.Provider>
);
}
As you can see, we have an array of dialogs here, it contains the dialog props that will be mapped to the actually <Dialog /> component when rendering.
export default function DialogProvider({ children }) {
const [dialogs, setDialogs] = React.useState([]);
return (
<DialogContext.Provider {...}>
{children}
{dialogs.map((dialog, i) => {
return <DialogContainer key={i} {...dialog} />;
})}
</DialogContext.Provider>
);
}
The <DialogContainer/> is the parent component of the <Dialog/>. Put anything that you want to be reusable in there. Here is a minimum example to get you started.
function DialogContainer(props: DialogContainerProps) {
const { children, open, onClose, onKill } = props;
return (
<Dialog open={open} onClose={onClose} onExited={onKill}>
{children}
</Dialog>
);
}
We can create and remove the dialog using setState as normal.
const [dialogs, setDialogs] = React.useState([]);
const createDialog = (option) => {
const dialog = { ...option, open: true };
setDialogs((dialogs) => [...dialogs, dialog]);
};
const closeDialog = () => {
setDialogs((dialogs) => {
const latestDialog = dialogs.pop();
if (!latestDialog) return dialogs;
if (latestDialog.onClose) latestDialog.onClose();
return [...dialogs].concat({ ...latestDialog, open: false });
});
};
But how do we call them in other components when we defined them here? Well, remember we're using Provider component here, which means we can pass the context data down so other components can reference, in this case we want to pass the createDialog and closeDialog down.
const [dialogs, setDialogs] = React.useState([]);
const createDialog = (option) => {/*...*/};
const closeDialog = () => {/*...*/};
const contextValue = React.useRef([createDialog, closeDialog]);
return (
<DialogContext.Provider value={contextValue.current}>
{children}
{dialogs.map((dialog, i) => ...)}
</DialogContext.Provider>
);
We're almost done here, now we need to add the DialogProvider to the component tree.
export default function App() {
return (
<DialogProvider>
<App {...} />
</DialogProvider>
);
}
But before we can use them, we should create a hook to easily access the context from the parent. So in your DialogProvider.jsx
export const useDialog = () => React.useContext(DialogContext);
Now we can use it like this.
import { useDialog } from "./DialogProvider";
export default function Content() {
const [openDialog, closeDialog] = useDialog();
const onOpenDialog = () => {
openDialog({
children: (
<>
<DialogTitle>This dialog is opened imperatively</DialogTitle>
<DialogContent>Some content</DialogContent>
<DialogActions>
<Button color="primary" onClick={closeDialog}>Close</Button>
</DialogActions>
</>
)
});
};
return (
<Button variant="contained" onClick={onOpenDialog}>
Show dialog
</Button>
);
}
Live Demo
You can play around in the live demo here

Categories

Resources