Kasper
Kasper
SSolidJS
Created by Kasper on 1/11/2024 in #support
API Proxy in SolidStart
In my application we have a dedicated API backend, on api.example.com, in our vite configuration we have set up a proxy forward with the config below. However when we deply the server with the content from dist/ it dose not use vite to serve the content, but the SolidStart server, are there a way to this "production" to also proxy the requests?
export default defineConfig({
plugins: [solid({ ssr: false })],
ssr: { external: ['@prisma/client'] },
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {
target: '[my-link]',
changeOrigin: true,
secure: false,
ws: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
resolve: {
alias: {
'~': path.resolve(__dirname, 'src'),
},
},
});
export default defineConfig({
plugins: [solid({ ssr: false })],
ssr: { external: ['@prisma/client'] },
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {
target: '[my-link]',
changeOrigin: true,
secure: false,
ws: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
resolve: {
alias: {
'~': path.resolve(__dirname, 'src'),
},
},
});
2 replies
SSolidJS
Created by Kasper on 5/21/2023 in #support
Running tests in docker fails, but works outside of docker
I am attempting to set up a Docker development environment for a SolidJS application using Docker Compose. When I access the webpage via localhost:3000, the application renders without issues. However, when I attempt to run npm test using vitest inside the Docker container, I run into a problem. The issue appears to be with the render function, as it does not seem to render the application correctly within the Docker container. The debug output only shows <body><div /><body>. Interestingly, when I run npm test outside the Docker container on my local machine, the test passes just fine and the debug correctly renders the application. Here's the relevant code from my test file: My test file:
import { render } from "@solidjs/testing-library";
import App from "../src/App";
import { describe, expect, it } from "vitest";
import "@testing-library/jest-dom"; // 👈 this is imported in order to use the jest-dom matchers

describe("App", () => {
it("should render the app", () => {
const { getByText, debug } = render(() => <App />);
debug();
expect(getByText("MagicDoor.com")).toBeInTheDocument();
});
});
import { render } from "@solidjs/testing-library";
import App from "../src/App";
import { describe, expect, it } from "vitest";
import "@testing-library/jest-dom"; // 👈 this is imported in order to use the jest-dom matchers

describe("App", () => {
it("should render the app", () => {
const { getByText, debug } = render(() => <App />);
debug();
expect(getByText("MagicDoor.com")).toBeInTheDocument();
});
});
The App.tsx file:
import type { Component } from "solid-js";

import logo from "./assets/magicaldoor.png";

const App: Component = () => {
return (
<div class="text-center">
<header class="bg-gray-800 min-h-screen flex flex-col items-center justify-center text-white text-3xl">
<img src={logo} class="animate-spin h-[40vmin] pointer-events-none" alt="logo" />
<span>MagicDoor.com</span>
</header>
</div>
);
};

export default App;
import type { Component } from "solid-js";

import logo from "./assets/magicaldoor.png";

const App: Component = () => {
return (
<div class="text-center">
<header class="bg-gray-800 min-h-screen flex flex-col items-center justify-center text-white text-3xl">
<img src={logo} class="animate-spin h-[40vmin] pointer-events-none" alt="logo" />
<span>MagicDoor.com</span>
</header>
</div>
);
};

export default App;
The npm test output
/app # npm test

> vite-template-solid@0.0.0 test
> vitest


DEV v0.31.1 /app

stdout | tests/App.test.tsx > App > should render the app
<body>
<div />
</body>

❯ tests/App.test.tsx (1)
❯ App (1)
× should render the app
⠸ [ afterEach ]

Failed Tests 1

FAIL tests/App.test.tsx > App > should render the app
TestingLibraryElementError: Unable to find an element with the text: MagicDoor.com. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<div />
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ getByText node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:10:12
8| const { getByText, debug } = render(() => <App />);
9| debug();
10| expect(getByText("MagicDoor.com")).toBeInTheDocument();
| ^
11| });
12| });

[1/2]⎯

FAIL tests/App.test.tsx > App > should render the app
TypeError: dispose is not a function
❯ cleanupAtContainer node_modules/@solidjs/testing-library/dist/index.js:96:3
94| function cleanupAtContainer(ref) {
95| const { container, dispose } = ref;
96| dispose();
| ^
97| if (container?.parentNode === document.body) {
98| document.body.removeChild(container);
❯ cleanup node_modules/@solidjs/testing-library/dist/index.js:103:21
❯ node_modules/@solidjs/testing-library/dist/index.js:17:13
/app # npm test

> vite-template-solid@0.0.0 test
> vitest


DEV v0.31.1 /app

stdout | tests/App.test.tsx > App > should render the app
<body>
<div />
</body>

❯ tests/App.test.tsx (1)
❯ App (1)
× should render the app
⠸ [ afterEach ]

Failed Tests 1

FAIL tests/App.test.tsx > App > should render the app
TestingLibraryElementError: Unable to find an element with the text: MagicDoor.com. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<div />
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ getByText node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:10:12
8| const { getByText, debug } = render(() => <App />);
9| debug();
10| expect(getByText("MagicDoor.com")).toBeInTheDocument();
| ^
11| });
12| });

