2024 06 15 managing props and state for zombie4 component in react components - bizkite-co/handterm GitHub Wiki
original file name: Codeium Chat - qRFrlmOxOqxDztAZza1RV2m55dIQb6gu.md
summary: In this chat, the user seeks advice on organizing props and state between two React components, namely HandTerm
and TerminalGame
, to control the Zombie4
character effectively. The user shares code snippets from both components and mentions the challenge of potentially overlapping state management.
I provide guidance on structuring the components to avoid prop drilling and suggest lifting the state related to Zombie4
up to a common ancestor component. I recommend using callbacks to update the Zombie4
state, making it a controlled component, and synchronizing state changes through props. I outline a sample implementation and advise on handling asynchronous logic and cleanup for timeouts or intervals.
By following the proposed approach, the user can maintain a cleaner state management system and ensure consistency in controlling the Zombie4
character across components.
date: 2024-06-15T20:55
Note: This is purely the output of the chat conversation and does not contain any raw data, codebase snippets, etc. used to generate the output.
User at 8:55 pm, Jun 15, 2024
I need your advice on how to organize props and state between these two components.
I am still a little new to React.
I am trying to control the Zombie4
character from both the HandTerm
and the TerminalGame
., but there may be some overlap which I want to resolve.
I have this in TerminalGame
:
public setZombie4ToDeathThenResetPosition = (): void => {
// Set the zombie action to 'Death'
if (this.zombie4DeathTimeout) {
clearTimeout(this.zombie4DeathTimeout);
this.zombie4DeathTimeout = null;
}
this.setZombie4Action('Death');
// After three seconds, reset the position
// TODO: The actions are controlled by props, not state.
console.log("Zombie4 action:", this.state.zombie4ActionType);
this.zombie4DeathTimeout = setTimeout(() => {
// Optionally reset the action if needed
this.setZombie4Action('Walk'); // Or the default action you want to set
this.setState({
zombie4Position: { topY: 0, leftX: -70 },
isPhraseComplete: false,
textScrollX: this.props.canvasWidth
});
this.zombie4DeathTimeout = null;
console.log("Zombie4 action:", this.state.zombie4ActionType);
}, 3000);
};
But I am currently using props to controll the Zombie4
render() {
return (
<>
<div
id={TerminalCssClasses.TerminalGame}
style={{ position: "relative", height: this.props.canvasHeight }}>
<div
className="parallax-background">
{this.state.isTextScrolling && (
<ScrollingTextLayer
text={this.state.textToScroll}
canvasHeight={this.props.canvasHeight} />
)}
{this.state.layers.map((layer, index) => (
<ParallaxLayer
key={index}
layer={layer}
offset={this.state.backgroundOffsetX}
canvasHeight={this.props.canvasHeight}
/>
))}
</div>
<canvas
style={{ position: "absolute", top: 0, left: 0, zIndex: 2 }}
ref={this.canvasRef}
width={this.props.canvasWidth}
height={this.props.canvasHeight}>
</canvas>
<Hero
ref={this.heroRef}
currentActionType={this.props.heroActionType}
scale={1.95}
/>
<Zombie4
ref={this.zombie4Ref}
currentActionType={this.props.zombie4ActionType}
scale={1.90}
/>
</div>
</>
);
}
I need to update the character actions from the HandTerm
too.
public render() {
const { terminalSize } = this.state;
const canvasWidth = terminalSize ? terminalSize.width : 800;
// canvas height does not need to match terminal height
return (
<CommandContext.Consumer>
{(context) => {
this.context = context;
return (
<div className="terminal-container">
<Output
ref={this.outputRef}
elements={this.state.outputElements}
onTouchStart={this.handleTouchStart}
onTouchEnd={this.handleTouchEnd}
/>
<TerminalGame
ref={this.terminalGameRef}
canvasHeight={this.state.canvasHeight}
canvasWidth={canvasWidth} // Use the width from terminalSize if available
isInPhraseMode={this.state.isInPhraseMode}
heroActionType={this.state.heroAction}
zombie4ActionType={this.state.zombie4Action}
onTouchStart={this.handleTouchStart}
onTouchEnd={this.handleTouchEnd}
/>
<NextCharsDisplay
ref={this.nextCharsDisplayRef}
onTimerStatusChange={this.handleTimerStatusChange}
commandLine={this.state.commandLine}
isInPhraseMode={this.state.isInPhraseMode}
newPhrase={this.state.phrase}
onPhraseSuccess={this.handlePhraseSuccess}
/>
<XtermAdapter
ref={this.adapterRef}
terminalElement={this.terminalElementRef.current}
terminalElementRef={this.terminalElementRef}
terminalFontSize={this.currentFontSize}
onAddCharacter={this.handleCharacter}
onTouchStart={this.handleTouchStart}
onTouchEnd={this.handleTouchEnd}
/>
{/* TODO: Move this into JSX in the WebCam component */}
<video
ref={this.videoElementRef as React.RefObject<HTMLVideoElement>}
id="terminal-video"
hidden={!this.isShowVideo}
></video>
</div>
);
}}
</CommandContext.Consumer>
)
}
Codeium at 8:55 pm, Jun 15, 2024
In React, the usual pattern for sharing state between components is lifting the state up to their closest common ancestor, or using a state management library like Redux or Context API for more complex applications.
In your case, the HandTerm
and TerminalGame
components both need to control the Zombie4
character. To avoid prop drilling and keep the state management clean, you can follow these steps:
-
Lift State Up: Move the state related to the
Zombie4
character up to their closest common ancestor component (which might be the component that renders bothHandTerm
andTerminalGame
). -
Use Callbacks: Pass down callbacks as props to
HandTerm
andTerminalGame
components that allow them to update the state of theZombie4
character. -
Controlled Components: Make
Zombie4
a controlled component, which means it will receive its state via props from the parent and will notify the parent of any required state changes through callbacks. -
Synchronize State: Ensure that any action that should change the state of the
Zombie4
character is propagated through these callbacks to update the state in the common ancestor, which in turn passes the new state down as props.
Here is a rough sketch of how this might look:
// In the common ancestor component
class App extends Component {
state = {
zombie4Action: 'Walk',
// ... other related state
};
setZombie4Action = (action) => {
this.setState({ zombie4Action: action });
};
render() {
return (
<>
<HandTerm
setZombie4Action={this.setZombie4Action}
// ... other props
/>
<TerminalGame
zombie4ActionType={this.state.zombie4Action}
setZombie4Action={this.setZombie4Action}
// ... other props
/>
</>
);
}
}
// In TerminalGame component
<Zombie4
currentActionType={this.props.zombie4ActionType}
scale={1.90}
/>
// In HandTerm component when you need to update the action
this.props.setZombie4Action('Death');
You'll need to adjust the setZombie4Action
function to handle any timeouts or asynchronous logic that might be involved with changing the Zombie4
character's state.
Remember to clean up any timeouts or intervals in the componentWillUnmount lifecycle method to prevent memory leaks when the component unmounts.
This structure should help keep props and state organized and maintain a single source of truth for the state of your Zombie4
character.