Skip to content

Scope Decorator

The @hook_scope decorator is used to define a scope for your hooks. It is useful for two main use cases:

  • When you want to persist state based on function parameters.
  • When you want to scope hooks in a method globally instead of to the instance or class.

Scoping state to function parameters

When using hooks inside a function, the scope by default will ignore function parameters. This means that if you call a hook inside a function, the hook will be scoped to the function and not the function parameters (Unless when using parameters as dependencies on use_effect).

You can use the @hook_scope decorator to scope hooks to function parameters. This is useful when you want to persist state based on function parameters.

from hooks import use_state, hook_scope


@hook_scope(parametrize=["owner"])
def owned_counter(owner: str):
    count, set_count = use_state(0)
    set_count(count + 1)
    print(f"{owner}'s count is {count}")

owned_counter("John")  # Output: John's count is 0
owned_counter("John")  # Output: John's count is 1
owned_counter("Jane")  # Output: Jane's count is 0
owned_counter("Jane")  # Output: Jane's count is 1

Note that you do not have to provide a value for the parametrize parameter. If you do not provide a value, the hook will be scoped to all function parameters.

from hooks import use_state, hook_scope


@hook_scope()
def owned_counter(owner: str):
    count, set_count = use_state(0)
    set_count(count + 1)
    print(f"{owner}'s count is {count}")

owned_counter("John")  # Output: John's count is 0
owned_counter("John")  # Output: John's count is 1
owned_counter("Jane")  # Output: Jane's count is 0
owned_counter("Jane")  # Output: Jane's count is 1

Scoping hooks globally

When using hooks inside a class, the default scoping mechanism is to scope the hook to the smallest enclosing scope. Sometimes, you'll want to scope hooks globally instead of to the instance or class. You can do this by using the @hook_scope decorator.

from hooks import use_state, hook_scope


class CounterClass:
    @hook_scope(use_global_scope=True, parametrize=[])
    def instance_method_scoped_globally(self):
        count, set_count = use_state(0)
        set_count(count + 1)
        return count

    def instance_method(self):
        count, set_count = use_state(0)
        set_count(count + 1)
        return count


counter = CounterClass()
counter_two = CounterClass()
print(counter.instance_method())                      # Output: 0
print(counter.instance_method())                      # Output: 1
print(counter_two.instance_method())                  # Output: 0
print(counter.instance_method_scoped_globally())      # Output: 0
print(counter_two.instance_method_scoped_globally())  # Output: 1

Note that when using use_global_scope=True, you have to provide a value for the parametrize parameter. You may provide an empty list if you want to scope the hook to no parameters.


Nesting scopes

As you might be aware hooks can be used anywhere and you may have functions that use hooks and are called by other functions that use hooks. When nesting hooks like this, scoping will affect all hooks in the call stack.

from hooks import use_state, hook_scope

class NestedStates:
    def nested_state(self) -> int:
        counter, set_counter = use_state(0)
        set_counter(counter + 1)
        return counter

    @hook_scope(parametrize=["counter_name"])
    def local_state(self, counter_name: str) -> int:
        return self.nested_state()

foo = NestedStates()

print(foo.local_state("A"))            # Output: 0
print(foo.local_state("A"))            # Output: 1

# As you can see the scope of local_state affects the nested_state's function hooks.
print(foo.local_state("B"))            # Output: 0
print(foo.local_state("B"))            # Output: 1
print(foo.local_state("A"))            # Output: 2

print(NestedStates().local_state("A")) # Output: 0
print(NestedStates().local_state("A")) # Output: 0

The same concept can be taken further by scoping the nested_state function.

from hooks import use_state, hook_scope

class NestedStates:
    @hook_scope()
    def nested_state_with_scope(self) -> int:
        counter, set_counter = use_state(0)
        set_counter(counter + 1)
        return counter

    @hook_scope(parametrize=["counter_name"])
    def local_state(self, counter_name: str) -> int:
        return self.nested_state_with_scope()

foo = NestedStates()

print(foo.local_state("A"))             # Output: 0
print(foo.local_state("A"))             # Output: 1

print(foo.local_state("B"))             # Output: 0
print(foo.local_state("B"))             # Output: 1
print(foo.local_state("A"))             # Output: 2

print(NestedStates().local_state("A"))  # Output: 0
print(NestedStates().local_state("A"))  # Output: 0

Next steps

Learn about backends.