Why <button onClick={function_name}> is not calling the FETCH function on click?

This is for a personal project where I am trying to incorporate all the things I have learned in the front-end developer course.

In short, I am trying to make a Reactjs app that uses the prebuilt Stripe Checkout option to pay for products. I have created the general website, a store page, a working shopping cart. A user is able to choose products, add them to the cart, etc.
Currently, I have an issue with taking the data of cart items and then HTTP POSTing them to my Node Express route that is in charge of creating a Checkout URL (hosted on Stripe) where the user could go and pay for the selected items by using the fetch() API.

The route (/create-checkout-session) works fine itself. I tested it with Postman and it works - the request is taken by the Express server and the response is a Checkout URL that I can redirect the user to,
Yet, I am unable to figure out a way how to combine the function a.k.a. goToCheckout() that I have created (that does the FETCHing with POST) to be called/activated with a press of an HTML button. I have tried using onClick, action attributes, and putting the button in a form, but nothing seems to work.

Currently, the project is on my computer in development so I do not have a link to a published version, thus I am going to share the code in question.

I would be grateful if anybody could give me a hint or solution on how to allow me to bind the goToCheckout() function to a click of a button.

Bellow is the code found on the cart-page.js file where all of the code tied to the shopping cart can be found.

  1. The cart items are saved in a state a.k.a. ‘cart’ on the store page and can be edited on the cart page as well with the ‘+’ and ‘-’ buttons.
  2. const checkoutData - prepares the cart item data in the format that Stripe wants it to be presented like.
  3. goToCheckout() - is the function in question that I wish to call, but I have no idea why it is not properly called, why the FETCH is not executed, and why the user is not redirected to the Stripe Checkout URL.
  4. Right before the HTML main tag is my attempt to create the form and button that would call goToCheckout(). But failing. What happens when I press the button, the cart page refreshes and to the cart URL (http://localhost:3000/cart) a question mark (http://localhost:3000/cart?) is added and that is it.

If posting any other code block would be beneficial (like my Express server code, etc.), then I would gladly post it, but since Postman was able to reach the route, I feel that the issue is with how I am binding the goToCheckout() function to the /.

import React from "react"; const CartPage = (props) => { const { cart, onAdd, onRemove } = props; const productTotal = cart.reduce((a, c) => a + c.unit_amount * c.qty, 0) // default value 0 const taxTotal = <p>Tax is included in the price.</p> const shippingTotal = <p>You can choose your shipping options at checkout.</p> const totalCost = productTotal; const checkoutData = cart.map(item => ( { price: item.id, quantity: item.qty, } )); const goToCheckout = async (e) => { e.preventDefault(); await fetch('http://localhost:8000/create-checkout-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: checkoutData}), }) .then(res => { if (res.ok) return res.json() return res.json().then(json => Promise.reject(json)) }) .then(({ url }) => { window.location = url }) .catch((error) => { console.error(error); }); }; const currencyFormatter = new Intl.NumberFormat('en-gb', { style:"currency", currency:"GBP" }) return ( <main> <h1>Your Cart</h1> {cart.length === 0 && <p>Your Cart is Empty...</p>} {cart.map((item) => ( <section className='cart-item' key={item.product.id}> <h4>{item.product.name}</h4> <section className='cart-item-buttons'> <button onClick={() => onAdd(item)}>+</button> <button onClick={() => onRemove(item)}>-</button> </section> <p>{item.qty} * {currencyFormatter.format(item.unit_amount / 100)}</p> </section> ))} {cart.length !== 0 && ( <section> <p>Total Product Price: {currencyFormatter.format(productTotal / 100)}</p> <p>Toal Tax: {taxTotal}</p> <p>Shipping Costs: {shippingTotal}</p> <p><strong>Total Costs: {currencyFormatter.format(totalCost / 100)}</strong></p> </section> )} {cart.length > 0 && ( <section> <form action={goToCheckout}> <button type="submit"> Go To Checkout </button> </form> </section> )} </main> ); }; export default CartPage;

The same code as above but formate in-post.

import React from "react";

const CartPage = (props) => {

    const { cart, onAdd, onRemove } = props;

    const productTotal = cart.reduce((a, c) => a + c.unit_amount * c.qty, 0) // default value 0
    const taxTotal = <p>Tax is included in the price.</p>
    const shippingTotal = <p>You can choose your shipping options at checkout.</p>
    const totalCost = productTotal;

    const checkoutData = cart.map(item => (
        { 
            price: item.id, 
            quantity: item.qty,
        }
    ));

    const goToCheckout = async (e) => {
        e.preventDefault();

        await fetch('http://localhost:8000/create-checkout-session', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ items: checkoutData}),
        })
        .then(res => {
            if (res.ok) return res.json()
            return res.json().then(json => Promise.reject(json))
        })
        .then(({ url }) => {
            window.location = url
        })
        .catch((error) => {
            console.error(error);
        });
    };

    const currencyFormatter = new Intl.NumberFormat('en-gb', {
        style:"currency", 
        currency:"GBP"
    }) 

    return (
        <main>

            <h1>Your Cart</h1>

            {cart.length === 0 && <p>Your Cart is Empty...</p>}

            {cart.map((item) => (
                <section className='cart-item' key={item.product.id}>
                    <h4>{item.product.name}</h4>

                    <section className='cart-item-buttons'>
                        <button onClick={() => onAdd(item)}>+</button>
                        <button onClick={() => onRemove(item)}>-</button>
                    </section>
                        
                    <p>{item.qty} * {currencyFormatter.format(item.unit_amount / 100)}</p>
                </section>
            ))}

            {cart.length !== 0 && (
                <section>
                    <p>Total Product Price: {currencyFormatter.format(productTotal / 100)}</p>
                    <p>Toal Tax: {taxTotal}</p>
                    <p>Shipping Costs: {shippingTotal}</p>
                    <p><strong>Total Costs: {currencyFormatter.format(totalCost / 100)}</strong></p>
                </section>
                
            )}

            {cart.length > 0 && (
                <section>

                    <form action={goToCheckout}>
                        <button type="submit">
                            Go To Checkout
                        </button>
                    </form>

                </section>
            )}

        </main>
    );
};

export default CartPage;

I don’t think you need a form there and I don’t think the action attribute is the right one if you use a form. The action attribute contains an URL when used. You should be able to just do the button with an onClick attribute pointing to the function in this case. Your goToCheckout has a mix of async/await and .then/.catch syntax for promises while doing the fetch. I would pick one of the 2 styles and use it throughout the function consistently to see if that helps.