1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
/*! # Consecuit An experimental functional web UI framework that uses the Rust type system for hooks and more. --- This crate is a library for building web UI with functional components and hooks. See the [README](https://github.com/wishawa/consecuit) for an overview. Also take a look at [our TodoMVC](https://wishawa.github.io/consecuit/todomvc) and [its source code](https://github.com/wishawa/consecuit/tree/main/examples/todomvc). # Using Consecuit Below, we show you how to * Write and use hooks. * Write and use components. * Write and use container components. The [the counters example](https://github.com/wishawa/consecuit/tree/main/examples/counters/src/lib.rs) is also good for getting started. ## Hooks A Consecuit hook is a function that can call other Consecuit hooks. So you can build your own hook by composing the basic hooks like [`use_state`][hooks::use_state], [`use_ref`][hooks::use_ref], and [`use_effect`][hooks::use_effect]. A Consecuit hook must be a function that takes 2 arguments: Consecuit's [`HookBuilder`][construction::hook::HookBuilder], and the args, which can be any type. The return type should not be named out concretely, but rather written as `impl Trait` of [`HookReturn`][construction::types::HookReturn], with the actual value as the generic parameter. The signature for a `use_i32_state` hook might look something like this ``` use consecuit::prelude::*; fn use_i32_state(cc: HookBuilder, initial_value: i32) -> impl HookReturn<(i32, Updater<i32>)> ``` In the function body, you can write code just like a normal function. To use other hooks, call `cc.hook(<hook function>, <hook args>)`. This will consume `cc` and return a new one, along with the return value of the hook, in a tuple like this `(cc. <hook return value>)`. You then can do whatever you want with the `<hook return value>`, and you can use the use the returned `cc` to call more hooks. ``` let (cc, some_hook_result) = cc.hook(some_hook_function, some_hook_args); println!("{:?}", some_hook_result + 1); let (cc, another_hook_result) = cc.hook(another_hook_function, another_hook_args); ``` When you are done and want to return from your hook, return `(cc, <your hook return value>)`. ``` (cc, some_value) ``` Our `use_i32_state` might look like this in full: ``` use consecuit::prelude::*; fn use_i32_state(cc: HookBuilder, initial_value: i32) -> impl HookReturn<(i32, Updater<i32>)> { // Use the `use_state` hook. let (cc, (number, updater)) = cc.hook(use_state, initial_value); // Print the value. prinln!("The state is {}", number.to_string()); // Return (cc, (number, updater)) } ``` Take a look at [the counters example](https://github.com/wishawa/consecuit/tree/main/examples/counters/src/lib.rs), specifically the function `counter_hook_extracted`, to see how we extract logic into a new hook. ## Components A Consecuit component is a function that can call Consecuit hooks, and render other Consecuit components. You can use the HTML components provided by the consecuit_html crate as building blocks for your components' UI. A Consecuit component must be a function that takes 2 arguments: Consecuit's [`ComponentBuilder`][construction::component::ComponentBuilder], and the props, which can be any type that is [`PartialEq`] and [`Clone`], and `'static` (i.e. not a borrow). (a component can only have 1 props). The return type should not be named out concretely, but rather written as `impl Trait` of [`ComponentReturn`][construction::types::ComponentReturn]. Here is an example: ``` use consecuit::prelude::*; fn my_component(cc: ComponentBuilder, props: MyComponentProps) -> impl ComponentReturn ``` In the function body, you can write normal code, and call hooks the same way as for hook functions documented above. You can also use the [`cc_tree`][consecuit_macros::cc_tree] macro to render other components. Like this: ``` use consecuit_html::prelude::*; // <- consecuit_html provides html components like div and span cc_tree!( <div> <div {html_props().class_name("some-class-name").onclick(onclick_callback)}> <span>"I'm a string literal"</span> <span {html_props().class_name("span-text")}>{format!("anything that is AsRef<str> works")}</span> {props.some_text} <my_component { MyComponentProps {text: "hello"} } /> </div> <div {html_props().class_name("nothing-here")} /> </div> ) ``` As you might have figured out from the code above already: * Use the name of the component function is the name of the tag. * Put the props in curly braces beside the tag name. If there are none, the macro will attempt to use the [`Default::default`] value (and error if the props doesn't implement Default). * Use string literal or any [`AsRef<str>`] type in braces to create text node. Here is an example of a component function: ``` use consecuit::prelude::*; use consecuit_html::prelude::*; fn show_plus_calculation(cc: ComponentBuilder, (lhs, rhs): (i32, i32)) -> impl ComponentReturn { let result = lhs + rhs; // These 2 lines are just to show how to use hooks let (cc, _some_example_1) = cc.hook(use_example_hook, 1234); let (cc, _some_example_2) = cc.hook(use_another_hook, 5678); // All there `h5`, `span`, `b` are from the `consecuit_html` crate. cc_tree!( <my_website_header /> <h5>"Calculation Result:"</h5> <span>{lhs.to_string()}</span> <span {html_props().class_name("plus-sign")}>" + "</span> <span>{rhs.to_string()}</span> <span {html_props().class_name("equal-sign")}>" = "</span> <span><b>{result.to_string()}</b></span> <my_website_footer /> ) } ``` ## Container Components Container components are components with 'hole', allowing you to give it children. Most components in the `consecuit_html` crate are containers. Container components return `impl ContainerReturn` rather than `impl ComponentReturn` For example, you can give div and span childrens: ``` cc_tree!( <div> "This is a child I put in the div" "This is another child I put in the div" <span>"This is a child I put in the span in the div"</span> </div> ) ``` To create your own container component, add the `HOLE` attribute to the component you wish to hole. This will "forward" the hole of that component. For example: ``` fn my_container(cc: ComponentBuilder, _: ()) -> impl ContainerReturn { cc_tree!( <div> "The hole of the div below is forwarded to become the hole of my_container!" <div HOLE /> </div> ) } ``` A container component must have exactly one hole. */ pub mod construction; pub mod executor; pub mod hooks; pub mod locking; mod stores; pub use consecuit_macros; pub use construction::mount; pub mod prelude { /*! Just import me. */ pub use crate::construction::{ component::ComponentBuilder, hook::HookBuilder, subtrees::*, types::{ComponentReturn, ContainerReturn, DynComponentReturn, HookReturn}, }; pub use crate::executor::{batch_updates, run_later}; pub use crate::hooks::*; pub use consecuit_macros::cc_tree; }