Fundamental reactivity
I must be missing something fundamental about how to implement that reactivity in SolidJS. I have a Card component (multiple for testing). That component includes some inputs with data that will be passed to child component (chart) which should trigger an update for that child component.
Parent Card(s) Component
import { createSignal, createEffect } from 'solid-js'
import { Card, CardHeader, CardContent, CardTitle } from '@/components/ui/card'
import { TextField, TextFieldLabel, TextFieldRoot } from '@/components/ui/textfield'
import ProbabilityConesChart, { ConeType } from '@/components/charts/ProbabilityCones'
import type { Component } from 'solid-js'
import type { ProcessedData } from '@/libs/stats'
interface CardProps {
data: ProcessedData | null
}
export const ProbabilityConesCard: Component<CardProps> = (props) => {
const [coneSize, setConeSize] = createSignal(30)
// createEffect(() => {
// console.log('here', coneSize())
// })
return (
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-6 mt-6">
<Card>
<CardHeader class="flex flex-row">
<CardTitle>Probability Cones</CardTitle>
<TextFieldRoot class="mt-0">
<TextFieldLabel for="coneSize">Cone Size</TextFieldLabel>
<TextField
id="coneSize"
class="mt-2"
type="number"
min={1}
value={coneSize()}
// onInput={(e) => setConeSize(Number((e.target as HTMLInputElement).value))}
onInput={(e) => {
console.log('here', coneSize())
setConeSize(Number((e.target as HTMLInputElement).value))
}}
/>
</TextFieldRoot>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneLength={coneSize()}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Probability Cones</CardTitle>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneType={ConeType.Linear}
/>
</CardContent>
</Card>
</div>
)
}
export default ProbabilityConesCard
import { createSignal, createEffect } from 'solid-js'
import { Card, CardHeader, CardContent, CardTitle } from '@/components/ui/card'
import { TextField, TextFieldLabel, TextFieldRoot } from '@/components/ui/textfield'
import ProbabilityConesChart, { ConeType } from '@/components/charts/ProbabilityCones'
import type { Component } from 'solid-js'
import type { ProcessedData } from '@/libs/stats'
interface CardProps {
data: ProcessedData | null
}
export const ProbabilityConesCard: Component<CardProps> = (props) => {
const [coneSize, setConeSize] = createSignal(30)
// createEffect(() => {
// console.log('here', coneSize())
// })
return (
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-6 mt-6">
<Card>
<CardHeader class="flex flex-row">
<CardTitle>Probability Cones</CardTitle>
<TextFieldRoot class="mt-0">
<TextFieldLabel for="coneSize">Cone Size</TextFieldLabel>
<TextField
id="coneSize"
class="mt-2"
type="number"
min={1}
value={coneSize()}
// onInput={(e) => setConeSize(Number((e.target as HTMLInputElement).value))}
onInput={(e) => {
console.log('here', coneSize())
setConeSize(Number((e.target as HTMLInputElement).value))
}}
/>
</TextFieldRoot>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneLength={coneSize()}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Probability Cones</CardTitle>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneType={ConeType.Linear}
/>
</CardContent>
</Card>
</div>
)
}
export default ProbabilityConesCard
1 Reply
I'm missing something fundamental about reactivity in solidjs. I have a parent component with some inputs that pass data to the child and the child is not being updated. When the
Child Chart Component
I can get some logging to work with this in the child component.
But the data for the chart doesn't seem to be updating here.
This is the specific line of code I've updated to use
So I think the reactivity is there. I believe it has to do with how I'm using getting and applying the data in the
And this creates the plot data.
Maybe this is breaking it.
coneSize
changes the chart in the child component should be updated.
Parent Card(s) Component
import { createSignal, createEffect } from 'solid-js'
import { Card, CardHeader, CardContent, CardTitle } from '@/components/ui/card'
import { TextField, TextFieldLabel, TextFieldRoot } from '@/components/ui/textfield'
import ProbabilityConesChart, { ConeType } from '@/components/charts/ProbabilityCones'
import type { Component } from 'solid-js'
import type { ProcessedData } from '@/libs/stats'
interface CardProps {
data: ProcessedData | null
}
export const ProbabilityConesCard: Component<CardProps> = (props) => {
const [coneSize, setConeSize] = createSignal(30)
// createEffect(() => {
// console.log('here', coneSize())
// })
return (
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-6 mt-6">
<Card>
<CardHeader class="flex flex-row">
<CardTitle>Probability Cones</CardTitle>
<TextFieldRoot class="mt-0">
<TextFieldLabel for="coneSize">Cone Size</TextFieldLabel>
<TextField
id="coneSize"
class="mt-2"
type="number"
min={1}
value={coneSize()}
// onInput={(e) => setConeSize(Number((e.target as HTMLInputElement).value))}
onInput={(e) => {
console.log('here', coneSize())
setConeSize(Number((e.target as HTMLInputElement).value))
}}
/>
</TextFieldRoot>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneLength={coneSize()}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Probability Cones</CardTitle>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneType={ConeType.Linear}
/>
</CardContent>
</Card>
</div>
)
}
export default ProbabilityConesCard
import { createSignal, createEffect } from 'solid-js'
import { Card, CardHeader, CardContent, CardTitle } from '@/components/ui/card'
import { TextField, TextFieldLabel, TextFieldRoot } from '@/components/ui/textfield'
import ProbabilityConesChart, { ConeType } from '@/components/charts/ProbabilityCones'
import type { Component } from 'solid-js'
import type { ProcessedData } from '@/libs/stats'
interface CardProps {
data: ProcessedData | null
}
export const ProbabilityConesCard: Component<CardProps> = (props) => {
const [coneSize, setConeSize] = createSignal(30)
// createEffect(() => {
// console.log('here', coneSize())
// })
return (
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-6 mt-6">
<Card>
<CardHeader class="flex flex-row">
<CardTitle>Probability Cones</CardTitle>
<TextFieldRoot class="mt-0">
<TextFieldLabel for="coneSize">Cone Size</TextFieldLabel>
<TextField
id="coneSize"
class="mt-2"
type="number"
min={1}
value={coneSize()}
// onInput={(e) => setConeSize(Number((e.target as HTMLInputElement).value))}
onInput={(e) => {
console.log('here', coneSize())
setConeSize(Number((e.target as HTMLInputElement).value))
}}
/>
</TextFieldRoot>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneLength={coneSize()}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Probability Cones</CardTitle>
</CardHeader>
<CardContent>
<ProbabilityConesChart
data={props.data}
coneType={ConeType.Linear}
/>
</CardContent>
</Card>
</div>
)
}
export default ProbabilityConesCard
import { createMemo, createEffect, splitProps } from 'solid-js'
import Plot from 'solid-plotly.js'
import { createLayout, getChartColors } from '@/libs/plotly'
import { generateProbabilityCones, generateLinearProbabilityCones } from '@/libs/stats'
import type { Component } from 'solid-js'
import type { PlotType } from 'plotly.js'
import type { ProcessedData } from '@/libs/stats'
export enum ConeType {
Linear,
Exponential,
}
interface ChartProps {
data: ProcessedData | null
coneType?: ConeType.Exponential | ConeType.Linear
coneLength?: number
}
const getConeMethod = (coneType: ConeType) => {
switch (coneType) {
case ConeType.Linear:
return generateLinearProbabilityCones
case ConeType.Exponential:
return generateProbabilityCones
default:
return generateProbabilityCones
}
}
export const ProbabilityCones: Component<ChartProps> = (props) => {
const coneType = () => props.coneType ?? ConeType.Exponential
const coneLength = () => props.coneLength ?? 30
// createEffect(() => {
// console.log(props.coneLength)
// })
console.log({ coneLength: coneLength() })
const coneMethod = getConeMethod(coneType())
// Memoize plotData and layout to optimize performance
const equityPlotData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: props.data?.dates,
y: props.data?.equity,
type: 'scatter' as PlotType,
name: 'Equity',
line: { color: getChartColors()[9] },
},
])
// const linearEquityPlotData = createMemo<Partial<Plotly.PlotData>[]>(() => [
// {
// x: props.data?.dates,
// y: calculateLinearAverageEquity(props.data),
// type: 'scatter' as PlotType,
// name: 'Average Equity',
// line: { color: getChartColors()[0] },
// },
// ])
const coneAData = createMemo(() =>
props.data ? coneMethod(props.data, 1) : { futureDates: [], upperCone: [], lowerCone: [] }
)
const upperConeAData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneAData().futureDates,
y: coneAData().upperCone,
type: 'scatter' as PlotType,
name: `1σ Upper Cone`,
line: { color: getChartColors()[11] },
},
])
const lowerConeAData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneAData().futureDates,
y: coneAData().lowerCone,
type: 'scatter' as PlotType,
name: `1σ Lower Cone`,
line: { color: getChartColors()[11] },
},
])
const coneBData = createMemo(() =>
props.data
? coneMethod(props.data, 2, coneLength())
: { futureDates: [], upperCone: [], lowerCone: [] }
)
const upperConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().upperCone,
type: 'scatter' as PlotType,
name: `2σ Upper Cone`,
line: { color: getChartColors()[3] },
},
])
const lowerConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().lowerCone,
type: 'scatter' as PlotType,
name: `2σ Lower Cone`,
line: { color: getChartColors()[3] },
},
])
// console.log({ coneAData: coneAData() })
const layout = createMemo(() => createLayout())
return (
<Plot
data={[
...equityPlotData(),
// ...linearEquityPlotData(),
...upperConeAData(),
...lowerConeAData(),
...upperConeBData(),
...lowerConeBData(),
]}
layout={layout()}
useResizeHandler={true}
/>
)
}
export default ProbabilityCones
import { createMemo, createEffect, splitProps } from 'solid-js'
import Plot from 'solid-plotly.js'
import { createLayout, getChartColors } from '@/libs/plotly'
import { generateProbabilityCones, generateLinearProbabilityCones } from '@/libs/stats'
import type { Component } from 'solid-js'
import type { PlotType } from 'plotly.js'
import type { ProcessedData } from '@/libs/stats'
export enum ConeType {
Linear,
Exponential,
}
interface ChartProps {
data: ProcessedData | null
coneType?: ConeType.Exponential | ConeType.Linear
coneLength?: number
}
const getConeMethod = (coneType: ConeType) => {
switch (coneType) {
case ConeType.Linear:
return generateLinearProbabilityCones
case ConeType.Exponential:
return generateProbabilityCones
default:
return generateProbabilityCones
}
}
export const ProbabilityCones: Component<ChartProps> = (props) => {
const coneType = () => props.coneType ?? ConeType.Exponential
const coneLength = () => props.coneLength ?? 30
// createEffect(() => {
// console.log(props.coneLength)
// })
console.log({ coneLength: coneLength() })
const coneMethod = getConeMethod(coneType())
// Memoize plotData and layout to optimize performance
const equityPlotData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: props.data?.dates,
y: props.data?.equity,
type: 'scatter' as PlotType,
name: 'Equity',
line: { color: getChartColors()[9] },
},
])
// const linearEquityPlotData = createMemo<Partial<Plotly.PlotData>[]>(() => [
// {
// x: props.data?.dates,
// y: calculateLinearAverageEquity(props.data),
// type: 'scatter' as PlotType,
// name: 'Average Equity',
// line: { color: getChartColors()[0] },
// },
// ])
const coneAData = createMemo(() =>
props.data ? coneMethod(props.data, 1) : { futureDates: [], upperCone: [], lowerCone: [] }
)
const upperConeAData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneAData().futureDates,
y: coneAData().upperCone,
type: 'scatter' as PlotType,
name: `1σ Upper Cone`,
line: { color: getChartColors()[11] },
},
])
const lowerConeAData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneAData().futureDates,
y: coneAData().lowerCone,
type: 'scatter' as PlotType,
name: `1σ Lower Cone`,
line: { color: getChartColors()[11] },
},
])
const coneBData = createMemo(() =>
props.data
? coneMethod(props.data, 2, coneLength())
: { futureDates: [], upperCone: [], lowerCone: [] }
)
const upperConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().upperCone,
type: 'scatter' as PlotType,
name: `2σ Upper Cone`,
line: { color: getChartColors()[3] },
},
])
const lowerConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().lowerCone,
type: 'scatter' as PlotType,
name: `2σ Lower Cone`,
line: { color: getChartColors()[3] },
},
])
// console.log({ coneAData: coneAData() })
const layout = createMemo(() => createLayout())
return (
<Plot
data={[
...equityPlotData(),
// ...linearEquityPlotData(),
...upperConeAData(),
...lowerConeAData(),
...upperConeBData(),
...lowerConeBData(),
]}
layout={layout()}
useResizeHandler={true}
/>
)
}
export default ProbabilityCones
const coneLength = () => props.coneLength ?? 30
// this works
createEffect(() => {
console.log(props.coneLength)
})
// this does not work
console.log({ coneLength: coneLength() })
const coneLength = () => props.coneLength ?? 30
// this works
createEffect(() => {
console.log(props.coneLength)
})
// this does not work
console.log({ coneLength: coneLength() })
const coneBData = createMemo(() =>
props.data
? coneMethod(props.data, 2, coneLength())
: { futureDates: [], upperCone: [], lowerCone: [] }
)
const upperConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().upperCone,
type: 'scatter' as PlotType,
name: `2σ Upper Cone`,
line: { color: getChartColors()[3] },
},
])
const coneBData = createMemo(() =>
props.data
? coneMethod(props.data, 2, coneLength())
: { futureDates: [], upperCone: [], lowerCone: [] }
)
const upperConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().upperCone,
type: 'scatter' as PlotType,
name: `2σ Upper Cone`,
line: { color: getChartColors()[3] },
},
])
coneLength()
.
coneMethod(props.data, 2, coneLength())
I should add that I understand why this doesn't work console.log({ coneLength: coneLength() })
since it doesn't re-render like a react component, but shouldn't the reactivity be captured here?
coneMethod(props.data, 2, coneLength())
If I change the return (render) function to something like this I see the updating coneLength()
value.
return (
<>
<div>Current Cone Length: {coneLength()}</div>
<Plot
data={[
...equityPlotData(),
// ...linearEquityPlotData(),
...upperConeAData(),
...lowerConeAData(),
...upperConeBData(),
...lowerConeBData(),
]}
layout={layout()}
useResizeHandler={true}
/>
</>
)
return (
<>
<div>Current Cone Length: {coneLength()}</div>
<Plot
data={[
...equityPlotData(),
// ...linearEquityPlotData(),
...upperConeAData(),
...lowerConeAData(),
...upperConeBData(),
...lowerConeBData(),
]}
layout={layout()}
useResizeHandler={true}
/>
</>
)
<Plot />
.
This returns the necessary cone data.
const coneBData = createMemo(() =>
props.data
? coneMethod(props.data, 2, coneLength())
: { futureDates: [], upperCone: [], lowerCone: [] }
)
const coneBData = createMemo(() =>
props.data
? coneMethod(props.data, 2, coneLength())
: { futureDates: [], upperCone: [], lowerCone: [] }
)
const upperConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().upperCone,
type: 'scatter' as PlotType,
name: `2σ Upper Cone`,
line: { color: getChartColors()[3] },
},
])
const upperConeBData = createMemo<Partial<Plotly.PlotData>[]>(() => [
{
x: coneBData().futureDates,
y: coneBData().upperCone,
type: 'scatter' as PlotType,
name: `2σ Upper Cone`,
line: { color: getChartColors()[3] },
},
])
x: coneBData().futureDates,
The coneBData()
should be reactive, but does that get lost here coneBData().futureDates
?
hmm, maybe it's the solid-plotly.js chart implementation (https://discord.com/channels/722131463138705510/1291807604284330044) that's not working?
Well snap! That's it. I gotta fix that correctly and then it does work how I originally expected.
😮💨 😤