ChrisThornham
ChrisThornham
SSolidJS
Created by ChrisThornham on 10/28/2024 in #support
RPC "use server" and CSRF Attacks
I’m looking to better understand the CSRF risks associated with SolidStart, specifically regarding the use of RPC calls with the "use server" function. In Next.js, when I process a form with server actions, the risk of CSRF attacks is significantly reduced for a few reasons: 1. Server actions are limited to POST requests. 2. Modern browsers enforce Same-Site cookies by default, which helps mitigate CSRF vulnerabilities. 3. I can further enhance security by ensuring that all cookies have the SameSite=Strict, HttpOnly, and Secure settings. With SolidStart, using "use server" means I’m making an RPC call to that function. It's my understanding that RPC calls use HTTP POST to invoke specific server-side functions by name. Given this, I believe the same three points regarding CSRF risk reduction should apply to SolidStart as well. Am I correct in my understanding? If not, what potential CSRF risks should I be aware of when using RPC calls in SolidStart? Thank you! Chris
5 replies
SSolidJS
Created by ChrisThornham on 10/3/2024 in #support
SRR And Web Crawlers. Help Me Understand
I just released a new business that has more than 150 pages of docs. I custom-coded the entire site and docs section using SolidStart. I have SSR turned on. Here's my issue. Crawlers aren't finding my pages. I'll give two examples. 1. Algolia just added a crawler to my site. Their crawler only found 2 pages, not 150. 2. I tried to use a sitemap generator. Same problem. It only found 2 or 3 pages. Can anyone help me understand why crawlers aren't finding my pages and how I can fix this? Either I screwed something up, or I fundamentally don't understand how this works. My site address is: https://www.quickstartjs.com Thanks! Chris
5 replies
SSolidJS
Created by ChrisThornham on 9/13/2024 in #support
Can I Pass `createAsync()` a Signal?
I'm building an analytics dashboard with the Plausible API. Their API let's you set the time period for the data you're requesting by appending a period value to your URL string like &period=6m0.
I want to dynamically change this period value when clicking buttons (e.g., "12mo", "month", "7day", etc). So my server function looks like this:
// This is very dumbed down for brevity
export async function getAggregateMetric(range: string) {
"use server";
await fetch(`https://plausible.io/api/v1/stats&perdiod=${range}`);
}
// This is very dumbed down for brevity
export async function getAggregateMetric(range: string) {
"use server";
await fetch(`https://plausible.io/api/v1/stats&perdiod=${range}`);
}
Here's my question. I'm using cache, createAsync() , and the load() function to render the initial data for the page. I'm setting a hard coded value for the first load. But after that I want to let users change the time period by clicking buttons. I've pasted my design pattern below. Is it a good pattern? I asking because I periodically get a page flicker or refresh when I click the buttons and I can't figure out why.
const getMetrics = cache(
async (range: string): Promise<AggregateResult | undefined> => {
"use server";
return await getAggregateMetrics(range);
},
"metrics"
);

// hard coded initial value
export const route = {
load() {
void getMetrics("6mo");
},
};

export default function AddNote() {
// hard coded initial value
const [range, setRange] = createSignal("6mo");
// using a signal here to force createAsync to rerun
const metrics = createAsync(() => getMetrics(range()));

function changeMetrics(newRange: string) {
setRange(newRange);
};

return (
<>
<h1>Metrics</h1>
<MetricsBar metrics={metrics}/>
<button onClick={() => changeMetrics("day")}>Day</button>
<button onClick={() => changeMetrics("6mo")}>6mo</button>
<button onClick={() => changeMetrics("12mo")}>12mo</button>
</>
);
}
const getMetrics = cache(
async (range: string): Promise<AggregateResult | undefined> => {
"use server";
return await getAggregateMetrics(range);
},
"metrics"
);

// hard coded initial value
export const route = {
load() {
void getMetrics("6mo");
},
};

