Fixed the TagSelector reactivity.
use_server_future was not reliably re-running when url_tag changed during direct URL navigation, causing tags to remain unselected. use_resource provides consistent reactivity across all navigation methods and handles both data fetching and selection logic atomically.
This commit is contained in:
@ -30,18 +30,20 @@ pub fn BlogItem(title: String, slug: String, author: String, summary: String,
|
||||
tags: Vec<String>)
|
||||
-> Element
|
||||
{
|
||||
println!("Blog Item: {title} -- [{tags:?}]");
|
||||
rsx!
|
||||
{
|
||||
li
|
||||
article
|
||||
{
|
||||
key: "{slug}",
|
||||
class: "blog_item",
|
||||
|
||||
h2
|
||||
{
|
||||
Link
|
||||
{
|
||||
to: Page::Post { slug: slug.clone() },
|
||||
h1 { "{title}" }
|
||||
|
||||
"{title}"
|
||||
}
|
||||
}
|
||||
|
||||
TagList
|
||||
@ -55,7 +57,7 @@ pub fn BlogItem(title: String, slug: String, author: String, summary: String,
|
||||
}
|
||||
}
|
||||
|
||||
h4 { "Author: {author}" }
|
||||
p { b { "Author: {author}" } }
|
||||
|
||||
p { "{summary}" }
|
||||
}
|
||||
@ -66,8 +68,10 @@ pub fn BlogItem(title: String, slug: String, author: String, summary: String,
|
||||
#[component]
|
||||
pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
|
||||
{
|
||||
let list = use_server_future(move || {
|
||||
let categories = tags().iter().cloned().collect();
|
||||
let list = use_server_future(move ||
|
||||
{
|
||||
let t = tags();
|
||||
let categories = t.iter().cloned().collect();
|
||||
|
||||
async move { get_blog_list(categories).await }
|
||||
})?;
|
||||
@ -75,12 +79,11 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
|
||||
rsx!
|
||||
{
|
||||
section
|
||||
{
|
||||
class: "blog_list_style",
|
||||
ul
|
||||
{
|
||||
class: "blog_list",
|
||||
|
||||
h1 { class: "blog_title", "Runes & Ramblings" }
|
||||
|
||||
if let Some(Ok(lores)) = &*list.read()
|
||||
{
|
||||
for lore in lores
|
||||
@ -109,7 +112,7 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[component]
|
||||
pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element
|
||||
@ -130,49 +133,108 @@ pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element
|
||||
},
|
||||
}
|
||||
|
||||
span { "{tag}" }
|
||||
"{tag}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Using use_resource instead of use_server_future for URL-dependent tag selection
|
||||
//
|
||||
// While use_server_future should theoretically be reactive when reading signals in the
|
||||
// closure (per Dioxus docs), in practice it doesn't reliably re-run when the url_tag
|
||||
// signal changes, especially during direct URL navigation (typing URLs in browser).
|
||||
//
|
||||
// use_resource provides more reliable reactivity for this use case because:
|
||||
// 1. It explicitly depends on url_tag and consistently re-runs when it changes
|
||||
// 2. It handles both async data fetching AND selection logic in a single atomic operation
|
||||
// 3. It works consistently across all navigation methods (links, direct URLs, etc.)
|
||||
// 4. It avoids timing coordination issues between separate hooks
|
||||
//
|
||||
// This approach combines fetching available tags from the server with determining
|
||||
// which tags should be selected based on the current URL, returning both pieces
|
||||
// of data together for clean state management.
|
||||
#[component]
|
||||
pub fn TagSelector(show_all: Signal<bool>, toggled_tags: Signal<HashSet<String>>) -> Element
|
||||
pub fn TagSelector(url_tag: ReadOnlySignal<String>,
|
||||
toggled_tags: Signal<HashSet<String>>) -> Element
|
||||
{
|
||||
let toggle_all: bool = show_all();
|
||||
// Use use_resource to handle both fetching tags AND initializing selection
|
||||
let tags_and_selection = use_resource(move ||
|
||||
{
|
||||
let current_url = url_tag();
|
||||
|
||||
println!("Tag Selector toggled tags: {:?}", toggled_tags());
|
||||
let tags_future = use_server_future(move || async move { get_tags().await })?;
|
||||
async move
|
||||
{
|
||||
match get_tags().await
|
||||
{
|
||||
Ok(available_tags) =>
|
||||
{
|
||||
// Determine what should be selected based on URL
|
||||
let should_show_all = current_url.is_empty() || current_url == "all";
|
||||
|
||||
use_effect(move ||
|
||||
let selected_tags: HashSet<String> = if should_show_all
|
||||
{
|
||||
if let Some(Ok(tags)) = &*tags_future.read()
|
||||
available_tags.iter().cloned().collect()
|
||||
}
|
||||
else
|
||||
{
|
||||
if toggle_all
|
||||
let url_categories = convert_categories(¤t_url);
|
||||
|
||||
let filtered: HashSet<String> = url_categories.into_iter()
|
||||
.filter(|tag| available_tags.contains(tag))
|
||||
.collect();
|
||||
|
||||
filtered
|
||||
};
|
||||
|
||||
Ok((available_tags, selected_tags))
|
||||
}
|
||||
|
||||
Err(e) =>
|
||||
{
|
||||
for tag in tags
|
||||
{
|
||||
toggled_tags.write().insert(tag.clone());
|
||||
eprintln!("RESOURCE - Error: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Separate effect to update toggled_tags when resource completes
|
||||
// This separates reading the resource from writing to toggled_tags
|
||||
use_effect(move ||
|
||||
{
|
||||
if let Some(Ok((_, selected_tags))) = &*tags_and_selection.read()
|
||||
{
|
||||
toggled_tags.set(selected_tags.clone());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
rsx!
|
||||
{
|
||||
section
|
||||
{
|
||||
class: "blog_nav_style",
|
||||
div
|
||||
class: "blog_nav",
|
||||
|
||||
h2 { class: "visually_hidden", "Filters and Navigation" }
|
||||
|
||||
fieldset
|
||||
{
|
||||
class: "tag_style",
|
||||
h2 { "Categories" }
|
||||
|
||||
legend { "Category Filter" }
|
||||
|
||||
ul
|
||||
{
|
||||
class: "tag_list",
|
||||
|
||||
if let Some(Ok(tags)) = tags_future()
|
||||
match &*tags_and_selection.read()
|
||||
{
|
||||
for tag in tags
|
||||
Some(Ok((available_tags, _))) =>
|
||||
{
|
||||
rsx!
|
||||
{
|
||||
for tag in available_tags
|
||||
{
|
||||
li
|
||||
{
|
||||
@ -181,14 +243,41 @@ pub fn TagSelector(show_all: Signal<bool>, toggled_tags: Signal<HashSet<String>>
|
||||
|
||||
ToggleTag
|
||||
{
|
||||
tag: &tag,
|
||||
tag: tag.clone(),
|
||||
toggled_tags: toggled_tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Err(_)) =>
|
||||
{
|
||||
rsx!
|
||||
{
|
||||
li { "Error loading tags" }
|
||||
}
|
||||
}
|
||||
|
||||
None =>
|
||||
{
|
||||
rsx!
|
||||
{
|
||||
li { "Loading tags..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_categories(categories: &str) -> HashSet<String>
|
||||
{
|
||||
categories
|
||||
.split('+')
|
||||
.filter(|s| !s.is_empty() && *s != "all")
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -30,13 +30,16 @@ pub fn PostHeaderAuthor(adventurer: Adventurer) -> Element
|
||||
{
|
||||
rsx!
|
||||
{
|
||||
h4
|
||||
p
|
||||
{
|
||||
b
|
||||
{
|
||||
"Author: ",
|
||||
a { href: "{adventurer.legend.profile}", "{adventurer.name} @{adventurer.handle}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PostHeader(tale: Tale) -> Element
|
||||
@ -99,7 +102,7 @@ pub fn BlogPost(slug: Signal<String>, children: Element) -> Element
|
||||
{
|
||||
article
|
||||
{
|
||||
class: "blog_post_style",
|
||||
class: "blog_post",
|
||||
|
||||
if let Some(Ok(tale)) = (post_future.value())()
|
||||
{
|
||||
@ -137,36 +140,31 @@ pub fn TagNav() -> Element
|
||||
{
|
||||
section
|
||||
{
|
||||
class: "blog_nav_style",
|
||||
div
|
||||
{
|
||||
class: "tag_style",
|
||||
h2 { "Categories" }
|
||||
ul
|
||||
class: "blog_nav",
|
||||
|
||||
fieldset
|
||||
{
|
||||
class: "tag_list",
|
||||
|
||||
legend { "Category Filter" }
|
||||
|
||||
if let Some(Ok(categories)) = &*tags.read()
|
||||
{
|
||||
for tag in categories
|
||||
{
|
||||
li
|
||||
{
|
||||
key: "{tag}",
|
||||
class: "tag_item",
|
||||
Link
|
||||
{
|
||||
class: "tag_item tag_{tag}",
|
||||
|
||||
to: Page::Blog { tag: tag.clone() },
|
||||
|
||||
"{tag}"
|
||||
}
|
||||
|
||||
//a { href: "/blog/{tag}", "{tag}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let Some(Err(e)) = &*tags.read()
|
||||
{
|
||||
p { "Unable to show desired post." }
|
||||
p { "Unable to load tags." }
|
||||
p { "{e}" }
|
||||
}
|
||||
else
|
||||
@ -177,4 +175,3 @@ pub fn TagNav() -> Element
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,15 +37,10 @@ pub fn TagList(children: Element) -> Element
|
||||
{
|
||||
rsx!
|
||||
{
|
||||
h5
|
||||
{
|
||||
class: "blog_tag_style",
|
||||
|
||||
ul
|
||||
{
|
||||
class: "tag_list",
|
||||
class: "blog_tag tag_list",
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,34 +7,12 @@ use crate::page::Page;
|
||||
|
||||
|
||||
|
||||
fn convert_categories(categories: &str) -> HashSet<String>
|
||||
{
|
||||
categories
|
||||
.split('+')
|
||||
.filter(|s| !s.is_empty() && *s != "all")
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Blog page
|
||||
#[component]
|
||||
pub fn Blog(tag: ReadOnlySignal<String>) -> Element
|
||||
{
|
||||
let mut show_all: Signal<bool> =
|
||||
use_signal(|| tag().is_empty() || tag() == "all");
|
||||
|
||||
let mut categories: Signal<HashSet<String>> =
|
||||
use_signal(|| convert_categories(&tag()));
|
||||
|
||||
use_effect(move ||
|
||||
{
|
||||
let new_tags = convert_categories(&tag());
|
||||
categories.set(new_tags);
|
||||
});
|
||||
|
||||
println!("Blog Categories: {:?}", categories());
|
||||
use_signal(|| HashSet::new());
|
||||
|
||||
rsx!
|
||||
{
|
||||
@ -47,13 +25,13 @@ pub fn Blog(tag: ReadOnlySignal<String>) -> Element
|
||||
|
||||
BlogList
|
||||
{
|
||||
tags: categories.clone()
|
||||
tags: categories
|
||||
}
|
||||
|
||||
TagSelector
|
||||
{
|
||||
show_all: show_all.clone(),
|
||||
toggled_tags: categories.clone()
|
||||
url_tag: tag,
|
||||
toggled_tags: categories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user