Components

Components 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

note

The .text method is a alias for .child, so the following section applies to both.

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