Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ create-missing = true

[output.html]
default-theme = "light"
preferred-dark-theme = "coal"
preferred-dark-theme = "ayu"
# copy-fonts = true
# additional-css = ["custom.css", "custom2.css"]
# additional-js = ["custom.js"]
Expand Down
20 changes: 11 additions & 9 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
- [Watch Mode](./02_11_watch_mode.md)

# Frontend
- [Frontend](./frontend/04_frontend.md)
- [Setup](./frontend/04_01_setup.md)
- [Starting the Application](./frontend/04_02_app_startup.md)
- [Components](./frontend/04_03_components.md)
- [Layout Components](./frontend/04_03_01_layout.md)
- [Reusable Components](./frontend/04_03_02_reusable_components.md)
- [State management](./frontend/04_04_state_management.md)
- [Global state](./frontend/04_04_01_global_state.md)
- [Local state](./frontend/04_04_02_local_state.md)
- [Frontend](./frontend/03_frontend.md)
- [Setup](./frontend/03_01_setup.md)
- [Starting the Application](./frontend/03_02_app_startup.md)
- [Components](./frontend/03_03_components.md)
- [Layout Components](./frontend/03_03_01_layout.md)
- [Reusable Components](./frontend/03_03_02_reusable_components.md)
- [State management](./frontend/03_04_state_management.md)
- [Global state](./frontend/03_04_01_global_state.md)
- [Local state](./frontend/03_04_02_local_state.md)
- [App effects](./frontend/03_04_03_effects.md)
- [Event handlers](./frontend/03_05_event_handlers.md)
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,34 @@ dioxus serve
```

Now, your development environment is up and running. Changes you make to your source code will automatically be reflected in the served application, thanks to the watching capabilities of both the Tailwind compiler and the Dioxus server. You're now ready to start building your Dioxus application!

## Logging

For applications that run in the browser, having a logging mechanism can be very useful for debugging and understanding the application's behavior.

The first step towards this involves installing the `wasm-logger` crate. You can do this by running the following command:

```bash
cargo add wasm-logger
```

Once `wasm-logger` is installed, you need to initialize it in your `main.rs` file. Here's how you can do it:

`main.rs`
```diff
...
fn main() {
+ wasm_logger::init(wasm_logger::Config::default().module_prefix("front"));
// launch the web app
dioxus_web::launch(App);
}
...
```

With the logger initialized, you can now log messages to your browser's console. The following is an example of how you can log an informational message:

```rust
log::info!("Message on my console");
```

By using this logging mechanism, you can make your debugging process more straightforward and efficient.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ In the `main.rs` file, the `App` component needs to be updated to introduce some

`main.rs`
```diff
...
-use components::{FilmModal, Footer, Header};
+use components::{FilmCard, FilmModal, Footer, Header};
use dioxus::prelude::*;
use models::FilmModalVisibility;
+use shared::models::Film;

...