export default function AddNote() {
// hard coded initial value
const [range, setRange] = createSignal("6mo");
// using a signal here to force createAsync to rerun
const metrics = createAsync(() => getMetrics(range()));

function changeMetrics(newRange: string) {
setRange(newRange);
};

return (
<>
<h1>Metrics</h1>
<MetricsBar metrics={metrics}/>
<button onClick={() => changeMetrics("day")}>Day</button>
<button onClick={() => changeMetrics("6mo")}>6mo</button>
<button onClick={() => changeMetrics("12mo")}>12mo</button>
</>
);
}
Thank you!
15 replies
SSolidJS
Created by ChrisThornham on 9/8/2024 in #support
Unexpected SolidStart Behavior On Vercel. Can You Help?
I have an AuthContextProvider that works on npm run dev but doesn't work on Vercel. I'm lost. Any help would be appreciated. Here's the flow: 1. I'm using a Solid store (createStore) to store auth state. 2. When the auth state changes, I update the Solid store. 3. If a user is authenticated AND they are on a page that has /auth in the pathname, I redirect the user to the app dashboard. This prevents authenticated users from accessing sign-in or sign-up pages. Everything works as expected when running npm run dev. When a user signs in on the /auth/sign-in page: 1. An auth state change occurs. 2. I set isAuthenticated to true 3. Then, I redirect the user to the app dashboard. After deploying to Vercel, the redirect stopped working. After signing in, the app stays on the /auth/sign-in page. I added a console.log to check both conditions, and both are true, but the redirect doesn't work. If I refresh my browser, the redirect occurs. Why is this happening? Does it have something to do with caching at Vercel? I'm using the latest version of SolidStart in SSR mode. Here's the code.
// Set up a store to track auth state
const defaultAuthState: SupabaseAuthData = {
isAuthenticated: false,
isLoading: true,
supabaseSession: null,
isAdmin: false,
};

const [authStore, setAuthStore] = createStore(defaultAuthState);

