S
SolidJS•2d ago
danchez

Understanding Component Render Timings with Contexts and <Show />

Seems like I have a gap in my understanding of something with regards to render timing of components when using things like <Show> with contexts. I have the following:
const Foo = () => {
// ...stuff
return (
<Chart>
<LineSeries data={[...]} />
</Chart>
)
}
const Foo = () => {
// ...stuff
return (
<Chart>
<LineSeries data={[...]} />
</Chart>
)
}
The implementation of <Chart /> is the following:
export const ChartContext = createContext<Accessor<IChartApi>>();

export const useChart = () => {
const ctx = useContext(ChartContext);

if (!ctx) {
throw new Error("Chart not found");
}

return ctx;
};

export const Chart = (props: ParentProps<ChartProps>) => {
let chartContainer!: HTMLDivElement;

const _props = mergeProps({
width: 0,
height: 0,
}, props);

const [chart, setChart] = createSignal<IChartApi>();

onMount(() => {
const chart = createChart(chartContainer, _props);
props.onCreateChart?.(chart);
setChart(chart);

createEffect(() => {
chart.applyOptions(_props);
});

onCleanup(() => {
chart.remove();
});
});

return (
<div ref={chartContainer}>
<Show when={chart()}>
{(chart) => (
<ChartContext.Provider value={chart}>
{props.children}
</ChartContext.Provider>
)}
</Show>
</div>
);
};
export const ChartContext = createContext<Accessor<IChartApi>>();

export const useChart = () => {
const ctx = useContext(ChartContext);

if (!ctx) {
throw new Error("Chart not found");
}

return ctx;
};

export const Chart = (props: ParentProps<ChartProps>) => {
let chartContainer!: HTMLDivElement;

const _props = mergeProps({
width: 0,
height: 0,
}, props);

const [chart, setChart] = createSignal<IChartApi>();

onMount(() => {
const chart = createChart(chartContainer, _props);
props.onCreateChart?.(chart);
setChart(chart);

createEffect(() => {
chart.applyOptions(_props);
});

onCleanup(() => {
chart.remove();
});
});

return (
<div ref={chartContainer}>
<Show when={chart()}>
{(chart) => (
<ChartContext.Provider value={chart}>
{props.children}
</ChartContext.Provider>
)}
</Show>
</div>
);
};
And lastly, my <LineSeries /> component is the following:
import { useChart } from "../Chart";

export const LineSeries = (props: LineSeriesProps) => {
const chart = useChart();
const [local, rest] = splitProps(props, ["data", "onCreateSeries"]);

onMount(() => {
const lineSeries = chart().addSeries(LineSeriesType);
local.onCreateSeries?.(lineSeries);

createEffect(() => {
lineSeries.setData(local.data);
});

createEffect(() => {
lineSeries.applyOptions(rest);
});

onCleanup(() => {
chart().removeSeries(lineSeries);
});
});

return null
};
import { useChart } from "../Chart";

