Why are my variables empty on first click Apollo Client React? - reactjs

I am trying to call useMutation when I click on the "Save" button on a modal. However, the first time I click it, the variables which get sent are empty (i can see the variables in the network calls tab and all are undefined) even though when I do a console.log I can see the data.
I.e
let globalState = {};
const SessionAdd = () => {
console.log(globalState); // This always displays the correct data when I click on the "Save" button.
const [AddsessionInput, {error} ] = useMutation(addSession,{
variables: {
title:String(globalState.title),
description:String(globalState.description)
}
});
return (
<div className="AddSession">
<Button variant="primary" onClick={handleShow}>
<i className='fas fa-plus' /> Create a new Session
</Button>
<Modal show={show} onHide={handleClose} size="md">
<Modal.Header closeButton>
<Modal.Title><i className='fas fa-camera' /> Create details</Modal.Title>
</Modal.Header>
<Modal.Body> <AddSessionForm /> </Modal.Body>
<Button variant="primary" onClick={AddsessionInput}>
Save
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
const AddSessionForm = () =>{
const [sessionDetails, setSessionDetails] = useState({
title:""
});
const handleSessionDetails = (evt) => {
sessionDetails[evt.target.name] = evt.target.value;
setSessionDetails(sessionDetails);
globalState = sessionDetails;
};
return(
<div>
<p><strong>Add title</strong></p>
<FormControl name="title"
onChange={handleSessionDetails}/>
<br/>
);
}
Is this the correct way to handle states when dealing with a parent component (i.e handling objects in child components from parent state)?
Thanks

I'd probably refactor it slightly:
const [AddSessionInput, {error}] = useMutation(QUERY_STRING);
const runAddSessionInput = async () => {
await AddSessionInput({ variables });
};
In your onClick:
return (
<Button variant="primary" onClick={runAddSessionInput} />
);

Related

Setting state to true inside a try {} block, but state automatically switching back to false on component re-render

I am trying to implement a Success Confirmation popup modal after a successful axios.delete call.
The delete is working and even console.log() works inside of my conditional rendering but I've noticed that my initial state is false, on successful delete (inside of my try block) the state changes to true but then once the component re-renders it chnages back to false, causing my popup modal to not render.
I'm not sure if the try block is the issue or the way I'm trying to render the popup modal.
Initial State
const [showSuccess, setShowSuccess] = useState(false);
axios request
const deleteService = async (id: number) => {
try {
const JWT = await getCookie("auth");
const { data } = await axios(
`/api/serviceType/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: JWT,
},
});
setData(data);
// Success Alert Popup
setShowSuccess(true);
} catch (e) {
// Error Alert Popup
setShowAlert(true);
}
};
The alert state change inside of the catch block works as needed!
Conditional Render
// Table state update on submit
useEffect(() => {
fetchData(data);
}, [data]);
// Success Alert
if (showSuccess === true) {
return (
<>
<Modal show={show} onHide={() => {setShowSuccess(false); handleClose(); }} backdrop="static">
<Modal.Header closeButton style={{ backgroundColor: "#00E676"}}></Modal.Header>
<AlertDialog
title={"Success!"}
message={"Service Type was successfully deleted."}
/>
</Modal>
</>
)
}
if (showAlert === true) {
return (
<>
<Modal show={show} onHide={() => {setShowAlert(false); handleClose(); }} backdrop="static">
<Modal.Header closeButton style={{ backgroundColor: "#FF1744"}}></Modal.Header>
<AlertDialog
title={"Error Deleting Data"}
message={"There was an error deleting the Service."}
/>
</Modal>
</>
)
}
return (
<>
<Trash onClick={handleShow}/>
<Modal show={show} backdrop="static" onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Delete Service</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete this Service? This process cannot be undone.
</Modal.Body>
<Modal.Footer>
<Button variant="outline-dark" onClick={handleClose}>
Cancel
</Button>
<Button type="submit" variant="danger" onClick={() => deleteService(id)}>
Delete
</Button>
</Modal.Footer>
</Modal>
</>
);
The error modal and confirm modal work, but the success modal is not.
Entire Component
import React, { useState, useEffect } from 'react';
import { getCookie } from "../../../utils/cookies";
import axios from "axios";
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import { Trash } from 'react-bootstrap-icons';
import AlertDialog from '../../../alerts/AlertDialog';
export default function DeleteService({ fetchData, id }) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
// Success Dialog
const [showSuccess, setShowSuccess] = useState(false);
console.log(showSuccess)
// Error Dialog
const [showAlert, setShowAlert] = useState(false);
// DELETE
const deleteService = async (id: number) => {
try {
const JWT = await getCookie("auth");
const { data } = await axios(
`/api/serviceType/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: JWT,
},
});
setData(data);
setIsLoading(false);
// Hides Modal on submission
setShow(false);
// Success Alert Popup
setShowSuccess(true);
} catch (e) {
setIsLoading(false);
// Error Alert Popup
setShowAlert(true);
}
};
// Table state update on submit
useEffect(() => {
fetchData(data);
}, [data]);
// Success Alert
if (showSuccess === true) {
return (
<>
<Modal show={show} onHide={() => {setShowSuccess(false); handleClose(); }} backdrop="static">
<Modal.Header closeButton style={{ backgroundColor: "#00E676"}}></Modal.Header>
<AlertDialog
title={"Success!"}
message={"Service was successfully deleted."}
/>
</Modal>
</>
)
}
if (showAlert === true) {
return (
<>
<Modal show={show} onHide={() => {setShowAlert(false); handleClose(); }} backdrop="static">
<Modal.Header closeButton style={{ backgroundColor: "#FF1744"}}></Modal.Header>
<AlertDialog
title={"Error Deleting Data"}
message={"There was an error deleting the Service."}
/>
</Modal>
</>
)
}
return (
<>
<Trash onClick={handleShow}/>
<Modal show={show} backdrop="static" onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Delete Service</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete this Service? This process cannot be undone.
</Modal.Body>
<Modal.Footer>
<Button variant="outline-dark" onClick={handleClose}>
Cancel
</Button>
<Button type="submit" variant="danger" onClick={() => deleteService(id)}>
Delete
</Button>
</Modal.Footer>
</Modal>
</>
);
}
You set show to false immediately before setting showSuccess to true. Remember all of your modals rely on show.
You already have separate variables to show the other modals, so why not use them?
Change show to showDelete (for clarity) and change your return to:
return (
<>
<Trash onClick={handleShow}/>
<Modal show={showDelete} backdrop="static" onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Delete Service</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete this Service? This process cannot be undone.
</Modal.Body>
<Modal.Footer>
<Button variant="outline-dark" onClick={handleClose}>
Cancel
</Button>
<Button type="submit" variant="danger" onClick={() => deleteService(id)}>
Delete
</Button>
</Modal.Footer>
</Modal>
<Modal show={showSuccess} onHide={() => {setShowSuccess(false); handleClose(); }} backdrop="static">
<Modal.Header closeButton style={{ backgroundColor: "#00E676"}}></Modal.Header>
<AlertDialog
title={"Success!"}
message={"Service was successfully deleted."}
/>
</Modal>
<Modal show={showAlert} onHide={() => {setShowAlert(false); handleClose(); }} backdrop="static">
<Modal.Header closeButton style={{ backgroundColor: "#FF1744"}}></Modal.Header>
<AlertDialog
title={"Error Deleting Data"}
message={"There was an error deleting the Service."}
/>
</Modal>
</>
);
Delete both if statements.
If that's too cluttered for you, you can put the modals into variables for organization.
const deleteModal =
<Modal show={showDelete} backdrop="static" onHide={handleClose}>
...
</Modal>
const successModal =
<Modal show={showSuccess} onHide={() => {setShowSuccess(false); handleClose(); }} backdrop="static">
...
</Modal>
const alertModal =
<Modal show={showAlert} onHide={() => {setShowAlert(false); handleClose(); }} backdrop="static">
...
</Modal>
return (
<>
<Trash onClick={handleShow}/>
{deleteModal}
{successModal}
{alertModal}
</>
);
In your deleteService function you call setShow(false); which causes the modal to be hidden, because you pass "show" as prop to your Modal even if showSuccess is true.
The confusion is created by variable name "show" that doesn't tell anything specific about its value, so at the end is used incorrectly

