Axios call made before modal box opens - reactjs

I'm using bootstrap to open a modal. Previously I was trying to use a boolean to show/ close the modal but couldn't get it to work. Here is athe StackOverflow post where I was trying to get some help:
(How to open Bootstrap Modal from a button click in React)
The code below is making an AJAX request before a user has clicked the button to open the modal to present the user with some data.
This button is in a main page:
<td><button type="button" data-bs-toggle="modal" data-bs-target="#staticBackdrop" onClick={() => rowEvents(id)}>Compare</button></td>
I include this component in the main page which in effect is then making the axios request:
<ComparisonModal previousWon={previousWon} currentWon={currentWon} />
useEffect(() => {
async function fetchData() {
const content = await client.get('1');
}
fetchData();
}, []);
I'm quite new to React, is there a better way to do this so that the Axios call is not made until a user clicks on the button to show the modal please?

Well, you have multiple questions at the same time, I just made this code sandbox as an example: https://codesandbox.io/s/relaxed-benji-8k0fr
Answering your questions:
Yes, you can show modal with a boolean. Basically would be like {booleanCondition && <MyModal />} (It's included in the code sandbox
Yes, you can do the axios request before when clicking the button. In the example we define a function: handleClick and on handleClick we do 2 things, getData which is a mocked request which last 3 seconds and setShowModal to be able to see the modal.
The result would be:
export default function App() {
const [showModal, setShowModal] = React.useState(false);
const [data, setData] = React.useState({});
const getData = async (id) => {
await wait(3000);
setData({ loaded: id });
};
const handleClick = (id) => {
setData({});
setShowModal(true);
getData(1);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => handleClick(1)}>Show Modal with Id 1</button>
<button onClick={() => handleClick(2)}>Show Modal with Id 2</button>
{showModal && <Modal data={data} onClose={() => setShowModal(false)} />}
</div>
);
}

I would try set a id to the button as it is a button on a table.
<td><button id="1" type="button" data-bs-toggle="modal" data-bs-target="#staticBackdrop" onClick={() => fetchData(id)}>Compare</button></td>
const fetchData = async (id) => {
try {
const content = await client.get(id);
} catch (err) {
console.log(err);
}
}

Related

Getting Undefined in console - React.js

