Skip to content

Look into Signals Pattern #854

Open

Description

@rmorshea

Current Situation

Preact, a fast and lightweight drop-in replacement for React, has a pluggable renderer that allows them to add new state and rendering primitives without a wholesale re-write. Signals are one of the new primitives they created. In this introductory article they lay out some advantages of signals over hooks.

Proposed Actions

While signals are an interesting pattern to explore, the more useful action here would be to implement signals for the purpose of developing a similarly pluggable layout rendering pipeline. This would ensure that, as better reactive patterns are developed, IDOM can more easily evolve with them.

Activity

  1. added this to the milestone on Dec 16, 2022
  2. rmorshea commented on Jan 7, 2023

    @rmorshea
    CollaboratorAuthor

    On thinking about this a bit. I actually think the current design is flexible enough to accommodate signals:

    from weakref import WeakSet
    from idom.core.hooks import current_hook, COMPONENT_WILL_UNMOUNT_EFFECT
    
    class Signal:
        def __init__(self, value):
            self._value = value
            self._subscribers = WeakSet()
    
        @property
        def value(self):
            try:
                hook = current_hook()
            except RuntimeError:
                pass
            else:
                self._subscribers.add(hook)
                hook.add_effect(COMPONENT_WILL_UNMOUNT_EFFECT, lambda: self._subscribers.remove(hook))
            return self._value
    
        @value.setter
        def value(self, new):
            for hook in self._subscribers:
                hook.schedule_render()
            self._value = new
  3. rmorshea commented on Jan 7, 2023

    @rmorshea
    CollaboratorAuthor

    The signal pattern seems preferable to contexts in most cases. Contexts have the benefit of being scoped to a given ContextProvider which means that one context can have different values under different providers. To achieve the same thing with signals you'd just have to declare more of them. With that said, allowing the same context to have two different values seems like a confusing concept to wrap your head around, so maybe signals are better in this respect too.

    Anyway, something to consider when it comes time to document contexts - perhaps we'd want to push the signal pattern instead.

  4. rmorshea commented on Jan 8, 2023

    @rmorshea
    CollaboratorAuthor

    The reasoning behind my assertion that signals are better comes down to two factors:

    1. No need for a context provider - one less thing you need to learn
    2. Only components which used the signal get rendered on a change - more performant
  5. removed this from the milestone on Feb 21, 2023
  6. rmorshea commented on Mar 8, 2023

    @rmorshea
    CollaboratorAuthor

    More reasons to use signals over standard hooks: https://emnudge.dev/blog/react-hostage#fixing-react

    I'm realizing that signals could actually be better than the standard use_state hook if integrated a bit more into ReactPy's core layout rendering logic. Instead of re-rending a whole component, if you pass a signal directly as a prop or child of an element, ReactPy could intelligently update the value for you without requiring the whole component to re-render.

    For example, this would cause an infinite loop:

    from time import sleep
    from threading import Thread
    
    @component
    def InfiniteLoop():
        count, set_count = use_state(0)
    
        Thread(target=lambda: sleep(1) or set_state(state + 1)).run()
    
        return html.p("Current value is:", count)

    However, with signals...

    from time import sleep
    from threading import Thread
    
    @component
    def RendersOnce():
        count = use_signal(0)
    
        Thread(target=lambda: sleep(1) or signal.value := signal.value + 1).run()
    
        return html.p("Current value is:", count)

    The component RendersOnce would, as the name suggests, only render once. Nonetheless the <p> element would receive a single update after the expected delay.

  7. VatsalJagani commented on Jul 14, 2023

    @VatsalJagani

    @rmorshea
    You meant this right?

    Thread(target=lambda: sleep(1) or count.value := count.value + 1).run()

    count.value instead of signal.value.

  8. rmorshea commented on Jul 14, 2023

    @rmorshea
    CollaboratorAuthor

    Correct.

  9. Archmonger commented on Feb 3, 2025

    @Archmonger
    Contributor

    When we develop this it's probably best situated as a external package, since it deviates from the ReactJS API.

    Would also be a good excuse to start digging into whether we can make the renderer support user extensions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority-3-lowMay be resolved one any timeline.type-investigationAbout research and gathering information

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions