
I am a developer from North Carolina. I have always been fascinated by the internet, and scince high school I have been trying to understand the magic behind it all. ECU Computer Science Graduate
Search for a command to run...

I am a developer from North Carolina. I have always been fascinated by the internet, and scince high school I have been trying to understand the magic behind it all. ECU Computer Science Graduate
No comments yet. Be the first to comment.
As a technical person, it’s always been easy for me to focus on hard skills, numbers, and statistics. This worked well through school, personal projects and smaller teams; environments where I could accomplish a ton on my own. But now I’m working in ...

I recently wrote an article detailing how to embed Yotpo’s loyalty widgets on a headless site. As I was writing it, I thought “there is no way that the reviews widget will be this troublesome”. I was wrong, it was just as troublesome. For 2 primary r...

Currently, I am working on turning a Liquid-based Shopify store into a headless Hydrogen-based store. Part of this was transferring Yotpo embedded forms to the new headless site. As for the loyalty and referrals portion of Yotpo, you can embed these ...

Within Shopify, store owners are restricted from having full control of their store’s checkout page. This is a safety feature on Shopify’s end, protectecting store owners and customers from accidentally (or purposely) exposing private information. Th...

HSTS is HTTPS Strict-Transport-Security. It enforces that only HTTPS is used across your web application, preventing unencrypted traffic being monitored and collected by unwanted entities. HSTS in Hydrogen It can be enabled in your Hydrogen app via r...

I have been assigned the task of transferring my employer's Shopify Storefront into a Hydrogen headless build for the past few weeks now.
Part of our store includes a signup form, where users can sign up for email discounts before checkout. They enter their email and password, select enter, then the info is to be sent to the Storefront API.
This had been giving me headaches for a while. Most of the Shopify Hydrogen documentation focuses on making API calls on component render, and not on event-driven scenarios. However, I finally found a solution and hopefully my solution can help.
In Hydrogen, you can not simply make API calls on the client side, like you can in other frameworks. This is to ensure security, so no one can access private storefront information from the client-side. To safely send data to a server component upon an event (form submission in my case), we will be using the useFetcher hook from Remix.
Make sure both of these components can be routed to. I am making both of them hybrid components, but you can make them client .client.jsx and server .server.jsx respectfully.
We need ...
A component that contains an event (I will be using form submission)
A component that calls the API via an action function
// The event triggering component
// Collects a form for user's email and password
import { useState, useEffect } from 'react';
import { useFetcher } from '@remix-run/react';
// empty loader function
export async function loader({context}) {
return null;
}
export default function CustomerSignUp() {
const [customerInput, setCustomerInput] = useState({
acceptsMarketing: true,
email: "",
password: "",
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setCustomerInput(input => ({
...input,
[name]: value
}));
};
const handleSignup = async (e) => {
e.preventDefault();
// where we will call our server component
};
return (
<div>
<form>
<div>Subscription Signup</div>
<input
type="email"
name="email"
value={customerInput.email}
onChange={handleInputChange}
placeholder="Email"
required
/>
<input
type="password"
name="password"
value={customerInput.password}
onChange={handleInputChange}
placeholder="Password"
required
/>
<button type="submit" className=''>Sign Up</button>
</form>
</div>
);
}
// Server Side Component
// Will call the Storefront API securely
import { useLoaderData } from '@remix-run/react';
// empty loader function
export async function loader({ request }) {
return null.
};
// action method is required for receiving POST requests
export const action = async ({ request }) => {
// the query I need to send to the Storefront API
const mutation = `
mutation CreateCustomer($input: CustomerCreateInput!) {
customerCreate(input: $input) {
customer {
id
email
firstName
lastName
acceptsMarketing
phone
}
customerUserErrors {
code
field
message
}
}
}
`;
};
// This is not neccessary
export default function SignUp() {
return (
<></>
);
}
The useFetcher hook will be used in the client component. It allows us to make requests to our server component. In this case, I will be using it for a POST request.
// Final Event Component
// Implements useFetcher hook
import { useState, useEffect } from 'react';
// import useFetcher
// ============================================
import { useFetcher } from '@remix-run/react';
// ============================================
export async function loader({context}) {
return null;
}
export default function CustomerSignUp() {
// How you initialize a useFetcher instance
// ============================================
const fetcher = useFetcher();
// ============================================
const [customerInput, setCustomerInput] = useState({
acceptsMarketing: true,
email: "",
password: "",
});
// console logs fetcher data when updated
// ============================================
useEffect(() => {
if (fetcher.data) {
console.log(fetcher.data);
}
}, [fetcher.data]);
// ============================================
const handleInputChange = (e) => {
const { name, value } = e.target;
setCustomerInput(input => ({
...input,
[name]: value
}));
};
const handleSignup = async (e) => {
e.preventDefault();
// How to submit a POST request to another component
// using fetcher
// ============================================
fetcher.submit({
email: customerInput.email,
password: customerInput.password,
}, {
method: "post",
action: "/signup", // Route to your server component
});
// ============================================
};
return (
<div>
<form
onSubmit={handleSignup}
>
<div>Subscription Signup</div>
<input
type="email"
name="email"
value={customerInput.email}
onChange={handleInputChange}
placeholder="Email"
required
/>
<input
type="password"
name="password"
value={customerInput.password}
onChange={handleInputChange}
placeholder="Password"
required
/>
<button type="submit" className=''>Sign Up</button>
</form>
</div>
);
}
Now that we are sending data to the server component, we must unpack it and send it to the API. Then, we can handle the API response and return it to the client event component. This is what our server component will look like now.
/**
* Signup server component
* Handles API call to storefront API
*/
import { useLoaderData } from '@remix-run/react';
// empty loader function
export async function loader({ request }) {
return null;
};
export const action = async ({ request }) => {
// Unpack data from POST Request
// Then make variables for GraphQL query
// ============================================
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
const variables = {
input: {
email,
password,
},
};
// ============================================
const mutation = `
mutation CreateCustomer($input: CustomerCreateInput!) {
customerCreate(input: $input) {
customer {
id
email
firstName
lastName
acceptsMarketing
phone
}
customerUserErrors {
code
field
message
}
}
}
`;
// Making Storefront API Call
// ============================================
try {
const response = await fetch("Storefront API Link", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Storefront-Access-Token": "your-token",
},
body: JSON.stringify({ query: mutation, variables }),
});
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const responseData = await response.json();
if (responseData.data && responseData.data.customerCreate) {
// succesfull customer creation
return new Response(JSON.stringify({
message: "Customer created successfully",
customer: responseData.data.customerCreate.customer
}), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
// handling errors
} else if (responseData.errors) {
return new Response(JSON.stringify({ errors: responseData.errors }), {
status: 400,
headers: {
"Content-Type": "application/json",
},
});
}
// handling errors
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
"Content-Type": "application/json",
},
});
}
// ============================================
};
export default function SignUp() {
return (
<></>
);
}
The useFetcher hook can be used to send data back and forth between components dynamically. It can be configured (like I have done) to make API calls conditionally and on events. It is very useful, and saved me a ton of headache.
Thanks for reading!