So, the request is returning the JSON file. But when in console it is saying 'Undefined' and I do not know why.
So the button when clicked will send the results from my request from the google Place API; which contains the place_id needed to make the call to the Place Details API to the Info component.
const OnButtonClick = (restaurant) => {
setRestaurant(restaurant)
setOpenPopup(true)
}
<button className="cardButton" onClick={() => OnButtonClick(restaurantData)}>
View Information
</button>
<InfoPopup open={openPopup} restaurant={restaurant} onClose={() => setOpenPopup(false)} />
So, this works the way I think it does (Sorry, I am new to React)
Here's the InfoPopup component
function InfoPopup({ open, onClose, restaurant }) {
const [restaurant1, setRestaurant1] = useState([])
let id = restaurant.place_id
let URL = `/maps/api/place/details/json?place_id=${id}&key=${process.env.REACT_APP_API_KEY}`
const fetchRestaurants1 = async () => {
const res1 = await axios.get(URL)
setRestaurant1(res1.data.results);
}
useEffect(() => {
fetchRestaurants1()
console.log(restaurant1) //This is getting 'Undefined'
}, [id]);
const navigate = useNavigate()
if (!open) {return null}
return ReactDOM.createPortal(
<>
<div>
{restaurant1?.map(restaurant => (
<div key={restaurant.place_id}> {restaurant.formatted_phone_number} </div>
))}
</div>
<div className="popup">
<div className="popup-inner">
<button className="close-btn" onClick={onClose}> Close </button>
<h1 className="title"> {restaurant.name} </h1>
<ul>
{/* <li className="service">
Status: {}
</li> */}
<li className="location">
Address: {restaurant.vicinity}
Phone Number:
</li>
<li className="cost">
Cost: {restaurant.price_level}
</li>
{/* <li className="food">
Food Type:
</li> */}
</ul>
<div className="links">
<Link className="writeButton" to="/write" state={{data: restaurant}}>
Write a review
</Link>
{/* <button className="writeButton" onClick={() => navigate("/write", {data:restaurant})}>
Write a review
</button> */}
<Link className="readButton" to="/read" state={{data: restaurant}}>
Read the reviews
</Link>
{/* <button className="readButton" onClick={() => navigate("/read")}>
Read the reviews
</button> */}
</div>
</div>
</div>
</>,
document.getElementById('portal')
)
}
I think the problem is on the first render, there's no ID being passed. But I do not know how to work around it. Any help would be appreciated.
Looking at this block of code:
const fetchRestaurants1 = async () => {
const res1 = await axios.get(URL)
setRestaurant1(res1.data.results);
}
useEffect(() => {
fetchRestaurants1()
console.log(restaurant1) //This is getting 'Undefined'
}, [id]);
You're awaiting the result of the GET call, which is good, because it allows you to set state in the next line after waiting for the response.
The problem: when you call fetchRestaurants1() in the useEffect(), you're not waiting for that function to execute, therefore, we jump straight to the next line and console.log() restaurant1, which is of course still blank.
The same issue arises with set state calls.
If you do:
const [value, setValue] = useState(null);
then sometime later:
setValue(5);
console.log(value);
The value posted to console will be null, because JS doesn't want to wait for the set state call to finish before moving onto the next line, console.log(value);
To fix this: make the useEffect callback async, and await the functions in which you're making your axios.get calls. Example:
const fetchSomeData = async () => {
const response = await axios.get(URL);
setData(response.data);
}
useEffect(async () => {
await fetchSomeData();
/* do some stuff */
}, []);
Of course, you still can't console.log after the set state call above.
If you want a generalizable way to log state changes without worrying about async behavior, you can add a simple useEffect:
useEffect(() => {
console.log(value);
}, [value]);
Where value is any state variable. Since value is in the dependency array, anytime it changes, the useEffect will fire and log the change.

Test modal component within another component

I'm trying to test a component that should open its modal. Modal is a part of this component, but it's rendered with createPortal(). I first check if modal exist in the document and after button click if it appeared but test fails.
Component:
const [openModal, setOpenModal] = useState(false);
function Component() {
return (
<div>
<button onClick={() => setOpenModal(true)}>Open Modal</button>
<Modal open={openModal}/>
</div>
)
}
Modal:
const Modal = ({ open, children }) => {
return createPortal(
<div style={{display: open ? "block" : "none"}} data-testid="modal">
{children}
</div>,
document.getElementById("modals")
);
};
Test:
test("component that opens modal", async () => {
render(<Component />);
const button = screen.getByText("Open Modal");
const modal = screen.queryByTestId("modal");
expect(modal).not.toBeInTheDocument();
fireEvent.click(button);
await waitFor(() => expect(modal).toBeInTheDocument()); // Fails
});
I tried to test it with await waitFor(() => expect(modal).toBeInTheDocument()) and also with standard expect(modal).toBeInTheDocument()). I also tried to render modal without portal, but still had no effect on the test. Could you please explain how it should be tested?
This kind of behavior is probably generating a new render, try using act
Some useful links: https://github.com/threepointone/react-act-examples/blob/master/sync.md
https://testing-library.com/docs/preact-testing-library/api/#act

button onClick works only once in react component