Looking for suggestions for a better solution

So I have a site which has 12 buttons. 6 of these buttons will need to launch a popup with a different video and 6 buttons will need to launch a popup with 6 images.
Now, currently, I am experimenting with a single popup displaying an image. And this is fairly easy, I set a state
const [showModal, setShowModal] = useState(false);
And then I have a button which will set the show onClick and then a model is shown - eg
<button className="buttonGeneral" onClick={()=>setShowModal(true)}>SUMMARY11</button> <ModalReact showModal={showModal} onClose={() => setShowModal(false)} image={props.image_1} size='med'/>
Thinking about this, I am thinking I will need 2 different types of modals, one for video and one for images. But with the approach above, I am going to need to useState with 12 different states. This seems a bit wrong, but I cannot think of any other way.
For completeness, here is the ModalReact component
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import '../styles/react-bs.css';
function ModalReact({showModal = false, onClose = () =>{}, image, size}) {
console.log("im ", image)
return (
<Modal
size={size}
show={showModal}
onHide={onClose}
backdrop="static"
keyboard={false}
// dialogClassName="videoPopup"
>
<Modal.Body><img src={image} alt="lala"></img></Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={onClose}>
Close
</Button>
</Modal.Footer>
</Modal>
)
}
export default ModalReact
You need to maintain different states for all the buttons. I would suggest writing a custom component with its state maintained in the same component so that it's available for only that component. Based on that, we can open a modal from its state variable.
export default function ModalReact({ itemNum }) {
const [showModal, setShowModal] = useState(false);
const handleClose = () => setShowModal(false);
const handleShow = () => setShowModal(true);
return (
<>
<Button variant="primary" onClick={handleShow}>
Launch demo modal {itemNum}
</Button>
<div style={{ display: "block", width: 700, padding: 30 }}>
<Modal show={showModal} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Modal heading {itemNum}</Modal.Title>
</Modal.Header>
<Modal.Body>
Woohoo, you're reading this text in a modal! {itemNum}
{itemNum > 6 ? "image" : "video"}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</div>
</>
);
}
We can use that from any component. You said you have 12 buttons so iterate and display all those buttons. I have used a simple array and iterating to display all the buttons.
return (
<div className="container">
{Array.from({ length: 12 }, (_, i) => i + 1).map((item) => (
<CustomModal itemNum={item} />
))}
</div>
);
Attached is a sandbox for reference.