const { data: { subscription }, } = supabase.auth.onAuthStateChange(async (_event, session) => {
// Check to see if there is a session.
if (!!session) {
// A session exists. Update the auth store
setAuthStore({ isAuthenticated: true, supabaseSession: session, isAdmin: adminUser });

console.log(authStore.isAuthenticated); // result true
console.log(location.pathname.includes("/auth"); // result true

// NOT WORKING
if (location.pathname.includes("/auth") && authStore.isAuthenticated) {
navigate(`/${appRoute}`, { replace: true });
}
}
});
// Set up a store to track auth state
const defaultAuthState: SupabaseAuthData = {
isAuthenticated: false,
isLoading: true,
supabaseSession: null,
isAdmin: false,
};

const [authStore, setAuthStore] = createStore(defaultAuthState);

const { data: { subscription }, } = supabase.auth.onAuthStateChange(async (_event, session) => {
// Check to see if there is a session.
if (!!session) {
// A session exists. Update the auth store
setAuthStore({ isAuthenticated: true, supabaseSession: session, isAdmin: adminUser });

console.log(authStore.isAuthenticated); // result true
console.log(location.pathname.includes("/auth"); // result true

// NOT WORKING
if (location.pathname.includes("/auth") && authStore.isAuthenticated) {
navigate(`/${appRoute}`, { replace: true });
}
}
});
5 replies
SSolidJS
Created by ChrisThornham on 9/6/2024 in #support
How Can I Generate A Sitemap?
I'm days away from releasing an SSR SolidStart project that has 150+ pages. I was hoping to dynamically create a sitemap, but I'm not having much luck. I tried this: https://github.com/jbaubree/vite-plugin-pages-sitemap Unfortunately, the docs weren't super clear. I configured all of the settings (shown below), but I couldn't figure out how to generate the actual sitemap. If anyone has an easy solution for creating a sitemap, I'm all ears. Thanks!
import { defineConfig } from "@solidjs/start/config";
/* @ts-ignore */
import pkg from "@vinxi/plugin-mdx";
import Pages from "vite-plugin-pages";
import generateSitemap from "vite-plugin-pages-sitemap";

const { default: mdx } = pkg;
export default defineConfig({
ssr: true,
server: { preset: "vercel" },
extensions: ["mdx", "md"],
vite: {
plugins: [
mdx.withImports({})({
jsx: true,
jsxImportSource: "solid-js",
providerImportSource: "solid-mdx",
}),
Pages({
onRoutesGenerated: (routes) =>
generateSitemap({
routes,
hostname: "https://www.example.com/",
readable: true,
exclude: [
"/account",
"/admin-dashboard",
"/app-dashboard",
"/checkout",
"/api",
],
}),
}),
],
},
});
import { defineConfig } from "@solidjs/start/config";
/* @ts-ignore */
import pkg from "@vinxi/plugin-mdx";
import Pages from "vite-plugin-pages";
import generateSitemap from "vite-plugin-pages-sitemap";

const { default: mdx } = pkg;
export default defineConfig({
ssr: true,
server: { preset: "vercel" },
extensions: ["mdx", "md"],
vite: {
plugins: [
mdx.withImports({})({
jsx: true,
jsxImportSource: "solid-js",
providerImportSource: "solid-mdx",
}),
Pages({
onRoutesGenerated: (routes) =>
generateSitemap({
routes,
hostname: "https://www.example.com/",
readable: true,
exclude: [
"/account",
"/admin-dashboard",
"/app-dashboard",
"/checkout",
"/api",
],
}),
}),
],
},
});
3 replies
SSolidJS
Created by ChrisThornham on 9/5/2024 in #support
Environment Variables Missing `npm run start`
All of my environment variables are missing or undefined after running npm run build and npm run start. npm run dev works as expected. Does the build have access to the .env file at the root of the project? If not, how do I access them in the build?
12 replies
SSolidJS
Created by ChrisThornham on 7/31/2024 in #support
<A> Component: #id Breaking Link
I'm building a docs page for a project I'm working on. If I create a link with a fragement id pointed to a div on the same page, everything works as expected. The page scrolls to the element with the appropriate id.
|<A href="#section1">Link to section 1</A>

// One the same page
<div id="section1">...</div>
|<A href="#section1">Link to section 1</A>

// One the same page
<div id="section1">...</div>
But, if I link to another page, nothing happens. I click the link and it doesn't navigate.
|<A href="/to/another/page#section1">Link to section 1</A>

// On another page
<div id="section1">...</div>
|<A href="/to/another/page#section1">Link to section 1</A>

// On another page
<div id="section1">...</div>
Is there an easy fix for this?
4 replies
SSolidJS
Created by ChrisThornham on 7/11/2024 in #support
Can The load() Function Access Secure Values?
If I want to take advantage of load on hover when using a url Param, I can use the following pattern. Pass the param
<A href={`/${orderId}`}>Link</A>
<A href={`/${orderId}`}>Link</A>
Then in the component
const getOrder = cache(async (orderId: string): Promise<Order | undefined> => {
"use server";
return await getOrder(orderId);
}, "order");

// Get the param
export const route = {
load({ params }) {
void getOrder(params.orderId);
},
} satisfies RouteDefinition;

export default function OrderDetailsPage(props: RouteSectionProps) {
const order = createAsync(() => getOrder(props.params.orderId));
//... use the order
}
const getOrder = cache(async (orderId: string): Promise<Order | undefined> => {
"use server";
return await getOrder(orderId);
}, "order");

// Get the param
export const route = {
load({ params }) {
void getOrder(params.orderId);
},
} satisfies RouteDefinition;

export default function OrderDetailsPage(props: RouteSectionProps) {
const order = createAsync(() => getOrder(props.params.orderId));
//... use the order
}
But what if I need to pass getOrder() something secure like a userId or a jwt? Is there a way that I can pass a secure value to the load function without exposing it?
15 replies
SSolidJS
Created by ChrisThornham on 7/11/2024 in #support
Help Me Understand "use server" Security
I'm new to this topic. So please excuse me if my questions if they're obvious or don't sound well thought out. I'm simply trying to learn. Let's assume I have an app dashboard. When a user visits that dashboard, I run a createEffect() to check their access status. Inside that "client-side" createEffect() I call a server function getUserRecord() Here's a simplified example to show logic.
// Get the userId and email of logged in user
const supabaseAuthContext = useSupabaseAuthContext()!;
const userId = supabaseAuthContext.supabaseSession?.user.id;
const userEmail = supabaseAuthContext.supabaseSession?.user.email;

createEffect(async () => {
if (userId && userEmail) {
const userRecord = await getUserRecord(userId);
if (userRecord) {
if (userRecord.has_app_access) {
// let them access the app
} else {
// Navigate away
}
}
}
});
// Get the userId and email of logged in user
const supabaseAuthContext = useSupabaseAuthContext()!;
const userId = supabaseAuthContext.supabaseSession?.user.id;
const userEmail = supabaseAuthContext.supabaseSession?.user.email;

createEffect(async () => {
if (userId && userEmail) {
const userRecord = await getUserRecord(userId);
if (userRecord) {
if (userRecord.has_app_access) {
// let them access the app
} else {
// Navigate away
}
}
}
});
Here's the getUserRecord() function:
export async function getUserRecord(userId: string) {
"use server";
// Get Supabase Admin Client
const supabaseAdmin = getSupabaseAdminClient();

if (supabaseAdmin) {
// Query supabase
const { data, error } = await supabaseAdmin
.from("users")
.select()
.eq("auth_user_id", userId);

if (error) {
console.log(error.message);
}
return data;
}
}
export async function getUserRecord(userId: string) {
"use server";
// Get Supabase Admin Client
const supabaseAdmin = getSupabaseAdminClient();

if (supabaseAdmin) {
// Query supabase
const { data, error } = await supabaseAdmin
.from("users")
.select()
.eq("auth_user_id", userId);

if (error) {
console.log(error.message);
}
return data;
}
}
For context, here's the getSupabaseAdminClient() function:
export function getSupabaseAdminClient() {
"use server";

// Get the environment variables
const projectURL = process.env.SUPABASE_PROJECT_URL;
const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE;
if (!projectURL || !supabaseServiceRole) {
// Log error
console.error("Unable to initialize Supabase Auth Admin Client");
return;
}

return createClient(projectURL, supabaseServiceRole, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
});
}
export function getSupabaseAdminClient() {
"use server";

// Get the environment variables
const projectURL = process.env.SUPABASE_PROJECT_URL;
const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE;
if (!projectURL || !supabaseServiceRole) {
// Log error
console.error("Unable to initialize Supabase Auth Admin Client");
return;
}

return createClient(projectURL, supabaseServiceRole, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
});
}
50 replies
SSolidJS
Created by ChrisThornham on 7/8/2024 in #support
<Title> Component Throwing Error. Can you help?
I built an SEO component that simplifies adding SEO to pages. Unfortunately, I'm now getting the following error on every page: "seo.tsx:20 computations created outside a createRoot or render will never be disposed" The <Title> component is causing the error.
<Title>
{props.seoData.title} - {brandName}
</Title>
<Title>
{props.seoData.title} - {brandName}
</Title>
Why? Here's the full component for context:
import { SEOType } from "~/types/seo";
import { brandName, domain } from "../../../../quickstart.config";
import { Link, Meta, Title } from "@solidjs/meta";
import { Show } from "solid-js";