export const LineSeries = (props: LineSeriesProps) => {
const chart = useChart();
const [local, rest] = splitProps(props, ["data", "onCreateSeries"]);

onMount(() => {
const lineSeries = chart().addSeries(LineSeriesType);
local.onCreateSeries?.(lineSeries);

createEffect(() => {
lineSeries.setData(local.data);
});

createEffect(() => {
lineSeries.applyOptions(rest);
});

onCleanup(() => {
chart().removeSeries(lineSeries);
});
});

return null
};
The issue is, when I try to run my App component that is supposed to render the Chart and LineSeries, I get the following unexpected error:
chart.tsx:10 Uncaught (in promise) Error: Chart not found
at useChart (chart.tsx:10:11)
at _$$component.location (LineSeries.tsx?t=1746631191461:9:17)
at @solid-refresh:25:42
at untrack (chunk-VWOXEQUG.js?v=d146421a:484:12)
at HMRComp.createMemo.name [as fn] (@solid-refresh:25:28)
at runComputation (chunk-VWOXEQUG.js?v=d146421a:761:22)
at updateComputation (chunk-VWOXEQUG.js?v=d146421a:740:3)
at createMemo (chunk-VWOXEQUG.js?v=d146421a:266:10)
at [solid-refresh]LineSeries (@solid-refresh:22:20)
chart.tsx:10 Uncaught (in promise) Error: Chart not found
at useChart (chart.tsx:10:11)
at _$$component.location (LineSeries.tsx?t=1746631191461:9:17)
at @solid-refresh:25:42
at untrack (chunk-VWOXEQUG.js?v=d146421a:484:12)
at HMRComp.createMemo.name [as fn] (@solid-refresh:25:28)
at runComputation (chunk-VWOXEQUG.js?v=d146421a:761:22)
at updateComputation (chunk-VWOXEQUG.js?v=d146421a:740:3)
at createMemo (chunk-VWOXEQUG.js?v=d146421a:266:10)
at [solid-refresh]LineSeries (@solid-refresh:22:20)
This is unexpected to me because within the implementation of <Chart /> I am making use of the <Show /> component to prevent the showing of the children until the chart instance is ready and can be passed to the context. It appears as if there is some eager kind of rendering going on that I did not expect. Is there a best practice way to go about this intent I am trying to achieve? In my head, even though I pass <LineSeries /> as a child to <Chart /> in my call-site App component, I would expect <LineSeries /> to not render until the parent <Chart /> has done what it needs to do. I'm definitely suspecting a gap in my thinking somewhere here.
73 Replies
REEEEE
REEEEE•2d ago
it's probably because of your use of mergeProps and passing those values into createChart I would split props first to pull either the values you want out or at least the children The reason it's probably causing the issue is because the children are being read in some other part before the show
bigmistqke
bigmistqke•2d ago
my guess would be that something somewhere is accessing props.children accidentally, maybe something inside createChart(chartContainer, _props)?
danchez
danchezOP•2d ago
I think the likelihood of that is probably low -- that createChart API comes from an external, normal/vanilla JS library that I'm just wrapping around with Solid.
bigmistqke
bigmistqke•2d ago
iirc already doing Object.keys(props) could be doing it you could const config = splitProps(props, ['children']) and pass that to createChart to sanity check
danchez
danchezOP•2d ago
Let me try that -- I think that may be what @REEEEE was perhaps alluding to earlier
bigmistqke
bigmistqke•2d ago
a ye exactly lol
danchez
danchezOP•2d ago
So I did the following and still no dice 😭
export const Chart = (props: ParentProps<ChartProps>) => {
let container!: HTMLDivElement;

const [local, chartOptions] = splitProps(props, ["children", "class", "style", "onCreateChart"]);

const _chartOptions = mergeProps(
{
width: 0,
height: 0,
},
chartOptions,
);

const [chart, setChart] = createSignal<IChartApi>();

onMount(() => {
const chart = createChart(container, _chartOptions);
setChart(chart);

createEffect(() => {
chart.applyOptions(_chartOptions);
});

onCleanup(() => {
chart.remove();
});
});

return (
<div ref={container} class={local.class} style={local.style}>
<Show when={chart()}>
{(chart) => <ChartContext.Provider value={chart}>{local.children}</ChartContext.Provider>}
</Show>
</div>
);
};
export const Chart = (props: ParentProps<ChartProps>) => {
let container!: HTMLDivElement;

const [local, chartOptions] = splitProps(props, ["children", "class", "style", "onCreateChart"]);

const _chartOptions = mergeProps(
{
width: 0,
height: 0,
},
chartOptions,
);

const [chart, setChart] = createSignal<IChartApi>();

onMount(() => {
const chart = createChart(container, _chartOptions);
setChart(chart);

createEffect(() => {
chart.applyOptions(_chartOptions);
});

onCleanup(() => {
chart.remove();
});
});

return (
<div ref={container} class={local.class} style={local.style}>
<Show when={chart()}>
{(chart) => <ChartContext.Provider value={chart}>{local.children}</ChartContext.Provider>}
</Show>
</div>
);
};
bigmistqke
bigmistqke•2d ago
code looks good to me is it hmr related or it never works?
danchez
danchezOP•2d ago
It appears that maybe there's something to do with the Context Provider lifecycle....like, even if I had my children wrapped with a provider, maybe it's collaboration with <Show /> is causing some render timing issue where the context isn't ready?
bigmistqke
bigmistqke•2d ago
another sanity check: try add the context and the useContext-hook in another file and import from there, maybe it's some weird side-effect of a circular dependency
danchez
danchezOP•2d ago
It never works, but oddly enough, I just tried getting rid of the <Show /> entirely, and it works. But obviously, I now have to add guards everywhere to all my child components because the context value is potentially undefined. Not sure if that breadcrumb reveals anything of interest to determine the root cause
bigmistqke
bigmistqke•2d ago
mm very strange
danchez
danchezOP•2d ago
Yeah, I did that as well -- I have a serpate context/chart.tsx file where I dumped the creation of the context and the convenience useChart context hook and I import that across the components.
bigmistqke
bigmistqke•2d ago
but it also includes the jsx of the Chart-component right?
danchez
danchezOP•2d ago
Still didn't work with the <Show /> in place No, I kept the root <Chart /> component in its own file since the all the child components are just going to consume the context hook And the <Chart /> just imports the context to set the Provider value
bigmistqke
bigmistqke•2d ago
gotcha ok then i m out of straws to pull 😅
danchez
danchezOP•2d ago
Same here haha! I'm like, "What is going on here?! WHAT AM I MISSING!?"
bigmistqke
bigmistqke•2d ago
would be handy to have a minimal representation of it in the playground to play around with dev life
danchez
danchezOP•2d ago
I'll see if I can get a playground set up. Does it support bringing in dependencies? Or maybe I can try mimicking the problem
bigmistqke
bigmistqke•2d ago
it actually does, you can import any external dependency and it will load it from esm.sh but ideally you wouldn't need dependencies to recreate the bug
danchez
danchezOP•2d ago
@bigmistqke sorry, I'm not good with using the Playground yet: https://playground.solidjs.com/anonymous/6a8456c7-34fd-43e7-a759-fee5f3967902
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
danchez
danchezOP•2d ago
Not sure how to render the content in the playground REPL
REEEEE
REEEEE•2d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
danchez
danchezOP•2d ago
Ah, nice, that's good to know how to do that.
REEEEE
REEEEE•2d ago
looks like it works fine there so maybe ssr related?
danchez
danchezOP•2d ago
It looks like it's behaving properly?... 🤔 This is all happening client-side actually 😅
REEEEE
REEEEE•2d ago
oh lol
danchez
danchezOP•2d ago
Simple SolidJS SPA in my case
REEEEE
REEEEE•2d ago
I definitely think it's something with the children being read from props early or something
bigmistqke
bigmistqke•2d ago
me too
danchez
danchezOP•2d ago
Man, I'm just trying to think what is doing that actually. Because that layout I gave at the start is literally all that I'm doing. Basically, it's just a small sandbox of me rendering these basic components I stripped out the children earlier like you suggested using splitProps
REEEEE
REEEEE•2d ago
Any chance you could do the same setup in the playground with the chart library? You should be able to import the library (it'll show an error but it should import)
danchez
danchezOP•2d ago
Yeah, I'm embarking on creating more data visualization libs for the community so I'm building a Solid Wrapper around Lightweight Charts from Trading View
danchez
danchezOP•2d ago
GitHub
GitHub - tradingview/lightweight-charts: Performant financial chart...
Performant financial charts built with HTML5 canvas - tradingview/lightweight-charts
danchez
danchezOP•2d ago
So how would I import that into the playground exactly? I'm a noob in this department 😅
REEEEE
REEEEE•2d ago
Just add the import statement to the top, it'll auto import I think or @bigmistqke does he need to add it to import map?
REEEEE
REEEEE•2d ago
Just in case it doesn't import automagically, I think adding it to the import map at the top should work
{
"solid-js": "https://esm.sh/solid-js",
"solid-js/web": "https://esm.sh/solid-js/web",
"lightweight-charts": "https://esm.sh/lightweight-charts"
}
{
"solid-js": "https://esm.sh/solid-js",
"solid-js/web": "https://esm.sh/solid-js/web",
"lightweight-charts": "https://esm.sh/lightweight-charts"
}
No description
bigmistqke
bigmistqke•2d ago
simply adding the import should be enough!
danchez
danchezOP•2d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
danchez
danchezOP•2d ago
This should recreate it -- it may not be exact, but what I can see is that, the library injects stuff into the tree when you get rid of the <Show /> component entirely. But when you keep it, the containing <div /> basically stays empty
REEEEE
REEEEE•2d ago
Looks like the library is modifying the children elements You could wrap the parent with the show + context or wrap the show with a div actually I guess you can't wrap the parent with the show + context cause it needs access to the container to create the chart obj
danchez
danchezOP•2d ago
And thus you see my dilemma 😅 I thought along those same lines
danchez
danchezOP•2d ago
I also forgot to give the Chart a width/height so this playground will be better to see the actual chart: https://playground.solidjs.com/anonymous/cc36b2da-ed1f-4eb7-bd2c-d4fae3a18ff6
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
danchez
danchezOP•2d ago
That one is without the <Show /> component there
REEEEE
REEEEE•2d ago
I put the show back and it works? Okay so if you use the callback form it fails but if you add keyed it works fine
bigmistqke
bigmistqke•2d ago
can u share playground, it doesn't on my end
danchez
danchezOP•2d ago
I tried both with and without callback form for Show and I got nothing Same here
REEEEE
REEEEE•2d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
danchez
danchezOP•2d ago
Holy crapola... What does keyed do ? Never used it before
bigmistqke
bigmistqke•2d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
REEEEE
REEEEE•2d ago
actually this is weird, I didn't realize I didn't call chart in the when condition
bigmistqke
bigmistqke•2d ago
instead of
<div ref={container}>
<Show>...</Show>
</div>
<div ref={container}>
<Show>...</Show>
</div>
<>
<div ref={container} />
<Show>...</Show>
</>
<>
<div ref={container} />
<Show>...</Show>
</>
REEEEE
REEEEE•2d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
bigmistqke
bigmistqke•2d ago
a yes, it's not actually doing the condition correctly yes, something about the dom being manipulated does not mesh well with the Show inside the jsx-node
danchez
danchezOP•2d ago
You guys are incredible -- that fragment trick is chef's kiss man
bigmistqke
bigmistqke•2d ago
worth adding an issue to dom-expressions
REEEEE
REEEEE•2d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
danchez
danchezOP•2d ago
I was about to say, is this an edge case that's worth raising awareness to?
bigmistqke
bigmistqke•2d ago
i think so! it's a big feature for solid how it can work together w vanilla js libraries
danchez
danchezOP•2d ago
Just curious -- how would you characterize the nature of the problem we're seeing here? So clearly the library is doing stuff to the container it is meant to bind to/target. Effectively, that's not playing nice with JSX children and screws up their rendering with context? Also, first time hearing about dom-expressions -- I consider myself a mediocre dev so I'm trying to catch up with everyone's skills 😅
bigmistqke
bigmistqke•2d ago
dom-expressions is the library that is powering solid
GitHub
GitHub - ryansolid/dom-expressions: A Fine-Grained Runtime for Perf...
A Fine-Grained Runtime for Performant DOM Rendering - ryansolid/dom-expressions
bigmistqke
bigmistqke•2d ago
you could add the issue to solid's github too 🙂 bit blurry which library is responsible for what
danchez
danchezOP•2d ago
What's a good way to summarize this? Not sure I have the right words for it haha
bigmistqke
bigmistqke•2d ago
yes, something about dom manipulation breaking context in jsx
danchez
danchezOP•2d ago
This is super awesome -- you guys have been a massive help! Thank you!
bigmistqke
bigmistqke•2d ago
Ur very welcome!
zulu
zulu•2d ago
directly mutating dom controlled by UI framework is not advisable. this may or may not be a bug, you can just put the reproduction as an issue and describe the issue, you are not to be expected to know about dom-expression as another alternative you can move your provider outside the JSX https://playground.solidjs.com/anonymous/16c8c567-6341-4120-a160-491da12c4c9b
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
bigmistqke
bigmistqke•2d ago
I am not sure if I agree: having a container element and doing stuff w a vanilla library is pretty solid, especially if you are not rendering additional dom elements in it.
zulu
zulu•2d ago
which part you do not agree?
directly mutating dom controlled by UI framework is not advisable.
this ?
bigmistqke
bigmistqke•2d ago
Yes, exactly!
zulu
zulu•2d ago
mutating the dom directly may be like pulling the rug under the framework. ( not in all cases) but in this case, if the show adds placeholder element , and the external code remove it, expectations are not meet and things can break.
bigmistqke
bigmistqke•2d ago
A I see what you mean, but that's not this bug. In this situation the children are not adding dom elements, they simply add config for the library through context, and then return null. I don't think these nulls are injected in the DOM.
zulu
zulu•this hour
It is the external library that mutates the container the show is in I am not excluding that there might be something that can be better handled on solid side. but I think the better practice and the general rule is not to change dom that is controlled by the framework

Did you find this page helpful?