-
-
Notifications
You must be signed in to change notification settings - Fork 628
FAQs
Check this FAQ, seek inside the GitHub discussions, Reddit, StackOverflow or connect with other developers on our Discord server. Because NiceGUI is build on FastAPI in the backend and Vue3 + Quasar in the frontend, you can also solve a lot of problems/questions by looking into their documentation.
When starting new questions or discussions, please put effort in formulating your post. Quite a number of people will read and think about your message. Make it worth their time.
If you encounter a bug or other issue with NiceGUI, the best way to report it is by opening a new issue on our GitHub repository.
Write your question/suggestion/info in your main language and translate it with an online tool like Google Translate, ChatGPT or similar. The output is often surprisingly good.
See the question about long running functions below.
NiceGUI (and the underlying FastAPI) are async frameworks. That means, no io-bound or cpu-bound tasks should be directly executed on the main thread but rather be "awaited". Otherwise they block execution on the main loop. See https://fastapi.tiangolo.com/async/ for a good in-depth explanation.
There are great libraries like aiofiles (for writing files async) and httpx (for async requests) or NiceGUIs generic run.io_bound
to do io-bound work.
Cpu-bound tasks need to be run in another process which can be archived with NiceGUIs run.cpu_bound
.
See our examples ffmepg, opencv webcam, search-as-you-type, progress and the script executor which could be helpful.
See below.
You are probably experiencing Python's "late binding".
Try
for i in [1, 2, 3]:
ui.button(i, on_click=lambda: ui.label(i))
vs.
for i in [1, 2, 3]:
ui.button(i, on_click=lambda i=i: ui.label(i))
The i=i
captures the i
within the lambda statement.
When the lambda is eventually evaluated, it would use the current value of i
(which is now 3).
But with i=i
the label is created using a local copy.
You are probably using reload=True
which runs the main code once and then spawnes a subprocess which is killed and relaunched on file change. To avoid evaluation of your code in the first "init" you have several options:
-
Use
ui.run(reload=False)
. Of course, you loose the handy auto-reload feature. But for production this might be the way to go. -
Use some kind of main guard:
if __name__ == '__mp_main__': print("Some message") ui.label("test") ui.run()
This avoids evaluating the code in the
"__main__"
process and restricts it to the child process"__mp_main__"
. -
Use a page decorator:
@ui.page('/') def main(): print("Some message") ui.label("test")
This evaluates the UI only when accessed. But if you have an expensive initialization to do once when starting the script, this might not be the way to go. Page decorators also change the visibility, since it generates a new page per client, so the state is not shared anymore.
-
Move expensive initialization into a startup callback:
def startup(): print("Some message") app.on_startup(startup) ui.label("test")
This way
startup
is only evaluated once in the child process, since the app doesn't start in the main process (unlessreload=False
).
In NiceGUI elements are placed wherever they are created. That allows you to quickly create and comprehend nested layout structures. If you create new elements inside an event handler, NiceGUI will place them in the parent container of the element which fired the event.
To pick some other place in the UI, you can enter its context explicitly. For example, if you have a button outside of a custom element to alter its state:
class LabeledCard(ui.card):
def __init__(self) -> None:
super().__init__()
with self:
ui.label('This is the LabeledCard')
def add_label(self) -> None:
with self: # Make sure to use the context of the card
ui.label('This is a label')
card = LabeledCard()
ui.button('Add label to card', on_click=card.add_label)
Binding is for automatically updating individual UI elements when certain values change. This won't add or remove element, but simply update them with new attributes. The refreshable
decorator wraps a function that will replace a whole container when refresh
is called. This might be more convenient to implement and allows for more complex dependencies (e.g. setting certain style or classes depending on some model state). But you have to be aware of the fact that a whole container element is replaced, which causes more network traffic. And client state, like the cursor position in an input element or the state of a dropdown menu, might get lost.
It cannot be achieved with the ui.upload
element because it uses the internal file picker dialog of the browser. For security reasons the browser does not provide the full path of the selected files. See #283 and #269 for more details.
But we have build an example to show a custom local file browser which acts on the filesystem of the running app (e.g. picking files from the server, not the user machine running the browser). If you are in native mode you can also use the system file picker which provides the paths:
from nicegui import app, ui
async def choose_file():
files = await app.native.main_window.create_file_dialog(allow_multiple=True)
for file in files:
ui.notify(file)
ui.button('choose file', on_click=choose_file)
ui.run(native=True)
You can use both but need to look out for subtile incompatibilities. For example when defining breakpoints. NiceGUI uses Vue3 with Quasar as a web framework because it has tons of greatly customizable UI elements and a huge community. On top of that, we decided early on that Tailwind adds quite a lot of nice styling features which would be a bit more difficult to achieve with Quasar alone.
The problem could be that for example "green-400" is a Tailwind color and not from the Quasar color palette.
The details, why the Tailwind color works in light mode, are a bit more complicated.
But Quasar basically assumes the color should be #fff
.
As the QToggle documentation states, the "color" prop needs to be a name from the Quasar Color Palette.
NiceGUI has a default gap applied so the children's size plus gaps are more than 100%.
Therefore the flex layout automatically wraps elements onto the next "line".
You can fix it by either set the no-wrap
or gap-0
classes.
See this question on StackOverflow and this discussion for more information.
app.storage.user
and app.storage.general
are saved in json files in the .nicegui
folder of your current working dir.
That means, if you deploy the app to a cloud server, the data will be stored there.
NiceGUI does not provide a central data server.
app.storage.browser
is an encrypted session cookie which can also hold small amounts of key/value data.
This data is only stored in the browser cookie and available in memory to the Python code. The browser data expires after 30 days after not accessing.
How to avoid the "reloading because handshake failed" javascript error message which results in a full page reload?
It might be that you are running Gunicorn or another load balancer with multiple workers. In that case the websocket connection may not be delivered to the same instance which created the html page. To avoid that you need switch to another more sophisticated load balancer like Traefic, Nginx or HAProxy which support sticky sessions. A simpler alternative is to just use a single worker and embrace async/await where an event loop ensures high speed concurrency. See the feature request about multiple worker support for details.
Thanks to the auto-index page, NiceGUI is very easy to start with. It also helps when trying or sharing short code snippets and simplifies example code. But as correctly stated on Discord, in 99% of all more complex apps, pages should rather be generated for each user individually using the @ui.page
decorator.