interface SEOProps {
seoData: SEOType;
}

export default function SEO(props: SEOProps) {
// VARIABLES ========================================================
const fullUrl = props.seoData.urlSlug
? `${domain}/blog/${props.seoData.urlSlug}`
: "";

// TSX ==============================================================
return (
<>
{/* General */}
<Title>
{props.seoData.title} - {brandName}
</Title>
<Show
when={props.seoData.index}
fallback={<Meta name="robots" content="noindex" />}
>
<Meta name="robots" content="index" />
</Show>
<Show
when={props.seoData.follow}
fallback={<Meta name="robots" content="nofollow" />}
>
<Meta name="robots" content="follow" />
</Show>
<Meta name="description" content={props.seoData.description} />

<Meta property="og:description" content={props.seoData.description} />
<Meta
property="og:title"
content={`${props.seoData.title} - ${brandName}`}
/>
<Meta property="og:type" content="website" />
<Show when={props.seoData.urlSlug}>
<Meta property="og:url" content={fullUrl} />
</Show>
<Meta property="og:site_name" content={brandName} />

<Show when={props.seoData.urlSlug}>
<Link rel="canonical" href={fullUrl} />
</Show>
</>
);
}
import { SEOType } from "~/types/seo";
import { brandName, domain } from "../../../../quickstart.config";
import { Link, Meta, Title } from "@solidjs/meta";
import { Show } from "solid-js";

