Components
Component
s are important part of natrix, and are the core of the reactivity system.
note
If you are looking for a way to create a component without any state there is a more light weight alternative in the Stateless Components section.
Basic Components
Components are implemented by using the Component
derive macro and manually implementing the Component
trait. This is because the derive macro actually implements the ComponentBase
trait.
Components have 3 required items, render
, EmitMessage
and ReceiveMessage
.
extern crate natrix;
use natrix::prelude::*;
#[derive(Component)]
struct HelloWorld;
impl Component for HelloWorld {
type EmitMessage = NoMessages;
type ReceiveMessage = NoMessages;
fn render() -> impl Element<Self> {
e::div().text("Hello World")
}
}
fn main() {
mount(HelloWorld);
}
important
With the nightly
feature you can omit the EmitMessage
and ReceiveMessage
types, as they default to NoMessages
. In all other examples we will omit them for simplicity as nightly is the recommended toolchain.
The render
function should return a type that implements the Element
trait. This is usually done by using the Html Elements or rust types that implement the Element
trait. Elements are generic over the component type, hence impl Element<Self>
, this provides strong type guarantees for reactivity and event handlers without needing to capture signals like in other frameworks.
In contrast to frameworks like React, the render
function is not called every time the component needs to be updated. Instead, it is only called when the component is mounted. This is because natrix uses a reactivity system that allows fine-grained reactivity.
State
Now components with no state are not very useful (well they are, but you should use Stateless Components instead), so lets add some state to our component. This is done simply by adding fields to the struct.
extern crate natrix;
use natrix::prelude::*;
#[derive(Component)]
struct HelloWorld {
counter: u8,
}
impl Component for HelloWorld {
fn render() -> impl Element<Self> {
e::button()
}
}
fn main() {
mount(HelloWorld { counter: 0 });
}
As you can see when mounting a component with state you simply construct the instance without needing any wrappers.
Displaying State
Natrix uses callbacks similar to other frameworks, but instead of capturing signals callbacks instead take a reference to the component. This is mainly done via the R
type alias, R<Self>
is a alias for &mut RenderCtx<Self>
extern crate natrix;
use natrix::prelude::*;
#[derive(Component)]
struct HelloWorld {
counter: u8,
}
impl Component for HelloWorld {
fn render() -> impl Element<Self> {
e::button()
.text(|ctx: R<Self>| *ctx.counter)
}
}
fn main() {
mount(HelloWorld { counter: 0 });
}
We need to specify the argument type of the closure, this is because of limitation in the type inference system. The closures also can return anything that implements the Element
trait, so you can use any of the Html Elements
or any other type that implements the Element
trait.
tip
See the reactivity section for more information on how fine grained reactivity works and best practices.
Updating State
Updating state is done very similarly, but using E
, the .on
method takes a callback that is called when the event is triggered. The callback takes a reference to the component and the event as arguments. The event is passed as a generic type, so you can use any event that implements the Event
trait. the second argument will automatically be inferred to the type of the event. for example the Click
event will be passed as a MouseEvent
type.
extern crate natrix;
use natrix::prelude::*;
#[derive(Component)]
struct HelloWorld {
counter: u8,
}
impl Component for HelloWorld {
fn render() -> impl Element<Self> {
e::button()
.text(|ctx: R<Self>| *ctx.counter)
.on::<events::Click>(|ctx: E<Self>, _| {
*ctx.counter += 1;
})
}
}
fn main() {
mount(HelloWorld { counter: 0 });
}
Defining methods
Construction
Construction methods can simple be defined as normal
extern crate natrix;
use natrix::prelude::*;
#[derive(Component)]
pub struct MyComponent {
private_field: u8,
}
impl MyComponent {
pub fn new(initial_value: u8) -> Self {
Self { private_field: initial_value }
}
}
impl Component for MyComponent {
fn render() -> impl Element<Self> {
e::div()
}
}
fn main() {
mount(MyComponent::new(0));
}
Methods for ctx
The above wont let you define methods that work on ctx
, this is because ctx
is actually a different type constructed by the derive macro.
failure
This feature isnt implemented yet