I am fetching data from API and Im displaying the users name in a parent component UserList and when I click on one name, it displays the child component called UserDetails. component).
The UserDetail comp contains a button that on click hides that component so the user can go and click on another user's name and see their details. It works fine the first time I choose a user and close the window. But when I click again on a second user nothing happens. I need to refresh the page so it works fine again.
I don't understand where the issue is. I suspect something to do with the ternary operator around the UserDetails component? Or something with the state? is it ok I write false instead of null?
The parent component:
const UserList = () => {
const [users, setUsers] = useState([])
const [selectedUser, setSelectedUser] = useState()
const [showUser, setShowUser] = useState(true)
const onHandleClick = () => setShowUser(false)
useEffect(() => {
fetch(URL)
.then(res => res.json())
.then(json => setUsers(json));
}, [])
return (
<>
<header>
<h1>Users list</h1>
</header>
<section>
<ul>
{users.map(user => (
<li key={user.id}>
<button onClick ={() => setSelectedUser(user)}>{user.name}</button></li>
))}
</ul>
{selectedUser && (
<>
{showUser ?
<UserDetail
name={selectedUser.name}
username={selectedUser.username}
email={selectedUser.email}
address={selectedUser.address.street}
phone={selectedUser.phone}
company={selectedUser.company.name}
onClick={onHandleClick}
/>
: false}
</>
)}
</section>
</>
)
}
export default UserList
The child component:
const UserDetail = ({name, username, email, address, phone, website, company, onClick}) => {
return (
<>
<DetailWindow>
<h1>{name}</h1>
<p>{username}</p>
<p>{email}</p>
<p>Adress:{address}</p>
<p>{phone}</p>
<p>{website}</p>
<p>{company}</p>
<button onClick={onClick}>X</button>
</DetailWindow>
</>
)}
const DetailWindow = styled.div`
border: black solid 1px;
`
export default UserDetail
After you set showUser to false, you never set it back to true again. I would suggest your onClick when you're mapping over users to both select the user and show the user -
onClick={() => {setSelectedUser(user); setShowUser(true);} }
It would probably make sense to declare this function outside of your return since it will be multi-line.

JSX button element form submissions

I have two Buttons in my component. the 'submit note' button refreshes the page (submits the form?) though the function handler for it only says to console.log. The "add note" button does not refresh the page. Both buttons are a part of the form, and both have arrow functions attached to them. Why isn't the "add note" function also refreshing the page? Is it because any component that changes state cannot also submit?
const Button = ({handleClick, text}) => {
return (
<button onClick={handleClick}>{text}</button>
)
}
export default Button; //component in separate file. added here for clarity
const NoteContainer = () => {
const [click, setClick] = useState(false)
const handleClick = () => setClick(true)
const submitNote = () => console.log('note submitted')
if (click === true) {
return (
<div>
<Note />
<Button handleClick={submitNote} text="Submit Note" />
</div>
)
}
return (
<div>
<Button handleClick={handleClick} text='Add Note'/>
</div>
)
}
export default NoteContainer;

setState hook does't change state invoking from child

I am using hook in component to manage modal state.
(Class version of component reproduce the problem)
handleClick will open modal and handleModalClose should close.
I send handleModalClose to Modal component and with console.log could see, that it is processed, but the isModalOpen state not changed (the same for callback setState).
When I am trying to invoke it with setTimeout - state changes and Modal is closing.
Why the hell the state not changes when I invoke changing from child???
const [isModalOpen, setModalOpen] = useState(false);
const handleClick = () => {
setModalOpen(true);
// setTimeout(() => handleModalClose, 10000);
};
const handleModalClose = () => {
console.log('!!!!!!!!handleModalClose');
setModalOpen(false);
};
return (
<div onClick={handleClick}>
{isModalOpen && <Modal closeModal={handleModalClose} />}
</div>
);
and here is extract from Modal
const Modal = (props) => {
const { closeModal } = props;
return (
<>
<div className="modal">
<form className="" onSubmit={handleSubmit(onSubmit)}>
<button type="button" className="button_grey button_cancel_modal" onClick={closeModal}>
</button>
PROBLEM SOLVED. e.stopPropagation() - added.
const handleModalClose = (e) => {
e.stopPropagation();
console.log('!!!!!!!!handleModalClose');
setModalOpen(false);
};
Modal was closed and instantly reopen by bubbling w/o this.

Resources