Bring Drag & Drop to Dash with Dashboard Engine. 💫 Learn how at our next webinar!

How to pass and read a variable (string) from a dcc.Store to a clientside_callback?

Hi,

I have a question regarding clientside callbacks and the parameters passed to the clientside function from the Python script. I am executing this checkout function to Paddle (see here more information), but it seems that the email does not get passed properly. I store my email address from a dbc.input form in dcc.Store(id='email', storage_type='memory'). Here is the callback function:

app.clientside_callback(
    """
    function(n_clicks, email) {
        if (n_clicks){
            //document.write(n_clicks)
            //document.write(email)
            Paddle.Checkout.open({
                product: 11111,
                email: email
                });
        }
    }
    """,
    Output('dummy_div', 'children'),
    [Input('btn', 'n_clicks')],
    [State('email', 'data')]
)

Upon execution, it shows that the n_clicks becomes 1 but that the email becomes ‘undefined’, meaning that email is not passed properly from the dcc.Store. The execution of the script goes fine, without errors but also without the email address. I have two questions:

  1. How can I check whether the dcc.Store contains the email from the input form?
  2. Are the JavaScript syntaxes passes properly and is the JavaScript snippet correct? I an not a JavaScript expert…

Best,
Vijay

@VijayB could you share the code on how you are storing the value from dcc.input to dcc.store(id=‘email’).

1 Like

@HarishOsthe The part of the code where the storing happens is:

#import dependencies
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc

external_scripts = [
    {'src': 'https://cdn.paddle.com/paddle/paddle.js'}
]

app = dash.Dash(__name__, suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.LITERA], external_scripts=external_scripts,
                meta_tags=[{'name': 'viewport',
                            'content': 'width=device-width, initial-scale=1.0'}])

server = app.server

global email #to be used as a variable else in the script

# Input layout, log in form:
email_log_in = dbc.FormGroup(
    [
        dbc.Label("Email", html_for="email"),
        dbc.Input(type="email", id="email-log-in", placeholder="Enter email")
    ]
)

password_log_in = dbc.FormGroup(
    [
        dbc.Label("Password", html_for="password"),
        dbc.Input(type="password", id="password-log-in", placeholder="Enter password")
    ]
)

log_in_form = dbc.Form([
    email_log_in,
    password_log_in,
    dbc.Button("Log In", id="btn-log-in", color="secondary", block=True, n_clicks=0),
    html.Br(),
    html.Div(id="log-in-error")
])

app.layout = html.Div([
    html.Div(id='page-content', className='content'),  
    dcc.Store(id='email', storage_type='memory'),
    html.Div(id='dummy_div'),
    dcc.Location(id='url', refresh=False),
    ])

# callback, getting user from Firebase Authentication
@app.callback(
    [Output("email", "data"),
      Output("log-in-error", "children"),
      Output('url_log_in', 'pathname')],
    Input("btn-log-in", "n_clicks"),
    [State("email-log-in", "value"),
     State("password-log-in", "value")]
    )
def log_in(n_clicks, inp_email, password):
    if n_clicks>0:      
        import requests
        import json
        import datetime
        
        if inp_email == None:
            return ['', dbc.Alert([html.Abbr("\u26A0"), ' ', 'Please enter your email address'], color='warning', dismissable=True), None]
        
        if password == None:
            return ['', dbc.Alert('Please enter your password', color='warning', dismissable=True), None]           
    
        payload = {"email":inp_email,
                    "password":password,
                    "returnSecureToken": True}
        
        apikey = 'API_firebase_authentication'
        
        r = requests.post('https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={}'.format(apikey),data=payload)
        
        print(f'imp_email: {inp_email}')
        print(type(inp_email))

        user_rec = json.loads(r.content)
        if r.ok:
            # return the email as a global variable
            global email
            email = str(inp_email or '')
            print(f'email: {email}')
            print(type(inp_email))

            # Get the ID token sent by the client
            id_token = user_rec['idToken']
            # Set session expiration to 1 days.
            expires_in = datetime.timedelta(days=1)

            try:
                # Create the session cookie. This will also verify the ID token in the process.
                # The session cookie will have the same claims as the ID token.
                session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
                response = jsonify({'status': 'success'})
                # Set cookie policy for session cookie.
                expires = datetime.datetime.now() + expires_in
                response.set_cookie(
                    'session', session_cookie, expires=expires, httponly=True, secure=True)
            except Exception:
                return [inp_email, None , '/']
            return [inp_email, dbc.Alert([html.Abbr("\u2713"), ' ', f'Sucessfully logged in {inp_email}, ', html.A("refresh page \u21BB", href="/", className="alert-link")], color='success'), '/']
        elif r.status_code == 400:
            if user_rec['error']['message'] == 'EMAIL_NOT_FOUND':
                e = f'{inp_email} is not in our database, please sign up first'
            elif user_rec['error']['message'] == 'INVALID_PASSWORD':
                e = 'Your password is not valid, please enter the correct password'
            else:
                e = 'Something went wrong, plase retry'
            return ['', dbc.Alert([html.Abbr("\u26A0"), ' ', e], color='warning', dismissable=True), None]
    return['', '', None]

The results of the print statements are:

imp_email: test@tester.nl
<class 'str'>
email: test@tester.nl
<class 'str'>

Are the JavaScript syntaxes passes properly and is the JavaScript snippet correct?

Yes, the javascript code looks correct.

How can I check whether the dcc.Store contains the email from the input form?

The easiest way to check what is happening is to replace this clientside callback for an actual callback like:

@app.callback(
    Output('dummy_div', 'children'),
    [Input('btn', 'n_clicks')],
    [State('email', 'data')]
)
def update_dummy(n_clicks, email):
    if not n_clicks:
        raise dash.exceptions.PreventUpdate
    return email

Based in your last reply, I suspect that this could be related to the data in dcc.Store being flushed by a page refresh, since your application seems to have a multi-page layout with some fairly complex authentication mechanism (which I will keep in my “pocket” in case I need it :smiley: ). If that is the case, then simply changing the storage_type to "session" might fix the issue…

I am not sure which side-effect to your app this declaration might have, however it might be a bad idea…

@jlfsjunior Thank you, that solved my problem, can’t believe how sloppy I was as I had storage_type='session' already everywhere else… :man_facepalming:

As for global email, I do this to make the email variable from the input a global variable. With this variable, I will do some calculations later on in the script. To be more specific I check access for the user to this dashboard. In Google Firestore I have a document where access is described for email addresses. Then with an if-else statement, the script will determine the access, it goes something like:

email = 'test@tester.nl'
# look up the acces for the email in Firestore
from google.cloud import firestore
import json
db = firestore.Client()
subscriptions = db.collection(email).where('status', '==', 'active').get()
active_dashboards = [json.loads(subscription.to_dict()['passthrough'])['dashboards'] for subscription in subscriptions]
if 'this_dashbaord' in active_dashboards:
   execute script
else:
    403: Access denied

Of course, this will only work if the variable is created and used in the same script, like index.py. Locally it works, but I still have to test it in the cloud.

Feel free to reuse and improve this. The Firebase authentication and Firestore database together with this in-app authorization is definitely a powerful instrument for this use case and I would recommend it :smiley:

Best,

Vijay Bhagwandas

I am glad that it worked! :smiley:

The global email will certainly crash your deployed application for the reasons explained in the documentation I posted. It would ultimately persist valid email values to unlogged users and mess up the authentication you are setting.

To avoid the issue, you can simply use the email stored in dcc.Store, which is local. I am not sure of what your script does, but I guess you could invoke it from a callback and use State("email", "data") to access it.

1 Like