Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow function_component creation based on function name #2292

Merged
merged 5 commits into from Dec 23, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/function_todomvc/src/components/filter.rs
Expand Up @@ -8,8 +8,8 @@ pub struct FilterProps {
pub onset_filter: Callback<FilterEnum>,
}

#[function_component(Filter)]
pub fn filter(props: &FilterProps) -> Html {
#[function_component]
pub fn Filter(props: &FilterProps) -> Html {
let filter = props.filter;

let cls = if props.selected {
Expand Down
19 changes: 14 additions & 5 deletions packages/yew-macro/src/function_component.rs
@@ -1,5 +1,5 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
Expand Down Expand Up @@ -140,18 +140,22 @@ impl Parse for FunctionComponent {
}

pub struct FunctionComponentName {
component_name: Ident,
component_name: Option<Ident>,
}

impl Parse for FunctionComponentName {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.is_empty() {
return Err(input.error("expected identifier for the component"));
return Ok(Self {
component_name: None,
});
}

let component_name = input.parse()?;

Ok(Self { component_name })
Ok(Self {
component_name: Some(component_name),
})
}
}

Expand All @@ -171,7 +175,12 @@ pub fn function_component_impl(
name: function_name,
return_type,
} = component;

let component_name = component_name.unwrap_or_else(|| function_name.clone());
let function_name = format_ident!(
"{}FunctionProvider",
function_name,
span = function_name.span()
);
Comment on lines +178 to +183
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to convert the function name to PascalCase with convert_case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really matter since it's not in the public API. Besides, generally this name would be PascalCase anyway because that's the convention for component names.

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

if function_name == component_name {
Expand Down
@@ -1,23 +1,25 @@
error: expected identifier
--> $DIR/bad-name-fail.rs:8:22
--> tests/function_component_attr/bad-name-fail.rs:8:22
|
8 | #[function_component(let)]
| ^^^

error: unexpected token
--> $DIR/bad-name-fail.rs:17:23
--> tests/function_component_attr/bad-name-fail.rs:17:23
|
17 | #[function_component(x, y, z)]
| ^

error: expected identifier
--> $DIR/bad-name-fail.rs:26:22
--> tests/function_component_attr/bad-name-fail.rs:26:22
|
26 | #[function_component(124)]
| ^^^

error: the component must not have the same name as the function
--> $DIR/bad-name-fail.rs:35:22
warning: type `component` should have an upper camel case name
--> tests/function_component_attr/bad-name-fail.rs:35:22
|
35 | #[function_component(component)]
| ^^^^^^^^^
| ^^^^^^^^^ help: convert the identifier to upper camel case (notice the capitalization): `Component`
|
= note: `#[warn(non_camel_case_types)]` on by default
Expand Up @@ -22,21 +22,21 @@ error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisf
27 | html! { <Comp<MissingTypeBounds> /> };
| ^^^^ the trait `yew::Properties` is not implemented for `MissingTypeBounds`
|
= note: required because of the requirements on the impl of `FunctionProvider` for `comp<MissingTypeBounds>`
= note: required because of the requirements on the impl of `FunctionProvider` for `compFunctionProvider<MissingTypeBounds>`

error[E0599]: the function or associated item `new` exists for struct `VChild<FunctionComponent<comp<MissingTypeBounds>>>`, but its trait bounds were not satisfied
error[E0599]: the function or associated item `new` exists for struct `VChild<FunctionComponent<compFunctionProvider<MissingTypeBounds>>>`, but its trait bounds were not satisfied
--> tests/function_component_attr/generic-props-fail.rs:27:14
|
27 | html! { <Comp<MissingTypeBounds> /> };
| ^^^^ function or associated item cannot be called on `VChild<FunctionComponent<comp<MissingTypeBounds>>>` due to unsatisfied trait bounds
| ^^^^ function or associated item cannot be called on `VChild<FunctionComponent<compFunctionProvider<MissingTypeBounds>>>` due to unsatisfied trait bounds
|
::: $WORKSPACE/packages/yew/src/functional/mod.rs
|
| pub struct FunctionComponent<T: FunctionProvider + 'static> {
| ----------------------------------------------------------- doesn't satisfy `_: yew::Component`
|
= note: the following trait bounds were not satisfied:
`FunctionComponent<comp<MissingTypeBounds>>: yew::Component`
`FunctionComponent<compFunctionProvider<MissingTypeBounds>>: yew::Component`

error[E0107]: missing generics for type alias `Comp`
--> tests/function_component_attr/generic-props-fail.rs:30:14
Expand Down
Expand Up @@ -5,13 +5,17 @@ struct Props {
a: usize,
}

#[function_component()]
fn comp(props: &Props) -> Html {
#[function_component]
fn Comp(props: &Props) -> Html {
html! {
<p>
{ props.a }
</p>
}
}

fn main() {}
fn main() {
let _ = html! {
<Comp a={0} />
};
}

This file was deleted.

33 changes: 26 additions & 7 deletions website/docs/concepts/function-components/attribute.mdx
Expand Up @@ -11,7 +11,26 @@ Functions with the attribute have to return `Html` and may take a single paramet
The parameter type needs to be a reference to a `Properties` type (ex. `props: &MyProps`).
If the function doesn't have any parameters the resulting component doesn't accept any props.

The attribute doesn't replace your original function with a component. You need to provide a name as an input to the attribute which will be the identifier of the component.
Just mark the component with the attribute. The component will be named after the function.

```rust
use yew::{function_component, html, Html};

#[function_component]
pub fn ChatContainer() -> Html {
html! {
// chat container impl
}
}

html! {
<ChatContainer />
};
```

## Specifying a custom component name

You need to provide a name as an input to the attribute which will be the identifier of the component.
Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this:

```rust
Expand Down Expand Up @@ -42,8 +61,8 @@ pub struct RenderedAtProps {
pub time: String,
}

#[function_component(RenderedAt)]
pub fn rendered_at(props: &RenderedAtProps) -> Html {
#[function_component]
pub fn RenderedAt(props: &RenderedAtProps) -> Html {
html! {
<p>
<b>{ "Rendered at: " }</b>
Expand All @@ -59,8 +78,8 @@ pub fn rendered_at(props: &RenderedAtProps) -> Html {
```rust
use yew::{function_component, html, use_state, Callback};

#[function_component(App)]
fn app() -> Html {
#[function_component]
fn App() -> Html {
let counter = use_state(|| 0);

let onclick = {
Expand Down Expand Up @@ -99,8 +118,8 @@ where
data: T,
}

#[function_component(MyGenericComponent)]
pub fn my_generic_component<T>(props: &Props<T>) -> Html
#[function_component]
pub fn MyGenericComponent<T>(props: &Props<T>) -> Html
where
T: PartialEq + Display,
{
Expand Down
4 changes: 2 additions & 2 deletions website/docs/concepts/function-components/introduction.mdx
Expand Up @@ -18,8 +18,8 @@ The easiest way to create a function component is to add the [`#[function_compon
```rust
use yew::{function_component, html};

#[function_component(HelloWorld)]
fn hello_world() -> Html {
#[function_component]
fn HelloWorld() -> Html {
html! { "Hello world" }
}
```
Expand Down