#eslint: How to assign a prop value to the initial value in a createSignal?

50 messages · Page 1 of 1 (latest)

terse mango
#

In one of my components, I have this:
const [isOpen, setIsOpen] = createSignal(Boolean(props.state.notes));

The intent is to show the notes data for this particular instance of the component if the notes data passed in (via a store) is not empty string, or other falsey value.

This code does work as intended, as far as I can tell.

However, I'm getting the following warning from the reactivity rule in eslint-plugin-solid:
The reactive variable 'props.state.notes' should be used within JSX, a tracked scope (like createEffect), or inside an event handler function

I can't seem to find a construction that the rule is happy with. I've attempted creating a separate function to retrieve the notes from the props, as well as doing a splitProps.

flat flume
#

I think it's just telling you that the signal won't be updated when the props.state.notes changes

terse mango
#

Isn't the intent of the initialValue of a createSignal(initialValue) is that it isn't updated except by the setter?

flat flume
#

yeah but I'm not too sure what the warning is trying to say otherwise.

terse mango
#

I suspect it's an example they haven't handled yet. I have other code that causes the same sort of warning, but I can't figure out what's wrong with those. Would you mind looking at another?

flat flume
#

sure

terse mango
#

So, there's the case of passing a callback function to a component to update it. I do this all over the place, but constructions like this give me a warning:

#

<button onClick={() => props.updateMode('preview')}>Show Preview</button>

#

The warning is This function should be passed to a tracked scope (like createEffect) or an event handler because it contains reactivity

#

What is the appropriate way to do this?

flat flume
#

I think it's fine but the appropriate way would be to make the function separate

#

I think the eslint rules need be more well defined and documented with explanation

terse mango
#

I'm doing some tests

#

Ok, I found something interesting.

#

I implemented the expected way, so props is used directly, rather than in an arrow function, the warning went away. Then, I went to the parent and made the callback a signal, such that I could change the function dynamically. When doing so, it actually breaks the page, somehow calling the newly set function followed by the older function again???

#

Maintaining reactivity with function passing seems to simply not work.

#

I think I haven't run into any issues so far because I don't dynamically set functions after the initial render.

#

Definitely something going on here.

#

Says in the SolidJs docs that you can put functions in signals

flat flume
#

can you try doing onClick={[props.updateMode, 'preview']}

terse mango
#

oh! That's handy

#

So have a look at this. I think I found a bug in Solid

#
import { render } from 'solid-js/web';

function TestFunctionReactivity() {
    const [mode, setMode] = createSignal('edit');

    const initial = (newMode) => {
        console.log(`called initial function with ${newMode}`);
        setMode(newMode);
    };

    const changed = (newMode) => {
        console.log(`called changed function with ${newMode}`);
        setMode(newMode);
    };

    const [inBetween, setInBetween] = createSignal(initial);

    const updateInBetweenFunction = () => {
        setInBetween(changed);
    };

    return (
        <ul>
            <li><pre>{mode()}</pre></li>

            <li><button onClick={() => inBetween()('preview')}>To Preview</button></li>
            <li><button onClick={() => inBetween()('edit')}>To Edit</button></li>

            <li><button onClick={updateInBetweenFunction}>Update inbetween function</button></li>
        </ul>
    );
}

render(() => (
    <TestFunctionReactivity />
), document.getElementById('root'));
flat flume
#

it's not technically a bug

#

to set functions in signals you need to do setSignal(() => myFunction)

terse mango
#

That's what I'm doing

flat flume
#

no you did setInBetween(changed);

terse mango
#

They're using arrow functions

#

oh

#

ok, one sec

#

Alright, here's the changed test, which exhibits identical broken behavior:

#
import { render } from 'solid-js/web';

function TestFunctionReactivity() {
    const [mode, setMode] = createSignal('edit');

    const [inBetween, setInBetween] = createSignal((newMode) => {
        console.log(`called initial function with ${newMode}`);
        setMode(newMode);
    });

    const updateInBetweenFunction = () => {
        setInBetween((newMode) => {
            console.log(`called changed function with ${newMode}`);
            setMode(newMode);
        });
    };

    return (
        <ul>
            <li><pre>{mode()}</pre></li>

            <li><button onClick={() => inBetween()('preview')}>To Preview</button></li>
            <li><button onClick={() => inBetween()('edit')}>To Edit</button></li>

            <li><button onClick={updateInBetweenFunction}>Update inbetween function</button></li>
        </ul>
    );
}

render(() => (
    <TestFunctionReactivity />
), document.getElementById('root'));
#

If you try to change the function via setInBetween, it immediately calls the updated function with the newMode variable set to the initial function (which makes no sense), then calls that initial function again with the original value of mode() at the time.

#
CriteriaPage.jsx:14 called changed function with t=>{console.log(`called initial function with ${t}`),e(t)}
CriteriaPage.jsx:8 called initial function with preview```
flat flume
#

I think you misunderstood

import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';

import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';

function TestFunctionReactivity() {
    const [mode, setMode] = createSignal('edit');

    const initial = (newMode) => {
        console.log(`called initial function with ${newMode}`);
        setMode(newMode);
    };

    const changed = (newMode) => {
        console.log(`called changed function with ${newMode}`);
        setMode(newMode);
    };

    const [inBetween, setInBetween] = createSignal(initial);

    const updateInBetweenFunction = () => {
        setInBetween(() => changed);
    };

    return (
        <ul>
            <li><pre>{mode()}</pre></li>

            <li><button onClick={() => inBetween()('preview')}>To Preview</button></li>
            <li><button onClick={() => inBetween()('edit')}>To Edit</button></li>

            <li><button onClick={updateInBetweenFunction}>Update inbetween function</button></li>
        </ul>
    );
}

render(() => (
    <TestFunctionReactivity />
), document.getElementById('root'));

This should work I think

terse mango
#

ok, so when setting the initial value, you give it the function as normal, but when changing a function, you have to wrap the function? This is different from other primitives.

flat flume
#

the reason is because the setter can take in a function that passes the previous value

setSignal((previous) => ...)

So if you just do setSignal(myfunction) it's essentially calling myFunction

terse mango
#

ah, so it's overloaded and doesn't usually expect a function

flat flume
#

yup

terse mango
#

ok, interesting

#

Alright, with this, I can check and see if I'm getting actual reactivity with my original code

#

Yep, functional reactivity is being maintained, the linter's rules don't handle my cases.

#

Well, I've learned a lot. Thank you @flat flume

flat flume
#

No problem, glad I could help 😄

tough valve
terse mango
#

@tough valve I was planning on it, perhaps monday!

tepid oriole
swift knot
terse mango
#

The first issue is definitely philosophical. I think one would expect that the value set to the initial value would by definition not change after the initial setup.

terse mango
#

I changed nothing over the weekend, but now the second issue I found is not issuing a warning in the linter.