widgetti / solara

A Pure Python, React-style Framework for Scaling Your Jupyter and Web Apps
https://solara.dev
MIT License
1.62k stars 105 forks source link

Question: Solara reactive list append, pop, remove #538

Closed BFAGIT closed 2 months ago

BFAGIT commented 2 months ago

Hello everyone, I have a very simple question. I'm going through the library and testing things out here and there. I have a question regarding the reactive component. Here is a simple example where I define a reactive list component and everytime I press increment it adds an other 'hello world' to the list. The element 'hello world' is correctly appended but it doesn't render any additional solara.info other then the 3 first elements.

What am i doing wrong and how should it be used correctly?

NB: I want to use this list to track some exceptions/ errors raised by more complexe code and display all of them at the bottom of my page

Thanks a lot

import solara

list_hello_world = solara.reactive(['hello world','hello world','hello world'])

def add_hello_world():
     list_hello_world.value.append('hello world')

@solara.component
def add_hello_world_button():

    solara.Button("Increment", on_click=add_hello_world)

@solara.component
def Page():
    add_hello_world_button()

    for i in list_hello_world.value:
        solara.Info(f" {i}")
iisakkirotko commented 2 months ago

Hi @BFAGIT!

Solara cannot track the action of .append() on the list, so you end up mutating the list in place. Instead you could do something like

list_hello_world.value = [*list_hellow_world.value, 'hello world']

or

list_hello_world.set([*list_hellow_world.value, 'hello world'])

We have discussed adding a convenience function to do this for reactive lists, but no final decision on it has been made yet.

BFAGIT commented 2 months ago

Hi @iisakkirotko, Thanks a lot for the blasing fast answer. It definitly makes sense that it cannot track action of append. Thanks for the solution I wouldn't of thought about it ^^ without having your answer.

Would be definitly nice to have a convience function or a small generic point that i guess solara cannot natively track internal object modifications with their own methods which is completly logical.

Thanks a lot, Will come back with more questions soon :)

BFAGIT commented 2 months ago

Actually i'm already back, apologies for closing the ticket so fast ... How do you deal with removing element from the list ? I tried a few things in the last 15 minutes but unsuccesfully

I guess I can use Listr comprenhension to remove all elements at once

list_hello_world.value = [list_hello_world.value.pop() for list_hello_world.value in list_hello_world.value if list_hello_world.value != 'hello world']
import solara
from typing import Optional, cast

list_hello_world = solara.reactive(cast(Optional[list], ''))
#solara.reactive(['hello world','hello world','hello world'])

def add_hello_world():

    list_hello_world.value = [*list_hello_world.value, 'hello world']    

def remove_hello_world():
    pass

@solara.component
def add_hello_world_button():
    solara.Button("Increment", on_click=add_hello_world)

@solara.component
def remove_hello_world_button():
    solara.Button("Decrement", on_click=remove_hello_world)

@solara.component
def Page():
    add_hello_world_button()
    remove_hello_world_button()
    if list_hello_world.value is not None:
        for i in list_hello_world.value:
            solara.Info(f" {i}")
BFAGIT commented 2 months ago

Okay found the solution on my side very simple actually ahahah.

def remove_hello_world():
     list_hello_world.value = list_hello_world.value[:-1]
iisakkirotko commented 2 months ago

Hey again!

This is indeed a bit trickier, and depends on the specifics. For the simple case where you just want to remove one from the end of the list, you could use (list slicing)[https://www.geeksforgeeks.org/python-list-slicing/] like so

list_hello_world.value = list_hello_world.value[:-1]

Edit: damn, you were too quick.

In more complex cases you could find the index you're interested in removing, and then slicing that out: [:i, i+1:]

BFAGIT commented 2 months ago

Aahahah you to ^^ but the solution was simple I should've spent a bit more time before opening the ticket back

For removing a specific element do you see something else then the list comprenhension i provided above ? There actually finding the position and slicing the list like you said just above.

And Thanks a lot you guys are really awesome and solara is so awesome to I've been playing around with native vue elements and its just crazy what you guys have built.

also I'll rename the ticket to make to easier for other having the same question