import React from 'react'; import { ContentPage } from '../components/ContentPage'; import { Link } from 'react-router-dom'; import { ApiSection } from '../types'; // Import ApiSection export const CoreConceptsPage: React.FC = () => { const intro = (

Chemical is built upon a few core concepts that work together to create reactive and manageable applications. Understanding these concepts is key to effectively using the library.

); const sections: ApiSection[] = [ { title: "Reactivity", entries: [ { signature: "Stateful Objects (Values, Tables, Maps)", description: ( <> At the heart of Chemical are stateful objects. These are containers for your application's data. The primary stateful object is a Value. For collections, Chemical provides Table (for arrays) and Map (for dictionaries). When the data within these objects changes, Chemical's reactive system is notified. ), example: ` local Chemical = require(game.ReplicatedStorage.Chemical) local Value = Chemical.Value local Table = Chemical.Table local Map = Chemical.Map local name = Value("Guest") local scores = Table({10, 20, 30}) local settings = Map({ theme = "dark", volume = 0.5 }) name:set("PlayerOne") -- Triggers reactivity scores:insert(40) -- Triggers reactivity settings:key("volume", 0.7) -- Triggers reactivity `, }, { signature: "Computed Values", description: ( <> Computed values are derived from one or more stateful objects. They automatically update whenever their underlying dependencies change. This allows you to create complex data relationships without manual synchronization. ), example: ` local Chemical = require(game.ReplicatedStorage.Chemical) local Value = Chemical.Value local Computed = Chemical.Computed local firstName = Value("John") local lastName = Value("Doe") local fullName = Computed(function() return firstName:get() .. " " .. lastName:get() end) print(fullName:get()) -- Output: John Doe firstName:set("Jane") -- fullName:get() will be "Jane Doe" after scheduler runs print("Full name after set:", fullName:get()) -- Might print old value here due to async update `, }, { signature: "Effects", description: ( <> Effects are used to perform side effects in response to state changes. This could include updating the UI, making network requests, or logging. Effects automatically re-run when their dependencies change. They can also return a cleanup function that runs before the next execution or when the effect is destroyed. ), example: ` local Chemical = require(game.ReplicatedStorage.Chemical) local Value = Chemical.Value local Effect = Chemical.Effect local count = Value(0) Effect(function() print("Current count is:", count:get()) -- Optional cleanup function return function() print("Cleaning up effect for count (previous value was):", count:get()) end end) count:set(1) -- Effect runs count:set(2) -- Cleanup for count 1 runs, then effect for count 2 runs `, }, { signature: "Observers and Watch", description: ( <> Observers provide a way to listen to changes in a specific stateful object and run callbacks. Watch is a higher-level utility built on Observers for simpler change tracking. ), example: ` local Chemical = require(game.ReplicatedStorage.Chemical) local Value = Chemical.Value local Observer = Chemical.Observer local Watch = Chemical.Watch local name = Value("Alex") -- Using Observer local obs = Observer(name) obs:onChange(function(newValue, oldValue) print("Observer: Name changed from", oldValue, "to", newValue) end) -- Using Watch local watchHandle = Watch(name, function(newValue, oldValue) print("Watch: Name changed from", oldValue, "to", newValue) end) name:set("Jordan") -- Both Observer and Watch callbacks will fire. obs:destroy() watchHandle:destroy() `, }, ], }, { title: "UI Composition with `Compose`", entries: [ { signature: "Declarative UI", description: ( <> Chemical's Compose API allows you to define UI structures declaratively. Properties of UI elements can be bound to stateful objects (Values or Computeds), and the UI will automatically update when the state changes. ), example: ` local Chemical = require(game.ReplicatedStorage.Chemical) local Value = Chemical.Value local Compose = Chemical.Compose local OnEvent = Chemical.OnEvent local Children = Chemical.Symbols.Children -- Assuming playerGui is defined: local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui") local isVisible = Value(true) local buttonText = Value("Click Me") local MyScreen = Compose("ScreenGui")({ Parent = playerGui, [Children] = { Compose("TextButton")({ Text = buttonText, -- Bound to the buttonText Value Visible = isVisible, -- Bound to the isVisible Value Size = UDim2.new(0, 100, 0, 50), -- Parent is implicitly MyScreen due to [Children] structure [OnEvent("Activated")] = function() print("Button clicked!") isVisible:set(false) -- Change state, UI updates end }) } }) buttonText:set("Submit") -- Button's text updates automatically `, }, ], }, { title: "Networking with Reactors", entries: [ { signature: "Reactions and Reactors", description: ( <> For networked applications, Chemical provides Reactors and Reactions. A Reactor is a server-side factory for creating Reactions. Reactions are stateful objects whose state can be replicated from the server to clients. Changes made to a Reaction on the server are automatically propagated to subscribed clients. Reaction properties are accessed directly. ), example: ` local Chemical = require(game.ReplicatedStorage.Chemical) local Value = Chemical.Value local Reactor = Chemical.Reactor local Effect = Chemical.Effect -- Server-side: Define a Reactor for player scores local ScoreReactor = Reactor({ Name = "PlayerScore" }, function(playerIdString) -- 'playerIdString' (the key) is available but typically not stored in the returned table. return { currentScore = Value(0) } end) -- Create a Reaction for a specific player local player1ScoreReaction = ScoreReactor:create("player123") -- Access Reaction properties directly: player1ScoreReaction.currentScore:set(100) -- This change will be sent to clients -- Client-side: Await and use the Reaction local player1ScoreClientReaction = ScoreReactor:await("player123") -- Yields if player1ScoreClientReaction then Effect(function() -- Access Reaction properties directly: print("Player 123 score on client:", player1ScoreClientReaction.currentScore:get()) end) end `, }, ], }, { title: "Underlying ECS", entries: [ { signature: "Entity-Component-System", description: ( <> Chemical internally uses an Entity-Component-System (ECS) architecture (specifically `JECS`) to manage its reactive objects and their relationships. While direct interaction with the ECS is usually not required for typical use cases, understanding this can be helpful for advanced scenarios or debugging. Each reactive object created by Chemical (Value, Computed, Effect, etc.) is an "entity" with associated "components" (like its current value, previous value, or callback functions) and "tags" (like `IsStateful`, `IsDirty`). ), example: ` local Chemical = require(game.ReplicatedStorage.Chemical) local Value = Chemical.Value -- This is a conceptual example, direct ECS manipulation is advanced. local myValue = Value(10) -- myValue.entity is the ECS entity ID. -- Chemical.World:get(myValue.entity, Chemical.ECS.Components.Value) would return 10. -- When myValue:set(20) is called, Chemical.World:set(...) updates the component -- and adds the Chemical.ECS.Tags.IsDirty tag. -- The Scheduler then processes entities with IsDirty. `, }, ], } ]; return ; };