S
SolidJS4mo ago
mizark

<For> throws Cannot access 'product2' before initialization

I am wrapping my component in a show on product().name and have used debugger to step through and verify the Component is not run until product().name is set, yet I am getting this error originating from the <For> below:
<For each={product().imgs} fallback="loading">
{(item, index) => {
const product = product();
const url = item.url;
const isFirst = index() == 0;
const isLast = index() == product.imgs.length - 1;

return (
<img
class="product-sub-imgs"
src={item.url}
onMouseEnter={() => handleChangeMainImg(item.url)}
style={{
border: (
mainSrc()
? mainSrc() === item.url
: product.main_img.url === item.url
)
? "solid black 2px"
: "solid white 2px",
"margin-left": isFirst && "0px",
"margin-right": isLast && "0px",
cursor: "pointer",
}}
/>
);
}}
</For>
<For each={product().imgs} fallback="loading">
{(item, index) => {
const product = product();
const url = item.url;
const isFirst = index() == 0;
const isLast = index() == product.imgs.length - 1;

return (
<img
class="product-sub-imgs"
src={item.url}
onMouseEnter={() => handleChangeMainImg(item.url)}
style={{
border: (
mainSrc()
? mainSrc() === item.url
: product.main_img.url === item.url
)
? "solid black 2px"
: "solid white 2px",
"margin-left": isFirst && "0px",
"margin-right": isLast && "0px",
cursor: "pointer",
}}
/>
);
}}
</For>
product().imgs are loaded and can never reach a break point within the For child function so breaks before I can check there. Any ideas?
4 Replies
lxsmnsyc
lxsmnsyc4mo ago
I would assume product() is a resource and so the error comes from the fact that it is initially undefined (becomes undefined.imgs which is the error). Resources being initially undefined is natural. The workaround is as simple as: product()?.imgs
mizark
mizarkOP4mo ago
Hmm I had to do product() && and it at least moved through the error but nothing loads just goes to my fallback even though the data is there on the server side before responding to the client on initial request... product is a signal set on initial render from result of a create async/ route data. I inject it in context and import it into this file and the parent like this: (layout).jsx:
const getProductData = cache(async (pathname) => {
"use-server";
const productPages = [
"/",
"/login",
"/sign-up",
"/exercise",
"/food",
"/sleep",
"/supplements",
"/water",
];

const splits = pathname.split("/");
const isProduct = splits[1] === "product";
if (productPages.includes(pathname)) {
const allProds = await getAllProducts(pathname);
return { product: false, ...allProds };
} else if (isProduct) {
const id = splits[2];
const prod = await getProduct(id);
return { ...prod, products: false, category_tags: false };
} else {
return { products: false, product: false, category_tags: false };
}
});

export const route = {
preload: ({ params, location, intent }) => { getProductData(location.pathname);
},
};

export default function Layout(props) {
const data = createAsync(async () => {
let pathname = useLocation().pathname;
let p = await getProductData(pathname);
return { ...u, ...p };
});

let initialProduct = {};

if (isServer) {
if (data()) {
initialProduct = data().product;
}
}

const [product, setProduct] = createSignal(initialProduct);

const getProductData = cache(async (pathname) => {
"use-server";
const productPages = [
"/",
"/login",
"/sign-up",
"/exercise",
"/food",
"/sleep",
"/supplements",
"/water",
];

const splits = pathname.split("/");
const isProduct = splits[1] === "product";
if (productPages.includes(pathname)) {
const allProds = await getAllProducts(pathname);
return { product: false, ...allProds };
} else if (isProduct) {
const id = splits[2];
const prod = await getProduct(id);
return { ...prod, products: false, category_tags: false };
} else {
return { products: false, product: false, category_tags: false };
}
});

export const route = {
preload: ({ params, location, intent }) => { getProductData(location.pathname);
},
};

export default function Layout(props) {
const data = createAsync(async () => {
let pathname = useLocation().pathname;
let p = await getProductData(pathname);
return { ...u, ...p };
});

let initialProduct = {};

if (isServer) {
if (data()) {
initialProduct = data().product;
}
}

const [product, setProduct] = createSignal(initialProduct);

(layout)/(page)/product/[id].jsx
import AppContext from "../../../../context/appContext";
import { useContext } from "solid-js";

export default function ProductRoute() {
const { product } = useContext(AppContext);
return (
<>
<Show when={product().name} fallback="loading">
<Product />
</Show>
</>
);
}
import AppContext from "../../../../context/appContext";
import { useContext } from "solid-js";

export default function ProductRoute() {
const { product } = useContext(AppContext);
return (
<>
<Show when={product().name} fallback="loading">
<Product />
</Show>
</>
);
}
Product.jsx
import AppContext from "../../context/appContext";
import { useContext, For, createSignal } from "solid-js";

const Product = () => {
const { addToCart, products, product } = useContext(AppContext);

...
import AppContext from "../../context/appContext";
import { useContext, For, createSignal } from "solid-js";

const Product = () => {
const { addToCart, products, product } = useContext(AppContext);

...
lxsmnsyc
lxsmnsyc4mo ago
ideally that initialProduct wouldn't work given the nature of createAsync (createResource)'s non-blocking behavior
mizark
mizarkOP4mo ago
Yeah I had to get rid of that and just use the data() from createAsync(). I guess if I want to destructure data() and use in context I should just use a store?
Want results from more Discord servers?
Add your server