A React renderer for InSim buttons, based on Node InSim.
Warning
This project is still under development. Any API may change as needed.
React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.
It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.
It allows you to create things like this:
Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);- Requirements
- Installation
- Basic usage
- Button
- Horizontal stack
- Vertical stack
- Flex
- Grid
- Toggle button
- Toggle button group
- Text box
- Hooks
- Scopes
- Using React Devtools
- Development
- Node.js 22 or higher
- Node InSim 6
- React 19
npm install react@19 node-insim react-node-insimyarn add react@19 node-insim react-node-insimpnpm add react@19 node-insim react-node-insimDisplaying an InSim button on a local computer
import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);You can use React hooks as usual to display stateful data via InSim.
import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);The Button component is used to display a button in LFS.
- Buttons are drawn on a 200 by 200 canvas using absolute positioning
- The maximum number of rendered buttons on a screen is 240
import{Button}from'react-node-insim';<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.
<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.
<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.
<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.
<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>You can choose from a set of semantic colors or use one of the colors from the LFS color palette.
Semantic colors
- default
- title
- unselected
- selected
- ok
- cancel
- textstring
- unavailable
Note: The semantic color values can be customized in LFS Options -> Display -> Interface.
LFS color palette
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.
<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.
import{HStack}from'react-node-insim';<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.
import{VStack}from'react-node-insim';<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>Flex layout displays buttons in a row or column with flexbox options.
import{Flex}from'react-node-insim';<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>Grid layout displays buttons in a grid.
import{Grid,GridButton}from'react-node-insim';<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>A button that can be toggled on and off by clicking it.
import{ToggleButton}from'react-node-insim';functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.
<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>Use the isDisabled prop to prevent toggling the button on/off.
<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>A group of buttons that can be toggled on and off by clicking them.
import{ToggleButtonGroup}from'react-node-insim';constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.
import{TextBox}from'react-node-insim';<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>Execute code after the InSim app has been connected.
The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.
import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}Execute code after the InSim app has been disconnected.
The first parameter is the "disconnect" event callback from Node InSim.
import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}Execute code when an InSim packet is received
import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}Get a live list of all connected guests.
import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}Get a live list of all players on track.
import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}Send a race control message (RCM) to a connection or a player.
import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}Access to Node InSim API of the current InSim client instance.
import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.
You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.
You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.
import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.
You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.
import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.
You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.
import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:
DEV=true npm startThen, start React Devtools itself:
npx react-devtoolsAfter it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.
yarnyarn startyarn lintyarn format
