interface SEOProps {
seoData: SEOType;
}

export default function SEO(props: SEOProps) {
// VARIABLES ========================================================
const fullUrl = props.seoData.urlSlug
? `${domain}/blog/${props.seoData.urlSlug}`
: "";

// TSX ==============================================================
return (
<>
{/* General */}
<Title>
{props.seoData.title} - {brandName}
</Title>
<Show
when={props.seoData.index}
fallback={<Meta name="robots" content="noindex" />}
>
<Meta name="robots" content="index" />
</Show>
<Show
when={props.seoData.follow}
fallback={<Meta name="robots" content="nofollow" />}
>
<Meta name="robots" content="follow" />
</Show>
<Meta name="description" content={props.seoData.description} />

<Meta property="og:description" content={props.seoData.description} />
<Meta
property="og:title"
content={`${props.seoData.title} - ${brandName}`}
/>
<Meta property="og:type" content="website" />
<Show when={props.seoData.urlSlug}>
<Meta property="og:url" content={fullUrl} />
</Show>
<Meta property="og:site_name" content={brandName} />

<Show when={props.seoData.urlSlug}>
<Link rel="canonical" href={fullUrl} />
</Show>
</>
);
}
7 replies
SSolidJS
Created by ChrisThornham on 7/1/2024 in #support
Is My Understanding Of RouteSectionProps Correct?
I’ve been trying to implement the cache, load, and createAsync pattern on all of my pages that load data. My goal is to load data on hover before navigating to the page. When the cache function does not need a route parameter, this pattern works as expected. BUT, I struggled to get this pattern working when I needed to pass my cache function a route param. After reading through the docs, I "thought" that I should use the useParams() primitive to get the route params, then pass that route param to the function inside of createAsync(). That didn't work. I dug through the HackerNews example and noticed the use of RouteSectionProps. With RouteSectionProps it does work. So... I want to make sure I understand WHY these two examples differ. When using useParams() I'm guessing that you only get access to the route params AFTER the component loads... meaning the params aren't available on hover. But, when using RouteSectionProps it appears that you get access to the route params BEFORE the component loads... which is why load on hover works. Am I correct? Here's my final implementation.
const getUser = cache(async (email: string): Promise<User> => {
"use server";
const { data, error } = await supabase
.from("users")
.select()
.eq("customer_email", email)
.single();

if (error) {
console.log(error.message);
throw error;
}

return data as User;
}, "user");

export const route: RouteDefinition = {
load({ params }) {
void getUser(params.email);
},
};

export default function UserDetailsPage(props: RouteSectionProps) {
const user = createAsync(() => getUser(props.params.email));
...
}
const getUser = cache(async (email: string): Promise<User> => {
"use server";
const { data, error } = await supabase
.from("users")
.select()
.eq("customer_email", email)
.single();

if (error) {
console.log(error.message);
throw error;
}

return data as User;
}, "user");

export const route: RouteDefinition = {
load({ params }) {
void getUser(params.email);
},
};

