# frender
[![Crates.io](https://img.shields.io/crates/v/frender?style=for-the-badge)](https://crates.io/crates/frender)
[![docs.rs](https://img.shields.io/docsrs/frender/latest?style=for-the-badge)](https://docs.rs/frender)
[![GitHub license](https://img.shields.io/github/license/frender-rs/frender?style=for-the-badge)](https://github.com/frender-rs/frender/blob/main/LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/frender-rs/frender?style=for-the-badge)](https://github.com/frender-rs/frender/stargazers)
<div style="text-align:center;margin:16px">
![frender logo](./logo.svg)
Functional Rendering: `React` in `Rust`
</div>
_**f**render_ is still in alpha and it's api might change.
For now it is recommended to specify the exact version in `Cargo.toml`.
Before updating, please see the full [changelog](https://github.com/frender-rs/frender/blob/alpha/CHANGELOG.md) in case there are breaking changes.
There are some example apps in
[`examples`](https://github.com/frender-rs/frender/tree/alpha/examples)
folder. You can preview them at [this site](https://frender-rs.github.io/frender/).
## Quick Start
1. Create a new cargo project
```sh
cargo new my-frender-app
cd my-frender-app
```
2. Add `frender` to dependencies in `Cargo.toml`.
```toml
[dependencies]
frender = "= 1.0.0-alpha.8"
```
3. Create `index.html` in the project root directory.
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>My frender App</title>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<link data-trunk rel="rust" href="Cargo.toml" />
</head>
<body>
<div id="frender-root"></div>
</body>
</html>
```
4. Modify `src/main.rs`
```rust
use frender::prelude::*;
#[component(main(mount_element_id = "frender-root"))]
fn Main() {
rsx!(
<div>
"Hello, frender!"
</div>
)
}
```
5. Run with `trunk`
Install [trunk](https://trunkrs.dev/#install) and then execute:
```sh
trunk serve
```
Then you can navigate to `http://localhost:8080` to see your frender app.
## `rsx` syntax
### `rsx` element
```rust
use frender::prelude::*;
rsx! (
<MyComp id="my-component">
// Child node can be any literal strings or numbers
"some string"
1
// Child node can be any rust expressions wrapped in braces
{ 1 + 6 }
{ value }
// Child node can be an element
<MyChild key="k" prop={any_expr} />
// Prop without value means `true`, just like React
<MyDialog show />
// Fragment
<>1 2 3</>
// Fragment with key
<# key="key">1 2 3</#>
// you can also use `</_>` to enclose any element
<path::to::Component></_>
// the above is equivalent to:
<path::to::Component></path::to::Component>
</MyComp>
)
```
Any component name starting with lower case letter `[a-z]`
will be interpreted as an **intrinsic component**.
For example, `rsx!( <div id="my-div" /> )` will be resolved to:
```rust
use frender::prelude::*;
use self::intrinsic_components::div::prelude::*;
rsx! (
<self::intrinsic_components::div::prelude::Component id="my-div" />
)
```
### `rsx` prop
In order to make rsx less verbose, frender provides
`IntoPropValue` trait. The `value` in
`<MyComponent prop={value} />` will be mapped to
`IntoPropValue::into_prop_value(value)`.
With this, assuming the prop accepts `Option<i32>`,
you can simplify `prop={Some(1)}` to `prop={1}`,
because `T` implements `IntoPropValue<Option<T>>`.
If you want to pass the value as is, you can
use `:=` to set prop. `prop:={value}`
## Write a component
### Component with no props
```rust
use frender::prelude::*;
#[component]
fn MyComponent() {
// ^
// the return type defaults to react::Element
rsx!( <div /> )
}
// Or you can specify the return type explicitly
#[component]
fn MyAnotherComponent() -> Option<react::Element> {
if check_something() {
Some(rsx!( <MyComponent /> ))
} else {
None
}
}
```
### Component with props
First, define `MyProps`
```rust
use frender::prelude::*;
def_props! {
pub struct MyProps {
// Required prop
name: String,
// Optional prop which defaults to `Default::default()`
// The following property `age` is optional, and defaults to `None`
age?: Option<u8>,
// The following property `tags` is optional, and defaults to `Vec::default()`
tags?: Vec<String>,
// If the prop type is not specified,
// then frender will infer the type by prop name.
// For example, `class_name` default has type `Option<String>`
// The following property `class_name` is optional, has type Option<String>
class_name?,
// The following property `id` is required, has type Option<String>
id,
// Prop can also have type generics.
// For example, the following is
// the default definition for prop `children`,
// which means it accepts any `Option<TNode>` where TNode implements react::Node,
// and then map the value into `Option<react::Children>` and store it into MyProps.
children<TNode: react::Node>(value: Option<TNode>) -> Option<react::Children> {
value.and_then(react::Node::into_children)
},
}
}
```
Then write the component with the above props:
```rust
use frender::prelude::*;
#[component]
pub fn MyComponent(props: &MyProps) {
rsx!(<div>{&props.children}</div>)
}
```
Due to the generics, in some very rare cases, you may meet errors like
`type annotations needed` `cannot infer type for type parameter`.
You can solve it by specifying the type
with the turbofish syntax `::<>`.
For example:
```rust
rsx! (
// ERROR: type annotations needed
<a children={None} />
)
rsx! (
// it works!
<a children={None::<()>} />
)
```
## Hooks
React hooks are also available in `frender`.
You checkout the [examples](https://github.com/frender-rs/frender/blob/alpha/examples/counter/src/my_counter.rs) for the usage.
## Future Development Plans
- [ ] Documentation
- [ ] Intrinsic svg components
- [ ] Export `frender` components to js
- [ ] Server Side Rendering
- [ ] Type checking for `CssProperties`
- [ ] Css-in-rust (For example, integrate with [`emotion/react`](https://emotion.sh/docs/@emotion/react))
## Contributing
`frender` is open sourced at [GitHub](https://github.com/frender-rs/frender).
Pull requests and issues are welcomed.
You can also [sponsor me](https://ko-fi.com/equalma) and I would be very grateful :heart:
[![Buy Me a Coffee at ko-fi.com](https://cdn.ko-fi.com/cdn/kofi2.png?v=3)](https://ko-fi.com/N4N26J11L)