Skip to content
Open
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
48 changes: 48 additions & 0 deletions .github/workflows/everything.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,61 @@ jobs:
- name: Run Tests
run: cargo make tests

e2e:
name: E2E Tests
runs-on: ubuntu-latest
services:
httpbin:
image: kennethreitz/httpbin
ports:
- 8081:80
steps:
- name: Checkout Project
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown

- name: Restore Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: |
.
e2e

- name: Setup trunk
uses: jetli/trunk-action@v0.4.0
with:
version: "latest"

- uses: browser-actions/setup-geckodriver@latest
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Build E2E fixture
run: cd e2e/query-app && trunk build

- name: Start geckodriver
run: geckodriver --port 4444 &

- name: Serve fixture app
run: python3 -m http.server 8000 -d e2e/query-app/dist &

- name: Run E2E tests
run: cd e2e && cargo test -- --test-threads=1

publish:
name: Publish to crates.io
runs-on: ubuntu-latest
needs:
- lint
- build
- test
- e2e
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))
steps:
- name: Checkout Project
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ crates/*/target
examples/*/target
examples/*/dist

e2e/target
e2e/*/target
e2e/*/dist

book-build

Cargo.lock
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
- Removed async_trait requirement from query and mutation traits
- Raise MSRV to 1.84.

### Other Changes

- Fixed use_query and use_prepared_query stalling when the input changes.

## Release 0.9.0

### Breaking Changes
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"examples/helmet-ssr",
"examples/persist",
]
exclude = ["e2e"]
resolver = "2"

[profile.release]
Expand Down
2 changes: 1 addition & 1 deletion crates/bounce/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub use states::slice::Slice;

/// A future-based notion that notifies states when it begins and finishes.
///
/// A future notion accepts a signle argument as input and returns an output.
/// A future notion accepts a single argument as input and returns an output.
///
/// It can optionally accept a `states` parameter which has a type of [`BounceStates`] that can be
/// used to access bounce states when being run.
Expand Down
131 changes: 86 additions & 45 deletions crates/bounce/src/query/use_prepared_query.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use std::cell::RefCell;
use std::rc::Rc;

use serde::de::Deserialize;
use serde::ser::Serialize;
use wasm_bindgen::UnwrapThrowExt;
use yew::platform::pinned::oneshot;
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};

use super::query_states::{
QuerySelector, QuerySlice, QuerySliceAction, QuerySliceValue, RunQuery, RunQueryInput,
};
use super::traits::Query;
use super::use_query::{QueryState, UseQueryHandle};
use super::use_query::{QueryMemoValue, QueryState, UseQueryHandle};
use crate::root_state::BounceRootState;
use crate::states::future_notion::use_future_notion_runner;
use crate::states::input_selector::use_input_selector_value;
Expand Down Expand Up @@ -154,68 +156,107 @@ where
.clone()
};

let value = use_memo(value_state.clone(), |v| match v.value {
Some(QuerySliceValue::Loading { .. }) | None => Err(Suspension::new()),
Some(QuerySliceValue::Completed { id, result: ref m }) => {
Ok((id, Rc::new(QueryState::Completed { result: m.clone() })))
}
Some(QuerySliceValue::Outdated { id, result: ref m }) => Ok((
id,
Rc::new(QueryState::Refreshing {
last_result: m.clone(),
}),
)),
});
let has_prepared = prepared_value.is_some();

{
let input = input.clone();
let run_query = run_query.clone();
let dispatch_state = dispatch_state.clone();

use_memo((), move |_| match prepared_value {
Some(m) => dispatch_state(QuerySliceAction::LoadPrepared {
id,
input,
result: m,
}),
None => run_query(RunQueryInput {
id,
input: input.clone(),
sender: Rc::default(),
is_refresh: false,
}),
use_memo((), move |_| {
if let Some(m) = prepared_value {
dispatch_state(QuerySliceAction::LoadPrepared {
id,
input,
result: m,
});
}
});
}

// Produce a Suspension or a ready value. When the value is not yet available,
// the query is initiated as part of constructing the Suspension (following
// the same pattern as Yew's use_future_with).
let value = use_memo((input.clone(), value_state.clone()), {
let run_query = run_query.clone();
move |(input, value_state): &(Rc<T::Input>, Rc<QuerySelector<T>>)| {
match value_state.value {
None => {
if has_prepared {
// A prepared value was dispatched via LoadPrepared
// and will be available on the next render.
let (suspension, handle) = Suspension::new();
QueryMemoValue::Suspended {
suspension,
_handle: Some(handle),
}
} else {
let (sender, receiver) = oneshot::channel();
run_query(RunQueryInput {
id,
input: input.clone(),
sender: Rc::new(RefCell::new(Some(sender))),
is_refresh: false,
});
QueryMemoValue::Suspended {
suspension: Suspension::from_future(async move {
let _ = receiver.await;
}),
_handle: None,
}
}
}
Some(QuerySliceValue::Loading { .. }) => {
let (suspension, handle) = Suspension::new();
QueryMemoValue::Suspended {
suspension,
_handle: Some(handle),
}
}
Some(QuerySliceValue::Completed { id, ref result }) => QueryMemoValue::Ready {
id,
state: Rc::new(QueryState::Completed {
result: result.clone(),
}),
},
Some(QuerySliceValue::Outdated { id, ref result }) => QueryMemoValue::Ready {
id,
state: Rc::new(QueryState::Refreshing {
last_result: result.clone(),
}),
},
}
}
});

{
let input = input.clone();
let run_query = run_query.clone();

use_effect_with(
(id, input, value_state.clone()),
move |(id, input, value_state)| {
if matches!(value_state.value, Some(QuerySliceValue::Outdated { .. })) {
run_query(RunQueryInput {
id: *id,
input: input.clone(),
sender: Rc::default(),
is_refresh: false,
});
}
use_effect_with((id, input, value_state), move |(id, input, value_state)| {
if matches!(value_state.value, Some(QuerySliceValue::Outdated { .. })) {
run_query(RunQueryInput {
id: *id,
input: input.clone(),
sender: Rc::default(),
is_refresh: false,
});
}

|| {}
},
);
|| {}
});
}

match value.as_ref().as_ref().cloned() {
Ok((state_id, state)) => Ok(UseQueryHandle {
state_id,
input,
match value.as_ref() {
QueryMemoValue::Ready {
id: state_id,
state,
} => Ok(UseQueryHandle {
state: state.clone(),
state_id: *state_id,
input,
dispatch_state,
run_query,
}),
Err((s, _)) => Err(s.clone()),
QueryMemoValue::Suspended { suspension, .. } => Err(suspension.clone()),
}
}
Loading
Loading