export default function UserDetailsPage(props: RouteSectionProps) {
const user = createAsync(() => getUser(props.params.email));
...
}
10 replies
SSolidJS
Created by ChrisThornham on 6/30/2024 in #support
Help Me Solve "undefined" warning When Using <Show> Component
I'm using createAsync() to get and order record.
const order = createAsync(() => getOrder(params.chargeId));
const order = createAsync(() => getOrder(params.chargeId));
order has the following type: const order: Accessor<Order | undefined> Because order could be undefined, I must check for that in my jsx. I'm trying to do that with <Show when={order()}> I thought adding the when={order()} would return false if order() was undefined, and satisfy typescript, but typescript is still complaining with this message:
Type 'Accessor<Order | undefined>' is not assignable to type 'Accessor<Order>'
Type 'Accessor<Order | undefined>' is not assignable to type 'Accessor<Order>'
So, how can I get rid of this "undefined" error? I'm trying to do this "the solid way," and I can't figure it out. I get the undefined error when trying to pass the order to a component like this: <OrderTable order={order} /> Here's the full component for context.
import { Order } from "~/types/order";
// CACHE ============================================================
const getOrder = cache(async (chargeId: string) => {
"use server";
const { data } = await supabase
.from("orders")
.select()
.eq("stripe_charge_id", chargeId)
.single();
if (!data) {
return undefined;
} else {
return data as Order;
}
}, "order");

export default function OrderDetailsPage() {
const params = useParams();
const order = createAsync(() => getOrder(params.chargeId));
return (
<>
<h4>Order Details</h4>
<Show when={order()} fallback="Order loading...">
<OrderTable order={order} />
</Show>
</>
);
}
import { Order } from "~/types/order";
// CACHE ============================================================
const getOrder = cache(async (chargeId: string) => {
"use server";
const { data } = await supabase
.from("orders")
.select()
.eq("stripe_charge_id", chargeId)
.single();
if (!data) {
return undefined;
} else {
return data as Order;
}
}, "order");

export default function OrderDetailsPage() {
const params = useParams();
const order = createAsync(() => getOrder(params.chargeId));
return (
<>
<h4>Order Details</h4>
<Show when={order()} fallback="Order loading...">
<OrderTable order={order} />
</Show>
</>
);
}
3 replies
SSolidJS
Created by ChrisThornham on 6/13/2024 in #support
What Replaced createRouteData?
In older version of SolidStart you could get all routes in a folder. This was useful for doing things like getting all blog articles. It worked like this:
export const routeData = () => {
return createRouteData(async () => {
const files = import.meta.glob('.blog/*.mdx')
})
}
export const routeData = () => {
return createRouteData(async () => {
const files = import.meta.glob('.blog/*.mdx')
})
}
But createRouteData no longer exists in the latest version of SolidStart. How could I do the same thing with the newest version of SolidStart? Thanks, Chris
5 replies
SSolidJS
Created by ChrisThornham on 6/5/2024 in #support
Is It Possible To Pass An Extra Parameter To A Form Action?
I'm trying to do this:
const userId = getUserId();

return (
<form action={deleteUser(userId) method="post">
<button>Delete User</button>
</form>
);
const userId = getUserId();

return (
<form action={deleteUser(userId) method="post">
<button>Delete User</button>
</form>
);
Here's the action:
export const deleteUser = action(async (userId: string) => {
await auth.deleteAccount(userId);
});
export const deleteUser = action(async (userId: string) => {
await auth.deleteAccount(userId);
});
I keep getting the following errors on the "action" of the form: Type 'Action<[userId: string], void>' is not assignable to type 'string | SerializableAttributeValue | undefined'.ts(2322) The expected type comes from property 'action' which is declared here on type 'FormHTMLAttributes<HTMLFormElement>' (property) JSX.FormHTMLAttributes<HTMLFormElement>.action?: string | JSX.SerializableAttributeValue | undefined
5 replies
SSolidJS
Created by ChrisThornham on 5/21/2024 in #support
Trouble With onCleanup()
I'm building a checkout flow with Stripe's embedded checkout form. It works like this. The user clicks a "Buy Now" button on a products page that navigates to a checkout page. The checkout page: - Gets the product ID and quantity from the search params. - Gets the checkout form from stripe - Mounts the checkout form. This works the first time I click a buy now button. BUT If I click back to the products page and click another buy now button, the checkout form doesn't load. I'm getting an error from Stripe: "You cannot have multiple embedded checkout items." So, how do I "clear" or unmount the checkout form when I click the back button? I want to start fresh each time I click a buy now button. My use of onCleanup (below) isn't working. Here's my checkout page:
export default function CheckoutPage() {

const [searchParams] = useSearchParams();

// ref
let checkoutElement: HTMLDivElement;

createEffect(async () => {
// extract the search params
const itemsArray = [
{
price: searchParams.priceId,
quantity: searchParams.qty,
},
];

// Stripe Stuff
// Create a checkout session
const response = await fetch("api/stripe/create-checkout-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
items: itemsArray,
}),
});

// Create checkout form
const { clientSecret } = await response.json();
const checkout = await stripe?.initEmbeddedCheckout({
clientSecret,
});

// Mount form
if (checkoutElement) {
checkout?.mount(checkoutElement);
}

// unmount the form
onCleanup(() => {
checkout?.unmount();
});
});

