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
agentsmith
agentsmithOP3mo ago
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 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
Child Chart Component
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
I can get some logging to work with this in the child component.
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() })
But the data for the chart doesn't seem to be updating here.
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] },
},
])
This is the specific line of code I've updated to use 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}
/>
</>
)
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 <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: [] }
)
And this creates the plot data.
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] },
},
])
Maybe this is breaking it. 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. 😮‍💨 😤
Want results from more Discord servers?
Add your server