question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Odd behavior with reactive inputs/outputs and reading/writing values from file

See original GitHub issue

I re-created an odd issue I just ran into. Here’s my reproducible example:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import dash
import dash_core_components as dcc
import dash_html_components as html


def generate_inputs():

    print('getting value!')
    f = open('./value.txt', 'r')
    current_value = str(f.read())
    f.close()

    print('current_value: ' + str(current_value))

    input_box = [html.Label(html.Strong('value to write; currently: ')),
                dcc.Input(type='number', value=current_value, id='input_value')]

    return input_box


app = dash.Dash()

app.layout = html.Div([
    html.H2('debug: getting and setting'),

    html.Div(
         generate_inputs()
    ),

    html.Div(id='file_value_written')

]) # app.layout


@app.callback(
    dash.dependencies.Output(component_id='file_value_written', component_property='children'),
    [dash.dependencies.Input(component_id='input_value', component_property='value')])
def update_file_value(value_to_write):

    print('set value!')
    print('value_to_write: ' + str(value_to_write))
    f = open('./value.txt', 'w')
    f.write(str(value_to_write))
    f.close()
    
    return value_to_write


if __name__ == '__main__':
    app.run_server(debug=True)

I walked through my process step by step as well:

| step | description                     | printed                              |   cat | gui   |        |
|      |                                 |                                      |       | box   | output |
|------+---------------------------------+--------------------------------------+-------+-------+--------|
|    0 | touch value.txt                 | -                                    |     - | -     | -      |
|    1 | echo "1" > value.txt            | -                                    |     1 | -     | -      |
|    2 | python debug-get-set.py         | getting value! current_value: 1 (2x) |     1 | -     | -      |
|    3 | open localhost:8050             | set value! value_to_write: 1         |     1 | 1     | 1      |
|    4 | click in box; delete 1          | set value! written value: empty      | empty | blank | blank  |
|    5 | type 2 in box                   | set value! written value: 2          |     2 | 2     | 2      |
|    6 | click to remove cursor from box | no message                           |     2 | 2     | 2      |
|    6 | Ctrl+R; reload page             | set value! value_to_write: 1         |     1 | 1     | 1      |

This seems odd to me, but I’m very new to dash, so please let me know where my thinking is awry. Throughout the changes, we can check four key values:

  • the value read from the file, stored as current_value
  • the dcc.Input default value, passed as value=current_value, to be shown as the prompt in the text input box
  • the value passed to the callback and written to the file, value_to_write, which we call if our dcc.Input value changes (not sure how dash checks for a change)
  • the return from the callback, put on the page with html.Div(id='file_value_written')

Upon load, they all agree on the value being 1. It’s printed, the file contains it, and it’s displayed in the box and below the box. We change the box contents to 2, and the effect pushes through to the html.Div on the screen and the file (checked with cat). It’s also still in the box, obviously.

When we reload we’re told we’re setting the file contents, but that should only change if we’re in callback. That only happens if the current text box changes, and we didn’t touch it. It almost behaves like the value currently in the box might be cached (or in some sense not really changed), and even when we delete/replace it with 2, the cached/stored value is 1. On refresh, things work in reverse: 2 is treated as the last value seen and the cached value, 1, shows up as a user change and is written. Is that a crazy interpretation?

What I expected: reloading would re-run things top to bottom, it should read 2 from the file, use 2 as the default value in the box, and wait for a callback trigger if the user changes what’s in the box 2.

This was a wild chase to just work through in my head and I am coming up clueless. Thanks for taking a look!

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
ned2commented, Sep 6, 2017

Nope, this is not odd. In the code you posted, you’re reading the value of the file within the serve_layout function, which is only ever evaluated once – the first time the app is loaded. So every time you hit refresh, you’re getting the same layout object that was previously evaluated and assigned to app.layout, irrespective of the contents of that file.

You raise an interesting point though, when you assign app.layout to the actual function serve_layout, as opposed to evaluating it and assigning app.layout to the component object it returns, this behaviour changes to display the current results of the file. It would seem that when you do this, Dash does indeed evaluate app.layout on every page load.

So which should you use? I think it just depends on what you’re trying to do. Personally, I think it’s better practice to default to putting all the dynamic generation of things within your callbacks (so in your case you would read the value of your file only within callbacks). This way you’re less likely to run into surprises like this. Also, using a static app.layout (as opposed function that’s evaluated on every page load) also means that any run-time errors in the evaluation of your layout will happen immediately on initialisation of your app rather than later on while someone is using it. But there may be times we you really do need dynamic evaluation of app.layout (maybe you have to call a database or something) so then you’d use a function.

0reactions
jwhendycommented, Sep 6, 2017

Thanks for the tips @ned2 . I didn’t initially expect the callbacks to run all the time, but now that @chriddyp clarified how those work (run on every load/refresh), having anything dynamic happen in them is a great point I hadn’t considered. Thanks to both of you for enduring a noob 😃

Read more comments on GitHub >

github_iconTop Results From Across the Web

Odd behavior of the reactiveValuesToList() function in R shiny
My app has 3 components: checkBoxGroupInput() : with 3 choices. uiOutput() : to dynamically create numeric inputs based on the boxes checked by ......
Read more >
Chapter 15 Reactive building blocks | Mastering Shiny
There are three fundamental building blocks of reactive programming: reactive values, reactive expressions, and observers. You've already seen most of the ...
Read more >
Chapter 3 Reactivity | A Minimal Book Example - Bookdown
This is a minimal example of using the bookdown package to write a book. The output format for this example is bookdown::gitbook.
Read more >
Communicating with Shiny via JavaScript
Shiny was designed with an emphasis on distinct input and output ... send a reactive input value to R with this JavaScript function:...
Read more >
Chapter 9 Writing and reading files - STAT 545
There will be many occasions when you need to write data from R. Two main examples: ... First tip: today's outputs are tomorrow's...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found