Getting started
Here's a very simple example of how using Facet
s for state management could look like:
tsx
importReact , {useCallback } from 'react'import {useFacetState ,NO_VALUE } from '@react-facet/core'import {createRoot } from '@react-facet/dom-fiber'constCounter = () => {const [counter ,setCounter ] =useFacetState (0)consthandleClick =useCallback (() => {setCounter ((counter ) => (counter !==NO_VALUE ?counter + 1 :counter ))}, [setCounter ])return (<div ><p >Current count: <fast-text text ={counter } /></p ><button onClick ={handleClick }>Increment</button ></div >)}constroot =createRoot (document .getElementById ('root'))root .render (<Counter />)
Installation
The packages of React Facet are grouped under the @react-facet
scope on npm. To install the packages for the example above, run:
sh
yarn add @react-facet/core @react-facet/dom-fiber
Note that if you want to use other packages, you will need to install them separately.
Using fast-*
Components
Using the Custom Renderer
We recommend using the custom Renderer provided with @react-facet/dom-fiber
to leverage the full power of Facet
s.
To render with the custom renderer, use createRoot
from @react-facet/dom-fiber
:
tsx
import {useFacetState } from '@react-facet/core'import {createRoot } from '@react-facet/dom-fiber'constHelloWorld = () => {const [className ,setClassName ] =useFacetState ('root')const [helloWorld ,setHelloWorld ] =useFacetState ('Hello World!')return (<fast-div className ={className }><fast-text text ={helloWorld } /></fast-div >)}constroot =createRoot (document .getElementById ('root'))root .render (<HelloWorld />)
Creating Facets
Facets are just JavaScript objects that update over time. An example of a facet interface definition could be:
tsx
export interfaceUserFacet {username : stringsignOut (): void}
They can be initialized by using Hooks provided in @react-facet/core
, and can be read and written to:
tsx
import {useFacetMap ,useFacetState ,useFacetCallback ,NO_VALUE } from '@react-facet/core'interfaceTemporaryValuesFacet {username : stringpassword : string}constUpdateLogin = ({onSubmit }:Props ) => {const [temporaryValues ,updateValues ] =useFacetState <TemporaryValuesFacet >({username : '',password : '',})constusername =useFacetMap ((values ) =>values .username , [], [temporaryValues ])constpassword =useFacetMap ((values ) =>values .password , [], [temporaryValues ])consthandleClick =useFacetCallback ((values ) => () => {onSubmit (values )},[onSubmit ],[temporaryValues ],)return (<div ><p >User name</p ><fast-input type ="text"value ={username }onKeyUp ={(event ) => {updateValues ((values ) => {if (values !==NO_VALUE ) {values .username = (event .target asHTMLInputElement ).value }returnvalues })}}/><p >Password</p ><fast-input type ="text"value ={password }onKeyUp ={(event ) => {updateValues ((values ) => {if (values !==NO_VALUE ) {values .password = (event .target asHTMLInputElement ).value }returnvalues })}}/><button onClick ={handleClick }>Submit</button ></div >)}
Interfacing with the game engine (Shared Facets)
Shared facets are facets that come from the "backend" game engine, usually implemented in C++. They are largely used the same way as "local" facets, except for a couple key differences:
- They cannot be mutated directly by JavaScript
- They are available globally
- They must be initialized using
sharedFacet
- They must be consumed in a React Component with
useSharedFacet
To use shared facets, you must wrap your React application inside SharedFacetDriverProvider
. You must also provide a sharedFacetDriver
, which takes care of requesting the facet from a C++ backend and registering a listener to be notified about updates. Below you can find a pseudo-code of a how an implementation would look like using an engine
that implements EventEmitter
tsx
import {SharedFacetDriverProvider ,OnChange } from '@react-facet/shared-facet'constsharedFacetDriver = (facetName : string,update :OnChange <unknown>) => {// register a listenerengine .on (`facet:updated:${facetName }`,update )// trigger an event to notify C++ we want to listen for updatesengine .trigger ('facet:request',facetName )// returns a cleanup function once no more components need the facet datareturn () => {engine .off (`facet:updated:${facetName }`,update )engine .trigger ('facet:discard',facetName )}}constApp = () => {return <SharedFacetDriverProvider value ={sharedFacetDriver }>...</SharedFacetDriverProvider >}
An example of defining and consuming a shared facet:
tsx
import {useSharedFacet ,sharedFacet ,sharedSelector } from '@react-facet/shared-facet'interfaceUserFacet {username : stringsignOut (): void}constuserFacet =sharedFacet <UserFacet >('data.user', {username : 'Alex',signOut () {},})constusernameSelector =sharedSelector ((value ) =>value .username , [userFacet ])export constCurrentUser = () => {constusername =useSharedFacet (usernameSelector )return <fast-text text ={username } />}