return (
<MainLayout>
<div
id="checkout"
ref={(el) => checkoutElement = el}
class="px-6 py-20 bg-[#0f151d]"
>
{/* Checkout will insert payment form here */}
</div>
</MainLayout>
);
}
export default function CheckoutPage() {

const [searchParams] = useSearchParams();

// ref
let checkoutElement: HTMLDivElement;

createEffect(async () => {
// extract the search params
const itemsArray = [
{
price: searchParams.priceId,
quantity: searchParams.qty,
},
];

// Stripe Stuff
// Create a checkout session
const response = await fetch("api/stripe/create-checkout-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
items: itemsArray,
}),
});

// Create checkout form
const { clientSecret } = await response.json();
const checkout = await stripe?.initEmbeddedCheckout({
clientSecret,
});

// Mount form
if (checkoutElement) {
checkout?.mount(checkoutElement);
}

// unmount the form
onCleanup(() => {
checkout?.unmount();
});
});

return (
<MainLayout>
<div
id="checkout"
ref={(el) => checkoutElement = el}
class="px-6 py-20 bg-[#0f151d]"
>
{/* Checkout will insert payment form here */}
</div>
</MainLayout>
);
}
30 replies
SSolidJS
Created by ChrisThornham on 5/13/2024 in #support
How Do I Update The createAsync() Signal?
I've been trying to use cache(), load(), and createAsync() because this pattern appears to be the preferred method for fetching data with SolidStart. Unfortunately, I can't figure out how to update the createAsync() signal. When using createStore() you get access to a getter and a setter:
const [ getNotes, setNotes ] = createStore([]);
const [ getNotes, setNotes ] = createStore([]);
But when using createAsync() I only get access to a getter.
const notes = createAsync(() => getNotes());
const notes = createAsync(() => getNotes());
How can I update the getter "notes" when using createAsync()?
33 replies
SSolidJS
Created by ChrisThornham on 5/10/2024 in #support
form action Breaking On Second Submit
To learn about actions, I built a simple notes app. The app displays a list of notes. When you click an edit button, the note text turns into a text input that can be edited. You can see what I'm describing in this code.
<Show
when={isEditing()}
fallback={<div>{props.noteText}</div>}
>
<form
action={updateNote}
method="post"
>
{/* New Note Input */}
<input
type="text"
name="note"
value={props.noteText}
placeholder={props.noteText}
/>
<input type="hidden" name="noteId" value={props.noteId} />
{/* Submit Button */}
<button>Save</button>
</form>
</Show>
<Show
when={isEditing()}
fallback={<div>{props.noteText}</div>}
>
<form
action={updateNote}
method="post"
>
{/* New Note Input */}
<input
type="text"
name="note"
value={props.noteText}
placeholder={props.noteText}
/>
<input type="hidden" name="noteId" value={props.noteId} />
{/* Submit Button */}
<button>Save</button>
</form>
</Show>
When you click save, the form calls the updateNote action.
const updateNote = action(async (formData: FormData) => {
// get note data
const newNote = formData.get("note");
const noteId = formData.get("noteId");

// update note in database...

setIsEditing(false);
});
const updateNote = action(async (formData: FormData) => {
// get note data
const newNote = formData.get("note");
const noteId = formData.get("noteId");

// update note in database...

setIsEditing(false);
});
The first time I perform an edit, the action works as expected. The edit is made and I stay on the same page. But if I try to make a second edit, the browser navigates to an action URL like this https://action/640465549 and the page shows a "This site can't be reached" message. I've noticed the same error occurs if you forget to include method="post" in your form. But in this case, I definitely have method="post" in my form. Does anyone know why this form is failing on the second submit? Thanks, Chris
4 replies
SSolidJS
Created by ChrisThornham on 5/9/2024 in #support
revalidate() Not Working As Expected
I'm fetching data from Supabase and using their Realtime feature so I can see live updates. To do that I'm using cache(), createAsync(), and revalidate() It looks like this: cache function:
const getNotes = cache(async () => {
"use server";
// get data
const { data } = await supabase.from("notes").select();
// return data
return data;
}, "notes");
const getNotes = cache(async () => {
"use server";
// get data
const { data } = await supabase.from("notes").select();
// return data
return data;
}, "notes");
createAsync function:
const notes = createAsync(() => getNotes());
const notes = createAsync(() => getNotes());
Here's where I'm confused. revalidate() works no matter what value I pass it. So all of these options will revalidate the notes Signal from createAsync. 1. revalidate() 2. revalidate("notes") 3. revalidate("some random string") I only expected option #2 to work. What am I missing? Thanks, Chris
10 replies
SSolidJS
Created by ChrisThornham on 4/25/2024 in #support
Help Me Stop Theme Flashing In SolidStart
I built a theme switcher using SolidStart. It works great, with one exception. On page reload, the theme flickers or flashes. It's happening because I'm using SSR mode. As a result, my ThemeProvider doesn't load the theme until the page loads. I believe I need to check the theme on the server so I can set the theme before my page loads using something like https://github.com/donavon/use-dark-mode/blob/develop/noflash.js.txt. Next.js has the next-themes package that does this for you. https://github.com/pacocoursey/next-themes I'm wondering how I can do this? Can anyone point me in the right direction? Or share some example code? I've pasted my ThemeProvider code in the next post:
20 replies
SSolidJS
Created by ChrisThornham on 4/15/2024 in #support
Why Would I Use An Action For Anything Other Than Form Submissions?
I’m struggling to understand why I would use an action for anything other than handling a form submission. Actions give you access to FormData, which simplifies working with forms. So, I can see a clear use case with forms. But why else would I use an action? Let me try to add some context to my confusion. The solid docs give the following example. I can use an action to do the following
import { action, useAction } from "@solidjs/router";

const echo = action(async (message: string) => {
// Imagine this is a call to fetch
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
console.log(message);
});

export default function MyComponent() {
const myEcho = useAction(echo);
myEcho("Hello from solid!");
}
import { action, useAction } from "@solidjs/router";

const echo = action(async (message: string) => {
// Imagine this is a call to fetch
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
console.log(message);
});

export default function MyComponent() {
const myEcho = useAction(echo);
myEcho("Hello from solid!");
}
BUT, I could also use a regular async function to achieve the same outcome in fewer lines of code.
async function echo(message: string) {
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
console.log(message);
}

export default function MyComponent() {
echo("Hello from solid!");
}
async function echo(message: string) {
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
console.log(message);
}

export default function MyComponent() {
echo("Hello from solid!");
}
Additionally, the docs say: "after submitting data the server sends some data back as well. Usually an error message if something failed. Anything returned from your action function can be accessed using the reactive action.result property. " BUT, I can also use a try/catch block in a regular async function to return an error message. So, what's the point of actions outside of working with forms? Am I missing something? Or are actions just an alternative way to do what regular async functions can do? Thanks, Chris
22 replies