[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component properties set through dynamic callbacks cannot be persisted #2678

Open
PierXuY opened this issue Oct 30, 2023 · 11 comments
Open

Component properties set through dynamic callbacks cannot be persisted #2678

PierXuY opened this issue Oct 30, 2023 · 11 comments
Labels
bug something broken P3 backlog

Comments

@PierXuY
Copy link
PierXuY commented Oct 30, 2023

I found that for a component with persistence=True set, if its properties are set through callbacks, the properties cannot be persisted when the web page is reloaded.

I don’t know if this is a bug or an intentional design?

I think if there is no difference between manual modification and callback settings, the experience will be better both intuitively and in terms of freedom.

dash    2.14.0
@guidocioni
Copy link
guidocioni commented Nov 15, 2023

I did notice this also today, and it is pretty annoying. As you said, once the value of a component is set (either by a callback or user interaction) it should be persisted in the same way.

I have the feeling this is intentional because on the doc page it says

If persisted is truthy and hasn't changed from its previous value, a value that the user has changed while using the app will keep that change

so it seems a user interaction is always necessary for persistence to work.

But it would be good to have an official clarification from the Dash development team.

@alexcjohnson
Copy link
Collaborator

I think this is a duplicate of #1252 - and yes as I said there:

That's as intended: if a value is set via callback, then the thing to be persisted should be the user input that led to that value, and that callback-set value will flow from the persisted user input. But I'm curious what use case you had in mind for callback outputs to be persisted?

ie if we need a single rule for this, I think the current behavior is correct, but there's clearly an appetite for more flexibility so if we can understand it we can certainly think about an extension of the feature.

@gvwilson
Copy link
Contributor

Hi - we are tidying up stale issues and PRs in Plotly's public repositories so that we can focus on things that are most important to our community. If this issue is still a concern, please add a comment letting us know what recent version of our software you've checked it with so that I can reopen it and add it to our backlog. (Please note that we will give priority to reports that include a short reproducible example.) If you'd like to submit a PR, we'd be happy to prioritize a review, and if it's a request for tech support, please post in our community forum. Thank you - @gvwilson

@antonymilne
Copy link

@gvwilson please would it be possible to reopen this issue?

@alexcjohnson I've seen this issued discussed in several places (here, #1252, plotly forums 1 2 3) and there appear to be several uses for it. Let me just add another one here.

It's not possible to persist some user inputs, and so your idea of setting the persisted value through a callback output unfortunately doesn't work e.g. say you have a callback with Input(dcc.Graph, "clickData") and Output(dcc.Dropdown, "value") and you wish to persist the value selected in the dropdown. Values set on the dropdown manually are persisted, while ones that are provoked through clicking on the graph are not, and it's not possible to persist any properties of dcc.Graph.

Outside of concrete users for this, judging by the repeated questions about this over the years, I think that the current behaviour causes quite a bit of confusion and awkward workarounds since it doesn't seem to follow typical Dash user expectations. If the proposed behaviour might be undesirable in some scenarios then perhaps there could be a new option that switched between the two behaviours?

This may seem like a minor thing but for our specific case it turns out to actually be quite important 😅 So if there were any way to prioritise this issue then it would be super helpful and we would be very grateful 🙏 My colleague @petar-qb and I would even be happy to try and contribute a fix if you could offer some guidance.

@gvwilson gvwilson reopened this Oct 31, 2024
@gvwilson gvwilson added bug something broken P3 backlog labels Oct 31, 2024
@gvwilson gvwilson changed the title [BUG]Component properties set through dynamic callbacks cannot be persisted. Component properties set through dynamic callbacks cannot be persisted Oct 31, 2024
@antonymilne
Copy link

@gvwilson thanks very much for reopening this! Just to understand, what does priority P3 mean in practice? Is there anything we could do to accelerate this being fixed? @petar-qb and I are very happy to help raise a PR or similar, but we would need some discussion with the Dash team first about what would be correct solution and how to implement it.

@gvwilson
Copy link
Contributor
gvwilson commented Nov 1, 2024

Hi @antonymilne - P1 means "someone should be working on it right now", P2 is "we're looking at including it in the next sprint", and P3 is "everything else" :-) If you and @petar-qb are willing to work up a PR, I can ask @T4rk1n to chat with you about what would be required where - we're hoping to get a Dash release out next week, so we could start the conversation after that.