[1/2]⎯

FAIL tests/App.test.tsx > App > should render the app
TypeError: dispose is not a function
❯ cleanupAtContainer node_modules/@solidjs/testing-library/dist/index.js:96:3
94| function cleanupAtContainer(ref) {
95| const { container, dispose } = ref;
96| dispose();
| ^
97| if (container?.parentNode === document.body) {
98| document.body.removeChild(container);
❯ cleanup node_modules/@solidjs/testing-library/dist/index.js:103:21
❯ node_modules/@solidjs/testing-library/dist/index.js:17:13
My docker files:
# pull official base image
FROM node:20.2-alpine3.16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
# pull official base image
FROM node:20.2-alpine3.16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
6 replies
SSolidJS
Created by Kasper on 2/24/2023 in #support
How to update store with arary
When updating a store that is an array is this not the correct way updating one of the items? When i do this, no error is thrown but my view is not updated
const [uploadFiles, setUploadFiles] = createStore<FileUpload[]>([]);

...

setUploadFiles((f) => file.fileName === f.fileName, "status", (s) => "uploading");

...

<For each={uploadFiles}>
{(file: FileUpload) => (
<div>
{file.file.name} - {file.status}
</div>
)}
</For>
const [uploadFiles, setUploadFiles] = createStore<FileUpload[]>([]);

...

setUploadFiles((f) => file.fileName === f.fileName, "status", (s) => "uploading");

...

<For each={uploadFiles}>
{(file: FileUpload) => (
<div>
{file.file.name} - {file.status}
</div>
)}
</For>
2 replies
SSolidJS
Created by Kasper on 2/20/2023 in #support
Component is not updated when prop is changed
I have a component "ShowDocument" with a resource inside, the component has a variables that needs to used in the resources. The component is called with the following: <ShowDocument documentId={showDoc()} onClose={closePanel} /> my issues is that when signal showDoc() in the prop "documentId" is updated the component ShowDocument is not updated, and will continue to show what was there when it was first rendered.
const ShowDocument = (props: { documentId: string | undefined; onClose: () => void }) => {
console.log("ShowDocument: ", props.documentId);
const { getSecureDocumentLink } = useDocuments();

const [documentLink] = createResource(props.documentId, async (documentId) => {
console.log("Fetching new document link: ", documentId);
if (!documentId) {
return undefined;
}
console.log("Getting secure link for document: ", documentId);
let link = await getSecureDocumentLink(documentId, false);
console.log("Got secure link: ", link);
return link;
});

return (
<div class="flex flex-col h-full w-full">
<div>
<button onClick={props.onClose} class="bg-blue-500 rounded font-bold p-2 border-solid border-2 border-blue-600 hover:bg-blue-600">
Close
</button>
</div>
<div class="flex-1">
<Show when={documentLink() && !documentLink.loading} fallback={<span>Loading...</span>}>
<iframe src={documentLink()} class=" h-full w-full"></iframe>
</Show>
</div>
</div>
);
};
const ShowDocument = (props: { documentId: string | undefined; onClose: () => void }) => {
console.log("ShowDocument: ", props.documentId);
const { getSecureDocumentLink } = useDocuments();

const [documentLink] = createResource(props.documentId, async (documentId) => {
console.log("Fetching new document link: ", documentId);
if (!documentId) {
return undefined;
}
console.log("Getting secure link for document: ", documentId);
let link = await getSecureDocumentLink(documentId, false);
console.log("Got secure link: ", link);
return link;
});

return (
<div class="flex flex-col h-full w-full">
<div>
<button onClick={props.onClose} class="bg-blue-500 rounded font-bold p-2 border-solid border-2 border-blue-600 hover:bg-blue-600">
Close
</button>
</div>
<div class="flex-1">
<Show when={documentLink() && !documentLink.loading} fallback={<span>Loading...</span>}>
<iframe src={documentLink()} class=" h-full w-full"></iframe>
</Show>
</div>
</div>
);
};
34 replies
SSolidJS
Created by Kasper on 2/18/2023 in #support
Right way of getting errors out of async context functions
I have a context that has methods that are async, the method itself on the context is sync, but that calls an Async method. This async method can throw an Unauthorized exception, it that happens i would like to redirect the user to "/signin". Would the correct way here be to have "error" signal that is read somewhere else on the UI thread and if that contains an error throw it, or are there a better way to do this?
export function PortfolioProvider(props: ProviderProps) {
const { user, isAuthenticated } = useUser();

const [loading, setLoading] = createSignal(false);
const [transactions, setTransactions] = createSignal<Transaction[]>();
const [pagination, setPagination] = createSignal<Pagination>();
const [currentRequest, setCurrentRequest] = createSignal<TransactionRequest>();

createEffect(() => {
console.log("Fetching transactions", currentRequest());
if (isAuthenticated() && currentRequest()) {
_fetch(currentRequest());
}
});

const _fetch = async (request: TransactionRequest | undefined) => {
if (!request) return;
setLoading(true);
try {
let response = await urqlClient(ContextHelpers.getAuthToken()).query(Queries.GetTransactions, request).toPromise();
ContextHelpers.verifyGraphQLReponse(response);
setTransactions(response.data.transactions.transactions as Transaction[]);
setPagination(response.data.transactions.pagination as Pagination);
} finally {
setLoading(false);
}
};

const response = {
transactions,
loading,
fetch: (request: TransactionRequest) => {
setCurrentRequest(request);
},
};

return <TransactionContext.Provider value={response}>{props.children}</TransactionContext.Provider>;
}
export function PortfolioProvider(props: ProviderProps) {
const { user, isAuthenticated } = useUser();

const [loading, setLoading] = createSignal(false);
const [transactions, setTransactions] = createSignal<Transaction[]>();
const [pagination, setPagination] = createSignal<Pagination>();
const [currentRequest, setCurrentRequest] = createSignal<TransactionRequest>();

createEffect(() => {
console.log("Fetching transactions", currentRequest());
if (isAuthenticated() && currentRequest()) {
_fetch(currentRequest());
}
});

const _fetch = async (request: TransactionRequest | undefined) => {
if (!request) return;
setLoading(true);
try {
let response = await urqlClient(ContextHelpers.getAuthToken()).query(Queries.GetTransactions, request).toPromise();
ContextHelpers.verifyGraphQLReponse(response);
setTransactions(response.data.transactions.transactions as Transaction[]);
setPagination(response.data.transactions.pagination as Pagination);
} finally {
setLoading(false);
}
};

const response = {
transactions,
loading,
fetch: (request: TransactionRequest) => {
setCurrentRequest(request);
},
};

return <TransactionContext.Provider value={response}>{props.children}</TransactionContext.Provider>;
}
2 replies
SSolidJS
Created by Kasper on 2/18/2023 in #support
Avoid Provider nesting
I am going to have a lot of Providers in my App, and i did not want to have 5+ nesting around my rotes. Here is the example with just 2 providers:
return (
<ErrorBoundary fallback={(err, reset) => <div onClick={reset}>Error: {err.toString()}</div>}>
<PortfolioProvider>
<UserProvider>
<Routes>
<Route path="/" element={PageLayout}>
<Route path="/" element={<h1>Home</h1>} />
<Route path="/about" element={<h1>About</h1>} />
<Route path="/users" element={<h1>Users</h1>} />
<Route path="/signin" element={SignIn} />
</Route>
</Routes>
</UserProvider>
</PortfolioProvider>
</ErrorBoundary>
);
return (
<ErrorBoundary fallback={(err, reset) => <div onClick={reset}>Error: {err.toString()}</div>}>
<PortfolioProvider>
<UserProvider>
<Routes>
<Route path="/" element={PageLayout}>
<Route path="/" element={<h1>Home</h1>} />
<Route path="/about" element={<h1>About</h1>} />
<Route path="/users" element={<h1>Users</h1>} />
<Route path="/signin" element={SignIn} />
</Route>
</Routes>
</UserProvider>
</PortfolioProvider>
</ErrorBoundary>
);
So i though i could put all my providers into to a single component, and then only have 1 "Provider" around my Routes. However when i do this, i can see from logging that my providers are created, but non of the sub-components have assess to the useUser or useProtfolios as they are undefined. This works fine when i have the providers around my rote but not when i wrap them in a component. Should the following not work?
import { JSX } from "solid-js";
import { UserProvider } from "./contexts/userContext";
import { PortfolioProvider } from "./contexts/portfolioContext";

interface ProviderProps {
children: JSX.Element;
}

const AppProviders = ({ children }: ProviderProps) => {
console.log("Adding context providers");
return (
<UserProvider>
<PortfolioProvider>{children}</PortfolioProvider>
</UserProvider>
);
};

export default AppProviders;
import { JSX } from "solid-js";
import { UserProvider } from "./contexts/userContext";
import { PortfolioProvider } from "./contexts/portfolioContext";

interface ProviderProps {
children: JSX.Element;
}

const AppProviders = ({ children }: ProviderProps) => {
console.log("Adding context providers");
return (
<UserProvider>
<PortfolioProvider>{children}</PortfolioProvider>
</UserProvider>
);
};

export default AppProviders;
App.tsx i replace the 2 providers with <AppProviders>...</AppProviders>
4 replies
SSolidJS
Created by Kasper on 2/18/2023 in #support
Guide for more complex usage of stores
Were can i find a more complex guid of how to use SolidJS stores. The example i wanted to do is to have a store of transactions, i will have a component to list all transactions, a component to create a new transaction with a parent page to contain it all. Down in the list component is a child component for each transaction. In the child component i can delete the given transaction, when this is done i would like to send a signal to the store that it needs to re-fetch the list of transactions, same when a new transaction is added. From what i understand from the tutorials i need to have the store i a separate file that gets included. But i can't really figure out what the best practice is here, and how can i have a single place where "fetch" logic is, so i don't have to copy it for the child component and the new transaction component.
30 replies
SSolidJS
Created by Kasper on 1/9/2023 in #support
Resource not being called when componet loads
I have create a resource, that i want to update when the "cursor" is changed. I followed the tutorial, but this dose not load when the componet loads.
const fetchSubjects = async (source: any, { value, refetching }: any) => {
console.log("fetching subjects", source, value);
return await await urqlClient().query(GET_SUBJECTS, { after: "", first: 2 }).toPromise();
};

export default function Subjects() {
const [cursor, setCursor] = createSignal();
const [subjects] = createResource(cursor, fetchSubjects);

....
const fetchSubjects = async (source: any, { value, refetching }: any) => {
console.log("fetching subjects", source, value);
return await await urqlClient().query(GET_SUBJECTS, { after: "", first: 2 }).toPromise();
};

export default function Subjects() {
const [cursor, setCursor] = createSignal();
const [subjects] = createResource(cursor, fetchSubjects);

....
2 replies
SSolidJS
Created by Kasper on 1/6/2023 in #support
Why dose createServerData$ return undefined
I am learning solid start, i am having some issues getting routeData and createServerData to work correctly, the below example prints Test: undefined where it should return Test: { Foo: "Bar" }
export function routeData() {
const user = createServerData$(async (_, { request }) => {
return {
Foo: "Bar",
};
});

return user;
}

export default function HeaderLayout() {
const user = useRouteData<typeof routeData>();
console.log("Test: ", user);
....
export function routeData() {
const user = createServerData$(async (_, { request }) => {
return {
Foo: "Bar",
};
});

return user;
}

export default function HeaderLayout() {
const user = useRouteData<typeof routeData>();
console.log("Test: ", user);
....
8 replies
SSolidJS
Created by Kasper on 1/5/2023 in #support
How to getUser without doing a server request on every page.
I am following the guide on https://tahazsh.com/blog/building-a-solidjs-app-from-scratch/ (expanding on the session in the https://start.solidjs.com/advanced/session) to create a authentication system in SolidJS Start. It works fine but it loads the if i want to use in a page, it dose a POST request to the backend server each time the page is loaded. How can i cache the user in the client, that also updates if the user logs out.
export function routeData() {
const user = createServerData$(async (_, { request }) => {
const user = await getUser(request);

if (!user) {
throw redirect("/auth/login");
}
return user;
});

return { user };
}
export function routeData() {
const user = createServerData$(async (_, { request }) => {
const user = await getUser(request);

if (!user) {
throw redirect("/auth/login");
}
return user;
});

return { user };
}
The get user, i though i could add it to the token, but i would have to redirect the user with the new headder to store it.
export const getUser = async (request: Request) => {
const session = await storage.getSession(request.headers.get("Cookie"));
const token = session.get("token");
if (!token) {
return null;
}

setToken(token.trim());

const result = await urqlClient().query(CURRENT_USER, {}).toPromise();

if (!result.data?.currentUser) {
return redirect("/auth/login");
}

return result.data.currentUser;
};
export const getUser = async (request: Request) => {
const session = await storage.getSession(request.headers.get("Cookie"));
const token = session.get("token");
if (!token) {
return null;
}

setToken(token.trim());

const result = await urqlClient().query(CURRENT_USER, {}).toPromise();

if (!result.data?.currentUser) {
return redirect("/auth/login");
}

return result.data.currentUser;
};
In react without SSR, it would just store the user in local storage, would that also be the right way to do here, as i guess that would cause problems with the SSR?
7 replies