change: pass Scope as argument into Resource::read() and Resource::with() (#542)

This commit is contained in:
Greg Johnston 2023-02-19 19:52:31 -05:00 committed by GitHub
parent ce4b0ecbe1
commit 4ff08f042b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 80 additions and 97 deletions

View file

@ -97,10 +97,10 @@ pub fn Counter(cx: Scope) -> impl IntoView {
|_| get_server_count(),
);
let value = move || counter.read().map(|count| count.unwrap_or(0)).unwrap_or(0);
let value = move || counter.read(cx).map(|count| count.unwrap_or(0)).unwrap_or(0);
let error_msg = move || {
counter
.read()
.read(cx)
.map(|res| match res {
Ok(_) => None,
Err(e) => Some(e),
@ -143,7 +143,7 @@ pub fn FormCounter(cx: Scope) -> impl IntoView {
let value = move || {
log::debug!("FormCounter looking for value");
counter
.read()
.read(cx)
.map(|n| n.ok())
.flatten()
.map(|n| n)

View file

@ -60,7 +60,7 @@ pub fn fetch_example(cx: Scope) -> impl IntoView {
// and by using the ErrorBoundary fallback to catch Err(_)
// so we'll just implement our happy path and let the framework handle the rest
let cats_view = move || {
cats.with(|data| {
cats.with(cx, |data| {
data.iter()
.flatten()
.map(|cat| view! { cx, <img src={cat}/> })

View file

@ -38,7 +38,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
let (pending, set_pending) = create_signal(cx, false);
let hide_more_link =
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
move || pending() || stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28;
view! {
cx,
@ -82,7 +82,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
fallback=move || view! { cx, <p>"Loading..."</p> }
set_pending=set_pending.into()
>
{move || match stories.read() {
{move || match stories.read(cx) {
None => None,
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }.into_any()),
Some(Some(stories)) => {

View file

@ -17,13 +17,13 @@ pub fn Story(cx: Scope) -> impl IntoView {
}
},
);
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
let meta_description = move || story.read(cx).and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
view! { cx,
<>
<Meta name="description" content=meta_description/>
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || story.read().map(|story| match story {
{move || story.read(cx).map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">

View file

@ -19,7 +19,7 @@ pub fn User(cx: Scope) -> impl IntoView {
view! { cx,
<div class="user-view">
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || user.read().map(|user| match user {
{move || user.read(cx).map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>

View file

@ -38,7 +38,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
let (pending, set_pending) = create_signal(cx, false);
let hide_more_link =
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
move || pending() || stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28;
view! {
cx,
@ -82,7 +82,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
fallback=move || view! { cx, <p>"Loading..."</p> }
set_pending=set_pending.into()
>
{move || match stories.read() {
{move || match stories.read(cx) {
None => None,
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }.into_any()),
Some(Some(stories)) => {

View file

@ -17,13 +17,13 @@ pub fn Story(cx: Scope) -> impl IntoView {
}
},
);
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
let meta_description = move || story.read(cx).and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
view! { cx,
<>
<Meta name="description" content=meta_description/>
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || story.read().map(|story| match story {
{move || story.read(cx).map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">

View file

@ -19,7 +19,7 @@ pub fn User(cx: Scope) -> impl IntoView {
view! { cx,
<div class="user-view">
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || user.read().map(|user| match user {
{move || user.read(cx).map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>

View file

@ -71,9 +71,10 @@ pub fn ContactList(cx: Scope) -> impl IntoView {
});
let location = use_location(cx);
let contacts = create_resource(cx, move || location.search.get(), get_contacts);
let contacts =
create_resource(cx, move || location.search.get(), get_contacts);
let contacts = move || {
contacts.read().map(|contacts| {
contacts.read(cx).map(|contacts| {
// this data doesn't change frequently so we can use .map().collect() instead of a keyed <For/>
contacts
.into_iter()
@ -126,12 +127,15 @@ pub fn Contact(cx: Scope) -> impl IntoView {
get_contact,
);
let contact_display = move || match contact.read() {
let contact_display = move || match contact.read(cx) {
// None => loading, but will be caught by Suspense fallback
// I'm only doing this explicitly for the example
None => None,
// Some(None) => has loaded and found no contact
Some(None) => Some(view! { cx, <p>"No contact with this ID was found."</p> }.into_any()),
Some(None) => Some(
view! { cx, <p>"No contact with this ID was found."</p> }
.into_any(),
),
// Some(Some) => has loaded and found a contact
Some(Some(contact)) => Some(
view! { cx,

View file

@ -39,7 +39,7 @@ fn HomePage(cx: Scope) -> impl IntoView {
let posts =
create_resource(cx, || (), |_| async { list_post_metadata().await });
let posts_view = move || {
posts.with(|posts| posts
posts.with(cx, |posts| posts
.clone()
.map(|posts| {
posts.iter()
@ -82,7 +82,7 @@ fn Post(cx: Scope) -> impl IntoView {
});
let post_view = move || {
post.with(|post| {
post.with(cx, |post| {
post.clone().map(|post| {
view! { cx,
// render content

View file

@ -140,7 +140,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
{move || {
let existing_todos = {
move || {
todos.read()
todos.read(cx)
.map(move |todos| match todos {
Err(e) => {
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]

View file

@ -159,7 +159,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
{move || {
let existing_todos = {
move || {
todos.read()
todos.read(cx)
.map(move |todos| match todos {
Err(e) => {
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]

View file

@ -28,7 +28,7 @@ use std::rc::Rc;
/// <div>
/// <Suspense fallback=move || view! { cx, <p>"Loading (Suspense Fallback)..."</p> }>
/// {move || {
/// cats.read().map(|data| match data {
/// cats.read(cx).map(|data| match data {
/// None => view! { cx, <pre>"Error"</pre> }.into_any(),
/// Some(cats) => view! { cx,
/// <div>{

View file

@ -35,7 +35,7 @@ use std::{cell::RefCell, rc::Rc};
/// set_pending=set_pending.into()
/// >
/// {move || {
/// cats.read().map(|data| match data {
/// cats.read(cx).map(|data| match data {
/// None => view! { cx, <pre>"Error"</pre> }.into_any(),
/// Some(cats) => view! { cx,
/// <div>{

View file

@ -54,11 +54,11 @@ use std::{
/// // when we read the signal, it contains either
/// // 1) None (if the Future isn't ready yet) or
/// // 2) Some(T) (if the future's already resolved)
/// assert_eq!(cats(), Some(vec!["1".to_string()]));
/// assert_eq!(cats.read(cx), Some(vec!["1".to_string()]));
///
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
/// set_how_many_cats(2);
/// assert_eq!(cats(), Some(vec!["2".to_string()]));
/// assert_eq!(cats.read(cx), Some(vec!["2".to_string()]));
/// # }
/// # }).dispose();
/// ```
@ -121,7 +121,6 @@ where
let source = create_memo(cx, move |_| source());
let r = Rc::new(ResourceState {
scope: cx,
value,
set_value,
loading,
@ -245,7 +244,6 @@ where
let source = create_memo(cx, move |_| source());
let r = Rc::new(ResourceState {
scope: cx,
value,
set_value,
loading,
@ -371,14 +369,14 @@ where
/// resource.
///
/// If you want to get the value without cloning it, use [Resource::with].
/// (`value.read()` is equivalent to `value.with(T::clone)`.)
pub fn read(&self) -> Option<T>
/// (`value.read(cx)` is equivalent to `value.with(cx, T::clone)`.)
pub fn read(&self, cx: Scope) -> Option<T>
where
T: Clone,
{
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
resource.read()
resource.read(cx)
})
})
.ok()
@ -392,10 +390,10 @@ where
///
/// If you want to get the value by cloning it, you can use
/// [Resource::read].
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
resource.with(f)
resource.with(cx, f)
})
})
.ok()
@ -427,13 +425,16 @@ where
/// Returns a [std::future::Future] that will resolve when the resource has loaded,
/// yield its [ResourceId] and a JSON string.
#[cfg(any(feature = "ssr", doc))]
pub async fn to_serialization_resolver(&self) -> (ResourceId, String)
pub async fn to_serialization_resolver(
&self,
cx: Scope,
) -> (ResourceId, String)
where
T: Serializable,
{
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
resource.to_serialization_resolver(self.id)
resource.to_serialization_resolver(cx, self.id)
})
})
.expect(
@ -479,11 +480,11 @@ where
/// // when we read the signal, it contains either
/// // 1) None (if the Future isn't ready yet) or
/// // 2) Some(T) (if the future's already resolved)
/// assert_eq!(cats(), Some(vec!["1".to_string()]));
/// assert_eq!(cats.read(cx), Some(vec!["1".to_string()]));
///
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
/// set_how_many_cats(2);
/// assert_eq!(cats(), Some(vec!["2".to_string()]));
/// assert_eq!(cats.read(cx), Some(vec!["2".to_string()]));
/// # }
/// # }).dispose();
/// ```
@ -531,48 +532,12 @@ where
{
}
#[cfg(not(feature = "stable"))]
impl<S, T> FnOnce<()> for Resource<S, T>
where
S: Clone + 'static,
T: Clone + 'static,
{
type Output = Option<T>;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.read()
}
}
#[cfg(not(feature = "stable"))]
impl<S, T> FnMut<()> for Resource<S, T>
where
S: Clone + 'static,
T: Clone + 'static,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.read()
}
}
#[cfg(not(feature = "stable"))]
impl<S, T> Fn<()> for Resource<S, T>
where
S: Clone + 'static,
T: Clone + 'static,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.read()
}
}
#[derive(Clone)]
pub(crate) struct ResourceState<S, T>
where
S: 'static,
T: 'static,
{
scope: Scope,
value: ReadSignal<Option<T>>,
set_value: WriteSignal<Option<T>>,
pub loading: ReadSignal<bool>,
@ -590,15 +555,15 @@ where
S: Clone + 'static,
T: 'static,
{
pub fn read(&self) -> Option<T>
pub fn read(&self, cx: Scope) -> Option<T>
where
T: Clone,
{
self.with(T::clone)
self.with(cx, T::clone)
}
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
let suspense_cx = use_context::<SuspenseContext>(self.scope);
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
let suspense_cx = use_context::<SuspenseContext>(cx);
let v = self
.value
@ -611,21 +576,23 @@ where
let increment = move |_: Option<()>| {
if let Some(s) = &suspense_cx {
let mut contexts = suspense_contexts.borrow_mut();
if !contexts.contains(s) {
contexts.insert(*s);
if let Ok(ref mut contexts) = suspense_contexts.try_borrow_mut()
{
if !contexts.contains(s) {
contexts.insert(*s);
// on subsequent reads, increment will be triggered in load()
// because the context has been tracked here
// on the first read, resource is already loading without having incremented
if !has_value {
s.increment();
// on subsequent reads, increment will be triggered in load()
// because the context has been tracked here
// on the first read, resource is already loading without having incremented
if !has_value {
s.increment();
}
}
}
}
};
create_isomorphic_effect(self.scope, increment);
create_isomorphic_effect(cx, increment);
v
}
@ -685,6 +652,7 @@ where
pub fn resource_to_serialization_resolver(
&self,
cx: Scope,
id: ResourceId,
) -> std::pin::Pin<Box<dyn futures::Future<Output = (ResourceId, String)>>>
where
@ -694,7 +662,7 @@ where
let (tx, mut rx) = futures::channel::mpsc::channel(1);
let value = self.value;
create_isomorphic_effect(self.scope, move |_| {
create_isomorphic_effect(cx, move |_| {
value.with({
let mut tx = tx.clone();
move |value| {
@ -731,6 +699,7 @@ pub(crate) trait SerializableResource {
fn to_serialization_resolver(
&self,
cx: Scope,
id: ResourceId,
) -> Pin<Box<dyn Future<Output = (ResourceId, String)>>>;
}
@ -746,9 +715,10 @@ where
fn to_serialization_resolver(
&self,
cx: Scope,
id: ResourceId,
) -> Pin<Box<dyn Future<Output = (ResourceId, String)>>> {
let fut = self.resource_to_serialization_resolver(id);
let fut = self.resource_to_serialization_resolver(cx, id);
Box::pin(fut)
}
}

View file

@ -421,11 +421,12 @@ impl Runtime {
pub(crate) fn serialization_resolvers(
&self,
cx: Scope,
) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
let f = FuturesUnordered::new();
for (id, resource) in self.resources.borrow().iter() {
if let AnyResource::Serializable(resource) = resource {
f.push(resource.to_serialization_resolver(id));
f.push(resource.to_serialization_resolver(cx, id));
}
}
f

View file

@ -1,5 +1,6 @@
#![forbid(unsafe_code)]
use crate::{
console_warn,
runtime::{with_runtime, RuntimeId},
suspense::StreamChunk,
EffectId, PinnedFuture, ResourceId, SignalId, SuspenseContext,
@ -266,10 +267,13 @@ impl Scope {
) {
_ = with_runtime(self.runtime, |runtime| {
let scopes = runtime.scopes.borrow();
let scope = scopes.get(self.id).expect(
"tried to add property to a scope that has been disposed",
);
f(&mut scope.borrow_mut());
if let Some(scope) = scopes.get(self.id) {
f(&mut scope.borrow_mut());
} else {
console_warn(
"tried to add property to a scope that has been disposed",
)
}
})
}
@ -353,8 +357,10 @@ impl Scope {
pub fn serialization_resolvers(
&self,
) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers())
.unwrap_or_default()
with_runtime(self.runtime, |runtime| {
runtime.serialization_resolvers(*self)
})
.unwrap_or_default()
}
/// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope,
@ -409,7 +415,9 @@ impl Scope {
{
with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
std::mem::take(&mut shared_context.pending_fragments)
let f = std::mem::take(&mut shared_context.pending_fragments);
println!("pending_fragments = {}", f.len());
f
})
.unwrap_or_default()
}

View file

@ -104,7 +104,7 @@
//! <div>
//! // show the contacts
//! <ul>
//! {move || contacts.read().map(|contacts| view! { cx, <li>"todo contact info"</li> } )}
//! {move || contacts.read(cx).map(|contacts| view! { cx, <li>"todo contact info"</li> } )}
//! </ul>
//!
//! // insert the nested child route here