@antonymilne
Copy link

Thanks for the information @gvwilson, that's very helpful. We'd love to talk to @T4rk1n about it however works best for him.

@antonymilne
Copy link

Hello @gvwilson. Now that the Dash release is done we'd love to try and make some progress on this. Please do let me know the best way forward 🙏

@gvwilson
Copy link
Contributor
gvwilson commented Nov 7, 2024

hi @antonymilne - can you please email me (greg.wilson@plot.ly) and suggest a few times Monday or Tuesday next week that would work for a quick chat and I'll set one up? thanks - @gvwilson

@T4rk1n
Copy link
Contributor
T4rk1n commented Nov 14, 2024

For references on how we record the persistence, the function to call is:

export function recordUiEdit(layout, newProps, dispatch) {

An example of the call can be found here:

recordUiEdit(_dashprivate_layout, newProps, dispatch);

The layout parameter is the "dry component", structured {type, namespace, props}, new props are the dict to change and dispatch is for the redux store dispatch.

I think the easiest way to ensure the record without adding a call every where would be to refactor the updateProps action

export const updateProps = createAction(getAction('ON_PROP_CHANGE'));

Into a function like:

// Change the variable name of the action
export const onPropChange = createAction(getAction('ON_PROP_CHANGE'));

export function updateProps(payload) {
  return (dispatch, getState) => {
       const component = path(payload.itempath, getState().layout);
       recordUiEdit(component, payload.props, dispatch);
       dispatch(onPropChange(payload));
  }
}

With that can remove the call at

recordUiEdit(_dashprivate_layout, newProps, dispatch);

If we want to put a flag to enable the persistence on a callback basis, could add an argument to

dash/dash/_callback.py

Lines 63 to 74 in 9566578

def callback(
*_args,
background=False,
interval=1000,
progress=None,
progress_default=None,
running=None,
cancel=None,
manager=None,
cache_args_to_ignore=None,
on_error: Optional[Callable[[Exception], Any]] = None,
**_kwargs,

Pass it along on the insert_callback function and inserted in the spec dictionary:

dash/dash/_callback.py

Lines 249 to 263 in 9566578

callback_spec = {
"output": callback_id,
"inputs": [c.to_dict() for c in inputs],
"state": [c.to_dict() for c in state],
"clientside_function": None,
# prevent_initial_call can be a string "initial_duplicates"
# which should not prevent the initial call.
"prevent_initial_call": prevent_initial_call is True,
"long": long
and {
"interval": long["interval"],
},
"dynamic_creator": dynamic_creator,
"no_output": no_output,
}

Then add the same key to the typescript definition:

export interface ICallbackDefinition {
clientside_function?: {
namespace: string;
function_name: string;
};
input: string;
inputs: ICallbackProperty[];
output: string;
outputs: ICallbackProperty[];
prevent_initial_call: boolean;
state: ICallbackProperty[];
long?: LongCallbackInfo;
dynamic_creator?: boolean;
running: any;
no_output?: boolean;

We can return to the updateProps function and do a check

const {enable_persistence, ...restPayload} = payload;
if (payload.source !== "response" || enable_persistence) {
       const component = path(payload.itempath, getState().layout);
       recordUiEdit(component, payload.props, dispatch);
} 

Then just add the enable_persistence to the appliedProps in executedCallbacks

        function applyProps(id: any, updatedProps: any, enable_persistence: boolean) {
             ...

            dispatch(
                updateProps({
                    itempath,
                    props,
                    source: 'response',
                    enable_persistence,
                })
            );

Then the call

const appliedProps = applyProps(parsedId, props);

To

                        const appliedProps = applyProps(parsedId, props, cb.callback.enable_persistence);

@petar-qb
Copy link

Wow @T4rk1n, this is super helpful and will surely accelerate the developing! 🏅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken P3 backlog
Projects
None yet
Development

No branches or pull requests

7 participants