fn App(cx: Scope) -> Element {
Expand Down Expand Up @@ -97,7 +104,8 @@ use crate::models::{ButtonType, FilmModalVisibility};

#[derive(Props)]
pub struct FilmModalProps<'a> {
on_create_or_update: EventHandler<'a, MouseEvent>,
- on_create_or_update: EventHandler<'a, MouseEvent>,
+ on_create_or_update: EventHandler<'a, Film>,
on_cancel: EventHandler<'a, MouseEvent>,
+ #[props(!optional)]
+ film: Option<Film>,
Expand Down
128 changes: 128 additions & 0 deletions docs/src/frontend/03_04_03_effects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# App Effects

Alright folks, we've got our state management all set up. Now, the magic happens! We need to synchronize the values of that state when different parts of our app interact with our users.

Imagine our first call to the API to fetch our freshly minted films, or the moment when we open the Film Modal in edit mode. We need to pre-populate the form with the values of the film we're about to edit.

No sweat, we've got the `use_effect` hook to handle this. This useful hook allows us to execute a function when a value changes, or when the component is mounted or unmounted. Pretty cool, huh?

Now, let's break down the key parts of the `use_effect` hook:
- It should be nestled inside a closure function.
- If we're planning to use a `use_state` hook inside it, we need to `clone()` it or pass the ownership using `to_owned()` to the closure function.
- The parameters inside the `use_effect()` function include the Scope of our app (`cx`), the `dependencies` that will trigger the effect again, and a `future` that will spring into action when the effect is triggered.

Here's a quick look at how it works:

```rust
{
let some_state = some_state.clone();
use_effect(cx, change_dependency, |_| async move {
// Do something with some_state or something else
})
}
```

Sure, here's a revised version with a more formal tone:

## Film Modal

We will begin by adapting our `FilmModal` component. This will be modified to pre-populate the form with the values of the film that is currently being edited. To accomplish this, we will use the `use_effect` hook.

```diff
...

pub fn FilmModal<'a>(cx: Scope<'a, FilmModalProps>) -> Element<'a> {
let is_modal_visible = use_shared_state::<FilmModalVisibility>(cx).unwrap();
let draft_film = use_state::<Film>(cx, || Film {
title: "".to_string(),
poster: "".to_string(),
director: "".to_string(),
year: 1900,
id: Uuid::new_v4(),
created_at: None,
updated_at: None,
});

+ {
+ let draft_film = draft_film.clone();
+ use_effect(cx, &cx.props.film, |film| async move {
+ match film {
+ Some(film) => draft_film.set(film),
+ None => draft_film.set(Film {
+ title: "".to_string(),
+ poster: "".to_string(),
+ director: "".to_string(),
+ year: 1900,
+ id: Uuid::new_v4(),
+ created_at: None,
+ updated_at: None,
+ }),
+ }
+ });
+ }

...
}
```

In essence, we are initiating an effect when the `film` property changes. If the `film` property is `Some(film)`, we set the `draft_film` state to the value of the `film` property. If the `film` property is `None`, we set the `draft_film` state to a new `Film` initial object.

## App Component

Next, we will adapt our `App` component to fetch the films from the API when the app is mounted or when we need to force the API to update the list of films. We'll accomplish this by modifying `force_get_films`. As this state has no type or initial value, it is solely used to trigger the effect.

We will also add HTTP request configurations to enable these functions. We will use the `reqwest` crate for this purpose, which can be added to our `Cargo.toml` file or installed with the following command:

```bash
cargo add reqwest
```

To streamline future requests, we will create a `films_endpoint()` function to return the URL of our API endpoint.

Here are the necessary modifications for the `App` component:

```diff
...

+const API_ENDPOINT: &str = "api/v1";

+fn films_endpoint() -> String {
+ let window = web_sys::window().expect("no global `window` exists");
+ let location = window.location();
+ let host = location.host().expect("should have a host");
+ let protocol = location.protocol().expect("should have a protocol");
+ let endpoint = format!("{}//{}/{}", protocol, host, API_ENDPOINT);
+ format!("{}/films", endpoint)
+}

+async fn get_films() -> Vec<Film> {
+ log::info!("Fetching films from {}", films_endpoint());
+ reqwest::get(&films_endpoint())
+ .await
+ .unwrap()
+ .json::<Vec<Film>>()
+ .await
+ .unwrap()
+}

fn App(cx: Scope) -> Element {
...
let force_get_films = use_state(cx, || ());

+ {
+ let films = films.clone();


+ use_effect(cx, force_get_films, |_| async move {
+ let existing_films = get_films().await;
+ if existing_films.is_empty() {
+ films.set(None);
+ } else {
+ films.set(Some(existing_films));
+ }
+ });
+ }
}
```

What we have done here is trigger an effect whenever there is a need to fetch films from our API. We then evaluate whether there are any films available. If there are, we set the `films` state to these existing films. If not, we set the `films` state to `None`. This allows us to enhance our `App` component with additional functionality.
Loading