{"id":17440901,"url":"https://github.com/pmndrs/koota","last_synced_at":"2025-05-16T10:07:36.234Z","repository":{"id":257854341,"uuid":"825488497","full_name":"pmndrs/koota","owner":"pmndrs","description":"🌎 Performant real-time state management for React and TypeScript","archived":false,"fork":false,"pushed_at":"2025-05-05T23:59:00.000Z","size":3856,"stargazers_count":356,"open_issues_count":29,"forks_count":15,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-05-11T03:02:14.675Z","etag":null,"topics":["data-oriented-design","data-oriented-programming","ecs","entity-component-system","game-development","javascript","react","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pmndrs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-07-07T23:15:11.000Z","updated_at":"2025-05-11T01:04:46.000Z","dependencies_parsed_at":"2024-10-24T23:40:34.574Z","dependency_job_id":"4fef4ece-bef0-40a8-919b-82eca51cb865","html_url":"https://github.com/pmndrs/koota","commit_stats":{"total_commits":163,"total_committers":3,"mean_commits":"54.333333333333336","dds":"0.012269938650306789","last_synced_commit":"51db30201d844f9dbd63bbfb57da57964ded2ca7"},"previous_names":["pmndrs/koota"],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Fkoota","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Fkoota/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Fkoota/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Fkoota/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pmndrs","download_url":"https://codeload.github.com/pmndrs/koota/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509476,"owners_count":22082891,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-oriented-design","data-oriented-programming","ecs","entity-component-system","game-development","javascript","react","typescript"],"created_at":"2024-10-17T15:05:05.761Z","updated_at":"2025-05-16T10:07:31.225Z","avatar_url":"https://github.com/pmndrs.png","language":"TypeScript","readme":"[![Discord Shield](https://img.shields.io/discord/740090768164651008?style=flat\u0026colorA=000000\u0026colorB=000000\u0026label=\u0026logo=discord\u0026logoColor=ffffff)](https://discord.gg/poimandres)\n\n\u003cimg src=\"logo.svg\" alt=\"Koota\" width=\"100%\" /\u003e\n\nKoota is an ECS-based state management library optimized for real-time apps, games, and XR experiences. Use as much or as little as you need.\n\n```bash\nnpm i koota\n```\n👉 [Try the starter template](https://github.com/Ctrlmonster/r3f-koota-starter)\n\n### First, define traits\n\nTraits are the building blocks of your state. They represent slices of data with specific meanings.\n\n```js\nimport { trait } from 'koota';\n\n// Basic trait with default values\nconst Position = trait({ x: 0, y: 0 });\nconst Velocity = trait({ x: 0, y: 0 });\n\n// Trait with a callback for initial value\n// ⚠️ Must be an object\nconst Mesh = trait(() =\u003e new THREE.Mesh());\n\n// Tag trait (no data)\nconst IsActive = trait();\n```\n\n### Spawn entities\n\nEntities are spawned in a world. By adding traits to an entity they gain content.\n\n```js\nimport { createWorld } from 'koota';\n\nconst world = createWorld();\n\nconst player = world.spawn(Position, Velocity);\n// Initial values can be passed in to the trait by using it as a function\nconst goblin = world.spawn(Position({ x: 10, y: 10 }), Velocity, Mesh);\n```\n\n### Query and update data\n\nQueries fetch entities sharing traits (archetypes). Use them to batch update entities efficiently.\n\n```js\n// Run this in a loop\nworld.query(Position, Velocity).updateEach(([position, velocity]) =\u003e {\n    position.x += velocity.x * delta;\n    position.y += velocity.y * delta;\n});\n```\n\n### Use in your React components\n\nTraits can be used reactively inside of React components.\n\n```js\nimport { WorldProvider, useQuery, useTrait } from 'koota/react'\n\n// Wrap your app in WorldProvider\ncreateRoot(document.getElementById('root')!).render(\n    \u003cWorldProvider world={world}\u003e\n        \u003cApp /\u003e\n    \u003c/WorldProvider\u003e\n);\n\nfunction RocketRenderer() {\n    // Reactively update whenever the query updates with new entities\n    const rockets = useQuery(Position, Velocity)\n    return (\n        \u003c\u003e\n            {rockets.map((entity) =\u003e \u003cRocketView key={entity} entity={entity} /\u003e)}\n        \u003c/\u003e\n    )\n}\n\nfunction RocketView({ entity }) {\n    // Observes this entity's position trait and reactively updates when it changes\n    const position = useTrait(entity, Position)\n    return (\n        \u003cdiv style={{ position: 'absolute', left: position.x ?? 0, top: position.y ?? 0 }}\u003e\n        🚀\n        \u003c/div\u003e\n    )\n}\n```\n\n### Modify Koota state safely with actions\n\nUse actions to safely modify Koota from inside of React in either effects or events.\n\n```js\nimport { createActions } from 'koota'\nimport { useActions } from 'koota/react';\n\nconst actions = createActions((world) =\u003e ({\n    spawnShip: (position) =\u003e world.spawn(Position(position), Velocity),\n    destroyAllShips: (world) =\u003e {\n        world.query(Position, Velocity).forEach((entity) =\u003e {\n            entity.destroy();\n        });\n    },\n}));\n\nfunction DoomButton() {\n    const { spawnShip, destroyAllShips } = useActions(actions);\n\n    // Spawn three ships on mount\n    useEffect(() =\u003e {\n        spawnShip({ x: 0, y: 1 });\n        spawnShip({ x: 1, y: 0 });\n        spawnShip({ x: 1, y: 1 });\n\n        // Destroy all ships during cleanup\n        return () =\u003e drestroyAllShips();\n    }, []);\n\n    // And destroy all ships on click!\n    return \u003cbutton onClick={destroyAllShips}\u003eBoom!\u003c/button\u003e;\n}\n```\n\nOr access world directly and use it.\n\n```js\nconst world = useWorld();\n\nuseEffect(() =\u003e {\n    const entity = world.spawn(Velocity, Position);\n    return () =\u003e entity.destroy();\n});\n```\n\n## Advanced\n\n### Relationships\n\nKoota supports relationships between entities using the `relation` function. Relationships allow you to create connections between entities and query them efficiently.\n\n```js\nconst ChildOf = relation();\n\nconst parent = world.spawn();\nconst child = world.spawn(ChildOf(parent));\n\nconst entity = world.queryFirst(ChildOf(parent)); // Returns child\n```\n\n#### With data\n\nRelationships can contain data like any trait.\n\n```js\nconst Contains = relation({ store: { amount: 0 } });\n\nconst inventory = world.spawn();\nconst gold = world.spawn();\ninventory.add(Contains(gold));\ninventory.set(Contains(gold), { amount: 10 });\n```\n\n#### Auto remove target\n\nRelations can automatically remove target entities and their descendants.\n\n```js\nconst ChildOf = relation({ autoRemoveTarget: true });\n\nconst parent = world.spawn();\nconst child = world.spawn(ChildOf(parent));\nconst grandchild = world.spawn(ChildOf(child));\n\nparent.destroy();\n\nworld.has(child); // False, the child and grandchild are destroyed too\n```\n\n#### Exclusive Relationships\n\nExclusive relationships ensure each entity can only have one target.\n\n```js\nconst Targeting = relation({ exclusive: true });\n\nconst hero = world.spawn();\nconst rat = world.spawn();\nconst goblin = world.spawn();\n\nhero.add(Targeting(rat));\nhero.add(Targeting(goblin));\n\nhero.has(Targeting(rat)); // False\nhero.has(Targeting(goblin)); // True\n```\n\n#### Querying relationships\n\nRelationships can be queried with specific targets, wildcard targets using `*` and even inverted wildcard searches with `Wildcard` to get all entities with a relationship targeting another entity.\n\n```js\nconst gold = world.spawn();\nconst silver = world.spawn();\nconst inventory = world.spawn(Contains(gold), Contains(silver));\n\nconst targets = inventory.targetsFor(Contains); // Returns [gold, silver]\n\nconst chest = world.spawn(Contains(gold));\nconst dwarf = world.spawn(Desires(gold));\n\nconst constainsSilver = world.query(Contains(silver)); // Returns [inventory]\nconst containsAnything = world.query(Contains('*')); // Returns [inventory, chest]\nconst relatesToGold = world.query(Wildcard(gold)); // Returns [inventory, chest, dwarf]\n```\n\n### Query modifiers\n\nModifiers are used to filter query results enabling powerful patterns. All modifiers can be mixed together.\n\n#### Not\n\nThe `Not` modifier excludes entities that have specific traits from the query results.\n\n```js\nimport { Not } from 'koota';\n\nconst staticEntities = world.query(Position, Not(Velocity));\n```\n\n#### Or\n\nBy default all query parameters are combined with logical AND. The `Or` modifier enables using logical OR instead.\n\n```js\nimport { Or } from 'koota';\n\nconst movingOrVisible = world.query(Or(Velocity, Renderable));\n```\n\n#### Added\n\nThe `Added` modifier tracks all entities that have added the specified traits since the last time the query was run. A new instance of the modifier must be created for tracking to be unique.\n\n```js\nimport { createAdded } from 'koota';\n\nconst Added = createAdded();\n\n// This query will return entities that have just added the Position trait\nconst newPositions = world.query(Added(Position));\n\n// After running the query, the Added modifier is reset\n```\n\n#### Removed\n\nThe `Removed` modifier tracks all entities that have removed the specified traits since the last time the query was run. This includes entities that have been destroyed. A new instance of the modifier must be created for tracking to be unique.\n\n```js\nimport { createRemoved } from 'koota';\n\nconst Removed = createRemoved();\n\n// This query will return entities that have just removed the Velocity trait\nconst stoppedEntities = world.query(Removed(Velocity));\n\n// After running the query, the Removed modifier is reset\n```\n\n#### Changed\n\nThe `Changed` modifier tracks all entities that have had the specified traits values change since the last time the query was run. A new instance of the modifier must be created for tracking to be unique.\n\n```js\nimport { createChanged } from 'koota';\n\nconst Changed = createChanged();\n\n// This query will return entities whose Position has changed\nconst movedEntities = world.query(Changed(Position));\n\n// After running the query, the Changed modifier is reset\n```\n\n### Query all entities\n\nTo get al queryable entities you simply query with not paramerters. Note, that not all entities are queryable. Any entity that has `IsExcluded` will not be able to be queried. This is used in Koota to exclude world entities, for example, but maybe used for other system level entities in the future. To get all entities regardless, use `world.entities`.\n\n```js\n// Returns all queryable entities\nconst allQueryableEntities = world.query()\n```\n\n### Add, remove and change events\n\nKoota allows you to subscribe to add, remove, and change events for specific traits.\n\n```js\n// Subscribe to Position changes\nconst unsub = world.onChange(Position, (entity) =\u003e {\n    console.log(`Entity ${entity} changed position`);\n});\n\n// Subscribe to Position additions\nconst unsub = world.onAdd(Position, (entity) =\u003e {\n    console.log(`Entity ${entity} added position`);\n});\n\n// Subscribe to Position removals\nconst unsub = world.onRemove(Position, (entity) =\u003e {\n    console.log(`Entity ${entity} removed position`);\n});\n\n// Trigger events\nconst entity = world.spawn(Position);\nentity.set(Position, { x: 10, y: 20 });\nentity.remove(Position);\n```\n\n### Change detection with `updateEach`\n\nBy default, `updateEach` will automatically turn on change detection for traits that are being tracked via `onChange` or the `Changed` modifier. If you want to silence change detection for a loop or force it to always run, you can do so with an options config.\n\n```js\n// Setting changeDetection to 'never' will silence it, triggering no change events\nworld.query(Position, Velocity).updateEach(([position, velocity]) =\u003e {\n}, { changeDetection: 'never' });\n\n// Setting changeDetection to 'always' will ignore selective tracking and always emit change events for all traits that are mutated\nworld.query(Position, Velocity).updateEach(([position, velocity]) =\u003e {\n}, { changeDetection: 'always' });\n```\n\n### World traits\n\nFor global data like time, these can be traits added to the world. **World traits do not appear in queries.**\n\n```js\nconst Time = trait({ delta: 0, current: 0 });\nworld.add(Time);\n\nconst time = world.get(Time);\nworld.set(Time, { current: performance.now() });\n```\n\n### Select traits on queries for updates\nQuery filters entity results and `select` is used to choose what traits are fetched for `updateEach` and `useStore`. This can be useful if your query is wider than the data you want to modify.\n\n```js\n// The query finds all entities with Position, Velocity and Mass\nworld.query(Position, Velocity, Mass)\n  // And then select only Mass for updates\n  .select(Mass)\n  // Only mass will be used in the loop\n  .updateEach([mass] =\u003e {\n    // We are going blackhole\n    mass.value += 1\n  });\n```\n\n### Modifying trait stores direclty\n\nFor performance-critical operations, you can modify trait stores directly using the `useStore` hook. This approach bypasses some of the safety checks and event triggers, so use it with caution. All stores are structure of arrays for performance purposes.\n\n```js\n// Returns the SoA stores\nworld.query(Position, Velocity).useStore(([position, velocity], entities) =\u003e {\n    // Write our own loop over the stores\n    for (let i = 0; i \u003c entities.length; i++) {\n        // Get the entity ID to use as the array index\n        const eid = entities[i].id();\n        // Write to each array in the store\n        position.x[eid] += velocity.x[eid] * delta;\n        position.y[eid] += velocity.y[eid] * delta;\n    }\n});\n```\n\n## APIs in detail until I make docs\n\nThese are more like notes for docs. Take a look around, ask questions. Eventually this will become proper docs.\n\n### World\n\nThis is where all data is stored. We have methods on entities but this is a bit of a trick, entities don't actually store any data and instead it is operating on the connected world. Each world has its own set of entities that do not overlap with another. Typically you only need one world.\n\nWorlds can have traits, which is our version of a singleton. Use these for global resources like a clock. Each world gets its own entity used for world traits. This entity is no queryable but will show up in the list of active entities making the only way to retrieve a world trait with its API.\n\n```js\n// Spawns an entity\n// Can pass any number of traits\n// Return Entity\nconst entity = world.spawn()\n\n// Checks if the world has the entity\n// Return boolean\nconst result = world.has(entity)\n\n// Get all entities that match the query parameters\n// Return QueryResult (which is Entity[] with extras)\nconst entities = world.query(Position)\n\n// Return the first entity that matches the query\n// Return Entity\nconst entity = world.queryFirst(Position)\n\n// The trait API is identical to entity's\n\n// Add a trait to the world\nworld.add(Time)\n\n// Remove a trait from the world\nworld.remove(Time)\n\n// Check if the world has a trait\n// Return boolean\nconst result = world.has(Time)\n\n// Gets a snapshot instance of the trait\n// Return TraitInstance\nconst time = world.get(Time)\n\n// Sets the trait and triggers a change event\nworld.set(Time, { current: performance.now() })\n// Can take a callback with the previous state passed in\nworld.set(Time, (prev) =\u003e ({\n  current: performance.now(),\n  delta: performance.now() - prev.current\n}))\n\n// Subscribe to add, remove or change events for a set of query parameters\n// Anything you can put in a query is legal\n// Return unsub function\nconst unsub = world.onAdd([Position], (entity) =\u003e {})\nconst unsub = world.onRemove([Position], (entity) =\u003e {})\nconst unsub = world.onChange([Position], (entity) =\u003e {})\n\n// An array of all entities alive in the world, including non-queryable entities\n// This is a copy so editing it won't do anything!\n// Entity[]\nworld.entities\n\n// Returns the world's unique ID\n// Return number\nconst id = world.id()\n\n// Resets the world as if it were just created\n// The world ID and reference is preserved\nworld.reset()\n\n// Nukes the world and releases its ID\nworld.destroy()\n```\n\n### Entity\n\nAn entity is a number encoded with a world, generation and ID. Every entity is unique even if they have the same ID since they will have different generations. This makes automatic-recycling possible without reference errors. Because of this, the number of an entity won't give you its ID but will have to instead be decoded with `entity.id()`.\n\n```js\n// Add a trait to the entity\nentity.add(Position) \n\n// Remove a trait from the entity\nentity.remove(Position)\n\n// Checks if the entity has the trait\n// Return boolean\nconst result = enttiy.has(Position) \n\n// Gets a snapshot instance of the trait\n// Return TraitInstance\nconst position = entity.get(Position)\n\n// Sets the trait and triggers a change event\nentity.set(Position, { x: 10, y: 10 })\n// Can take a callback with the previous state passed in\nentity.set(Position, (prev) =\u003e ({\n  x: prev + 1,\n  y: prev + 1\n}))\n\n// Get the targets for a relationship\n// Return Entity[]\nconst targets = entity.targetsFor(Contains)\n\n// Get the first target for a relationship\n// Return Entity\nconst target = entity.targetFor(Contains)\n\n// Get the entity ID\n// Return number\nconst id = entity.id()\n\n// Destroys the entity making its number no longer valid\nentity.destroy()\n```\n\n### Trait\n\nA trait is a specific block of data. They are added to entities to build up its overall data signature. If you are familiar with ECS, it is our version of a component. It is called a trait instead to not get confused with React or web components. \n\nA trait can be created with a schema that describes the kind of data it will hold. \n\n```js\nconst Position = trait({ x: 0, y: 0, z: 0 })\n```\n\nIn cases where the data needs to be initialized for each instance of the trait created, a callback can be passed in to be used a as a lazy initializer.\n\n```js\n// ❌ The items array will be shared between every instance of this trait\nconst Inventory = trait({ \n  items: [], \n  max: 10, \n})\n\n// ✅ With a lazy initializer, each instance will now get its own array\nconst Inventory = trait({ \n  items: () =\u003e [], \n  max: 10, \n})\n```\n\nSometimes a trait only has one field that points to an object instance. In cases like this, it is useful to skip the schema and use a callback directly in the trait.\n\n```js\nconst Velocity = trait(() =\u003e new THREE.Vector3())\n\n// The returned state is simply the instance\nconst velocity = entity.get(Velocity)\n```\n\nBoth schema-based and callback-based traits are used similarly, but they have different performance implications due to how their data is stored internally:\n\n1. Schema-based traits use a Structure of Arrays (SoA) storage.\n2. Callback-based traits use an Array of Structures (AoS) storage.\n\n[Learn more about AoS and SoA here](https://en.wikipedia.org/wiki/AoS_and_SoA).\n\n#### Structure of Arrays (SoA) - Schema-based traits\n\nWhen using a schema, each property is stored in its own array. This can lead to better cache locality when accessing a single property across many entities. This is always the fastest option for data that has intensive operations.\n\n```js\nconst Position = trait({ x: 0, y: 0, z: 0 });\n\n// Internally, this creates a store structure like:\nconst store = {\n  x: [0, 0, 0, ...], // Array for x values\n  y: [0, 0, 0, ...], // Array for y values\n  z: [0, 0, 0, ...], // Array for z values\n};\n```\n\n#### Array of Structures (AoS) - Callback-based traits\n\nWhen using a callback, each entity's trait data is stored as an object in an array. This is best used for compatibiilty with third party libraries like Three, or class instnaces in general.\n\n```js\nconst Velocity = trait(() =\u003e ({ x: 0, y: 0, z: 0 }));\n\n// Internally, this creates a store structure like:\nconst store = [\n  { x: 0, y: 0, z: 0 },\n  { x: 0, y: 0, z: 0 },\n  { x: 0, y: 0, z: 0 },\n  // ...\n];\n\n// Similarly, this will create a new instance of Mesh in each index\nconst Mesh = trait(() =\u003e new THREE.Mesh())\n```\n\n#### Typing traits\n\nTraits can have a schema type passed into its generic. This can be useful if the inferred type is not good enough.\n\n```js\ntype AttackerSchema = {\n  continueCombo: boolean | null,\n  currentStageIndex: number | null,\n  stages: Array\u003cAttackStage\u003e | null,\n  startedAt: number | null,\n}\n\nconst Attacker = trait\u003cAttackerSchema\u003e({\n  continueCombo: null,\n  currentStageIndex: null,\n  stages: null,\n  startedAt: null,\n})\n```\n\nHowever, this will not work with interfaces without a workaround due to intended behavior in TypeScript: https://github.com/microsoft/TypeScript/issues/15300\nInterfaces can be used with `Pick` to convert the key signatures into something our type code can understand.\n\n```js\ninterface AttackerSchema {\n  continueCombo: boolean | null,\n  currentStageIndex: number | null,\n  stages: Array\u003cAttackStage\u003e | null,\n  startedAt: number | null,\n}\n\n// Pick is required to not get type errors\nconst Attacker = trait\u003cPick\u003cAttackerSchema, keyof AttackerSchema\u003e\u003e({\n  continueCombo: null,\n  currentStageIndex: null,\n  stages: null,\n  startedAt: null,\n})\n```\n\n\n### React\n\n### `useQuery` \n\nReactively updates when entities matching the query changes. Returns a `QueryResult`, which is like an array of entities.\n\n```js\n// Get all entities with Position and Velocity traits\nconst entities = useQuery(Position, Velocity);\n\n// Render a view\nreturn (\n  \u003c\u003e\n    {entities.map(entity =\u003e \u003cView key={entity.id()} entity={entity} /\u003e)}\n  \u003c/\u003e\n);\n```\n\n### `usQueryFirst` \n\nWorks like `useQuery` but only returns the first result. Can either be an entity of undefined.\n\n```js\n// Get the first entity with Player and Position traits\nconst player = useQueryFirst(Player, Position);\n\n// Render a view if an entity is found\nreturn player ? (\n  \u003cView entity={player} /\u003e\n) : null;\n\n```\n\n### `useWorld` \n\nReturns the world held in context via `WorldProvider`.\n\n```js\n// Get the default world\nconst world = useWorld();\n\n// Use the world to create an entity on mount\nuseEffect(() =\u003e {\n    const entity = world.spawn()\n    return =\u003e entity.destroy()\n}, [])\n\n```\n\n### `WorldProvider` \n\nThe provider for the world context. A world must be created and passed in.\n\n```js\n// Create a world and pass it to the provider\nconst world = createWorld();\n\n// All hooks will now use this world instead of the default\nfunction App() {\n  return (\n    \u003cWorldProvider world={world}\u003e\n      \u003cGame /\u003e\n    \u003c/WorldProvider\u003e\n  );\n}\n\n```\n\n### `useTrait` \n\nObserves an entity, or world, for a given trait and reactively updates when it is added, removed or changes value. The returned trait snapshot maybe `undefined` if the trait is no longer on the target. This can be used to conditionally render.\n\n```js\n// Get the position trait from an entity and reactively updates when it changes\nconst position = useTrait(entity, Position);\n\n// If position is removed from entity then it will be undefined\nif (!position) return null\n\n// Render the position\nreturn (\n  \u003cdiv\u003e\n    Position: {position.x}, {position.y}\n  \u003c/div\u003e\n);\n```\n\nThe entity passed into `useTrait` can be `undefined` or `null`. This helps with situations where `useTrait` is combined with queries in the same component since hooks cannot be conditionally called. However, this means that result can be `undefined` if the trait is not on the entity or if the target is itself `undefined`. In most cases the distinction will not matter, but if it does you can disambiguate by testing the target.\n\n```js\n// The entity may be undefined if there is no valid result\nconst entity = useQueryFirst(Position, Velocity)\n// useTrait handles this by returned undefined if the target passed in does not exist\nconst position = useTrait(entity, Position);\n\n// However, undefined here can mean no entity or no component on entity\n// To make the outcome no longer ambiguous you have to test the entity\nif (!entity) return \u003cdiv\u003eNo entity found!\u003c/div\u003e\n\n// Now this is narrowed to Position no longer being on the component\nif (!position) return null\n\nreturn (\n  \u003cdiv\u003e\n    Position: {position.x}, {position.y}\n  \u003c/div\u003e\n);\n```\n\n### `useTraitEffect` \n\nSubscribes a callback to a trait on an entity. This callback fires as an effect whenenver it is added, removed or changes value without rerendering.\n\n```js\n// Subscribe to position changes on an entity and update a ref without causing a rerender\nuseTraitEffect(entity, Position, (position) =\u003e {\n  if (!position) return;\n  meshRef.current.position.copy(position);\n});\n\n// Subscribe to world-level traits\nuseTraitEffect(world, GameState, (state) =\u003e {\n  if (!state) return;\n  console.log('Game state changed:', state);\n});\n\n```\n\n### `useActions` \n\nReturns actions bound to the world that is context. Use actions created by `createActions`.\n\n```js\n// Create actions\nconst actions = createActions((world) =\u003e ({\n    spawnPlayer: () =\u003e world.spawn(IsPlayer).\n    destroyAllPlayers: () =\u003e {\n        world.query(IsPlayer).forEach((player) =\u003e {\n            player.destroy()\n        })\n    }\n}))\n\n// Get actions bound to the world in context\nconst { spawnPlayer, destroyAllPlayers } = useActions();\n\n// Call actions to modify the world in an effect or handlers\nuseEffect(() =\u003e {\n    spawnPlayer()\n    return () =\u003e destroyAllPlayers()\n}, [])\n```\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmndrs%2Fkoota","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpmndrs%2Fkoota","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmndrs%2Fkoota/lists"}