findDOMNode is deprecated in StrictMode with React.createRef()

I'm using a functional component in react to launch a popup form. I want to grab the values in the form on submission and was using the variable = React.createRef() and then a ref={variable} in the input field to retrieve them but I keep getting
Warning: findDOMNode is deprecated in Strictmode
error in the console. Is there a better way to do this?
Const AddProduct = (section) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
let itemName = React.createRef();
const createProduct = () => {
console.log(itemName.current.value);
handleClose();
};
return (
<>
<a href="#" className="nav-link" onClick={handleShow}>
Add Product
</a>
<Modal show={show} onHide={handleClose}>
<Modal.Header>
<Modal.Title>Create Item</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Group controlId="itemName">
<Form.Label>Item Name</Form.Label>
<Form.Control type="text" name="name" ref={itemName} />
</Form.Group>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={createProduct}>
Create
</Button>
</Modal.Footer>
</Modal>
</>
);
};

How to close Modal in Reactjs?

On button click i was trying to open a model (and modal opening too) and in a same modal it contain a button and on button click i was trying to open another model (and second modal opening too), but when second modal is opening i want first model to be closed. can it be possible?
Here is my sandbox demo https://codesandbox.io/embed/dreamy-herschel-cyetn?fontsize=14&hidenavigation=1&theme=dark
const Practice = () => {
const [modalShow, setModalShow] = useState(false);
const handleSubmit = event => {
setModalShow(true);
};
return (
<div>
<Button onSubmit={handleSubmit} type="submit">
Submit
</Button>
<Modals show={modalShow} onhide={() => setModalShow(false)} />
</div>
);
};
here is my modal part
const Modals = ({ show, onhide }) => {
const [modalShowsec, setModalShowsec] = useState(false);
const Validation = () => {
setModalShowsec(true);
};
return (
<div>
<Modal show={show} onHide={onhide} size="sm" aria-labelledby="contained-modal-title-vcenter" centered>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">HELLO</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Hi</p>
</Modal.Body>
</Modal>
<button onClick={Validation}> Validate </button>
<Modal show={modalShowsec} onHide={() => setModalShowsec(false)}>
<Modal.Header closeButton />
<Modal.Body>
<p>Hi cool</p>
</Modal.Body>
</Modal>
</div>
);
};
Call onhide inside Validation function. This will hide the first modal.
const Validation = () => {
setModalShowsec(true)
onhide()
}

React - how to invoke popup window in my case?

I'm not a React expert yet thus I have a question for you - how to invoke my popup window from:
import {options, columns,convertToArray} from './consts'
const index = () => {
const {data, loading, error, performFetch} = fetchHook({path: "/xxx/yyy", fetchOnMount: true})
return (
<div className={classes.Container}>
<h1>List of products</h1>
<Divider className={classes.Divider} />
<ProductTable data={convertToArray(data)} options={options} columns={columns}/>
</div>
)
}
export default index;
consts.js
export const actions = (productPropertyId, showModal) => {
const productDetails = (productPropertyId) => {
}
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
return (
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
>{"Remove"}
</Button>
</div>
)
};
export const convertToArray = (productList) => {
let products = []
if (productList != null) {
productList.map(product => {
column1, column2, column3, actions(product.id)]
products.push(prod)
})
}
return products;
};
My popup is --> <FormDialog/> based on react Materials.
Is it possible to invoke popup in this place?
I have a react material table with some columns. The last column contains 2 buttons, one of them is "Remove" (row). Here I want to invoke my popup. Maybe I should rebuild my structure?
UPDATE
Below is my popup - I wonder how to run this popup from the place above:
const formDialog = (popupOpen) => {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
{/*<Button variant="outlined" color="primary" onClick={handleClickOpen}>*/}
{/* Open alert dialog*/}
{/*</Button>*/}
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We will send updates
occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Email Address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default formDialog;
UPDATE 2
I updated my code taking into cosideration the tips from your response, see above. Can I add a parameter showModal in my export const actions = (productPropertyId, showModal) and then invoke this component with different showModal value? UNfortunately my popup doesn't appear when I click on Remove button :(
You can invoke it conditionally and controle it using some state variable. Like this:
const [removeModal, removeToggle] = useState(false);
return (<>
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
onClick={() => removeToggle(true)}
>{"Remove"}
</Button>
</div>
{removeModal ? <YourComponent /> : ''}
</>
)
I'm using a react fragment there <></> just to position the modal div outside the main div, but you can also invoke it inside your main div as well (I usually do this and set the position: fixed so the modal/popup coud appear in top of everything).

Resources