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 {render } 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 >)}render (<Counter />,document .getElementById ('root'))
#
InstallationThe 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, such as the DOM Components, you will need to install them separately:
sh
yarn add @react-facet/dom-components
…and so on for any other required package.
#
Fastest pathUse the components provided in @react-facets/dom-components
wherever you need to render using Facet
s. There are components that serve as drop-in replacements for most of the heavily used HTML elements.
While this won't give you the full performance benefits of Facets, it provides an easier/less risky adoption path if you have a large codebase.
Note: since Gameface only supports a subset of HTML elements, we don't attempt to create components for every single HTML element.
tsx
import {useFacetState } from '@react-facet/core'import {fast } from '@react-facet/dom-components'constHelloWorld = () => {const [className ,setClassName ] =useFacetState ('root')const [text ,setText ] =useFacetState ('Hello World!')return (<fast .div className ={className }><fast .text text ={text } /></fast .div >)}
#
Recommended pathWe 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, replace the render
function from react-dom
with the new one:
diff
- import { render } from 'react-dom'+ import { render } from '@react-facet/dom-fiber'
From here, you can start using fast-*
elements anywhere you need to bind a Facet
to the DOM:
tsx
import {useFacetState } from '@react-facet/core'import {render } 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 >)}render (<HelloWorld />,document .getElementById ('root'))
#
Creating FacetsFacets 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-p ><fast-text text ={username } /></fast-p >}