I have a terminal component that render a terminale emulator.
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import { XTerm } from '../../node_modules/react-xterm';
import os from 'os';
import { connect } from 'react-redux';
import '../../node_modules/xterm/dist/xterm.css';
const pty = require('node-pty');
const mapStateToProps = (state) => {
return state;
};
class Terminal extends Component {
constructor(props) {
super(props);
this.xtermLoaded = false;
this.ptyProcess = null;
this.term = null;
this.shell = process.env[os.platform() === 'win32' ? 'COMSPEC' : 'SHELL'];
this.ptyProcess = pty.spawn(this.shell, [], {
name: 'xterm-color',
cols: 50,
rows: 30,
cwd: process.cwd(),
env: process.env
});
}
bindXterm(xterm) {
if (!xterm || this.xtermLoaded) return;
let term = xterm.getTerminal();
if (!term.on) return;
this.term = term;
this.xtermLoaded = true;
this.term.on('data', data => {
this.ptyProcess.write(data);
});
this.ptyProcess.on('data', data => {
this.term.write(data);
});
}
render() {
return (
<XTerm style={{
addons: ['fit', 'fullscreen'],
overflow: 'hidden',
position: 'relative',
width: '100%',
height: '100%'
}} ref={xterm => this.bindXterm(xterm)}/>
);
}
}
export default connect(mapStateToProps)(withStyles(styles)(Terminal));
With this.ptyProcess.write(data), i can write new things into the terminal.
But how i can access to this.ptyProcess.write(data) from another component ?
Can someone help me please ? :)
Thanks.
Presumably this component is created either by another component, or by a call to ReactDOM.Render(). You should make ptyProcess a member of the nearest common ancestor of all the components that need to access it, and then pass it down to them as part of the props object.
As a general rule in react, if a property needs to be shared by multiple child components, then you should "hoist" that property up into the parent component and pass it down to the children through props.
You are already using redux. Just include a dataForTerminal reducer that when changes, new data are written into the terminal.
So create an action creator for it and then you can call the action creator from any component
Related
I want to pass a state using react navigator. I want to pass showing: false, so my progress bar component will disappear.Can someone please explain how I can do this. Thanks so much.
Here is my code.
import React, { Component } from "react";
import { Button, View, Text, TextInput } from "react-native";
import ContinueButton from "./ContinueButton";
import { CreateAboutMe } from "./StyleSheet/AboutMeStyle";
import * as Progress from "react-native-progress";
export class AboutUser extends Component {
constructor(props) {
super(props);
this.navigatToInterests = this.navigatToInterests.bind(this);
this.checkEntry = this.checkEntry.bind(this);
this.state = {
value: "",
showing: true,
};
}
navigatToInterests = ({ navigation }) => {
let checkDescription = this.state.value;
if (checkDescription === "") {
alert("Please tell people about yourself");
} else {
this.props.navigation.navigate("Interests");
}
};
checkEntry = (Description, value) => {
this.setState({ value: value });
console.log(this.state.value);
};
render() {
return (
<View style={CreateAboutMe.overAllContainer}>
{this.state.showing && (
<Progress.Bar
progress={0.7667}
width={300}
color={"red"}
style={CreateAboutMe.progressbar}
showing={this.state.showing}
/>
)}
Which version of React Navigation are you using?
In version 4, you can send some data using the second argument of the navigate function like this:
this.props.navigation.navigate("Interests",{"someKey":"someValue", ...});
Then you can grab the data in the next page through the props:
let someValue = this.props.navigation.getParam('someKey');
I'm using a component called react-input-autosize. The issue I'm facing is that it won't resize the input on viewport resize, so I wan't to manually hook into the component methods and run copyInputStyles() and updateInputWidth().
Pretty new to React so don't know how to achieve this. You can expose the input via the inputRef, but that doesn't really help me no?
My current reduced test case looks like this, would be happy with any pointers on how to run the component methods.
import React from 'react';
import styled from '#emotion/styled';
import AutosizeInput from 'react-input-autosize';
import {throttle} from 'lodash';
const StyledAutosizeInput = styled(AutosizeInput)`
max-width: 100vw;
`;
export default class signUp extends React.Component {
constructor (props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
console.log(this.inputRef); // outputs input node
window.addEventListener('resize', this.resize);
this.resize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
}
resize = throttle(() => {
console.log('force resize');
}, 100);
render() {
return (
<StyledAutosizeInput
inputRef={node => this.inputRef = node}
/>
);
}
}
The inputRef={node => this.inputRef = node} callback is referring to the html input element and not the AutosizeInput component. Pass the ref via the ref prop to access the component's methods.
...
resize = throttle(() => {
if (this.inputRef.current) {
this.inputRef.current.copyInputStyles()
this.inputRef.current.updateInputWidth()
}
}, 100);
render() {
return (
<StyledAutosizeInput
ref={this.inputRef}
/>
);
}
I've got a React application created with create-react-app and I'm trying to integrate Phaser 3 as well. I followed this guide to get started. I've got the canvas rendering the text but loading images in the preload does not seem to be working. I get the default failed to load texture image displayed.
import ExampleScene from "./scenes/ExampleScene";
import * as React from "react";
export default class Game extends React.Component {
componentDidMount() {
const config = {
type: Phaser.AUTO,
parent: "phaser-example",
width: 800,
height: 600,
scene: [ExampleScene]
};
new Phaser.Game(config);
}
shouldComponentUpdate() {
return false;
}
render() {
return <div id="phaser-game" />;
}
}
ExampleScene:
import Phaser from "phaser";
export default class ExampleScene extends Phaser.Scene {
preload() {
this.load.image("logo", "assets/logo.png");
}
create() {
const text = this.add.text(250, 250, "Phaser", {
backgroundColor: "white",
color: "blue",
fontSize: 48
});
text.setInteractive({ useHandCursor: true });
this.add.image(400, 300, "logo");
text.on("pointerup", () => {
console.log("Hello!");
//store.dispatch({ type: ACTION_TYPE });
});
}
}
The idea is to create a visualization with flowers growing based on a simple gene engine. So Phaser would get instructions from the Store about the current state.
I'm guess this has something to do with the way Phaser loads and there's a conflict with how React updates. I'm preventing the component from updating as I only need the game to receive instructions by listening to the store
I've already looked at this SO answer and the accompanying wrapper, but it is outdated.
How can I get Phaser to load images when in a Create-React-App?
CodeSandbox: https://codesandbox.io/s/github/nodes777/react-punnett/tree/phaser-game
Repo: https://github.com/nodes777/react-punnett/tree/phaser-game
Other option is using WebComponents to be able to integrate Phaser with any other framework (React, Angular, VueJS, etc), check this npm package: https://www.npmjs.com/package/#ion-phaser/core
Also, you can use the React wrapper of that library to use Phaser with React components easily, so you don't need to manipulate WebComponents directly, example:
import React from 'react'
import Phaser from 'phaser'
import { IonPhaser } from '#ion-phaser/react'
const game = {
width: "100%",
height: "100%",
type: Phaser.AUTO,
scene: {
init: function() {
this.cameras.main.setBackgroundColor('#24252A')
},
create: function() {
this.helloWorld = this.add.text(
this.cameras.main.centerX,
this.cameras.main.centerY,
"Hello World", {
font: "40px Arial",
fill: "#ffffff"
}
);
this.helloWorld.setOrigin(0.5);
},
update: function() {
this.helloWorld.angle += 1;
}
}
}
const App = () => {
return (
<IonPhaser game={game} />
)
}
export default App;
Fore more details check the repo: https://github.com/proyecto26/ion-phaser/tree/master/react
A year ago I was here looking for the answer myself. Here's pattern which should work.
import Phaser from "phaser"
import React, { useEffect, useState } from "react"
/** #tutorial I made this! This answers how you get your image. */
import logoImage from "./path-to-logo.png"
/** #tutorial I made this! Use a functional React component and `useEffect` hook.*/
export const Phaser3GameComponent = ({ someState }) => {
// Optional: useful to delay appearance and avoid canvas flicker.
const [isReady, setReady] = useState(false)
// Just an example... do what you do here.
const dataService = (changedState) => {
// I'm not sure how to use stores, but you'll know better what to do here.
store.dispatch(
{
...someState,
...changedState,
},
{ type: ACTION_TYPE }
)
}
// This is where the fun starts.
useEffect(() => {
const config = {
callbacks: {
preBoot: game => {
// A good way to get data state into the game.
game.registry.merge(someState)
// This is a good way to catch when that data changes.
game.registry.events.on("changedata", (par, key, val, prevVal) => {
// Simply call whatever functions you want outside.
dataService({ [key]: val })
})
},
},
type: Phaser.AUTO,
parent: "phaser-example",
width: 800,
height: 600,
scene: [ExampleScene],
}
let game = new Phaser.Game(config)
// Triggered when game is fully READY.
game.events.on("READY", setReady)
// If you don't do this, you get duplicates of the canvas piling up.
return () => {
setReady(false)
game.destroy(true)
}
}, []) // Keep the empty array otherwise the game will restart on every render.
return (
<div id="phaser-example" className={isReady ? "visible" : "invisible"} />
)
}
export default class ExampleScene extends Phaser.Scene {
preload() {
this.load.image("logo", logoImage)
}
create() {
// You made this!
const text = this.add.text(250, 250, "Phaser")
text.setInteractive({ useHandCursor: true })
this.add.image(400, 300, "logo")
/** #tutorial I made this! */
// Get all that lovely dataState into your scene,
let { clickCount } = this.registry.getAll()
text.on("pointerup", () => {
// This will trigger the "changedata" event handled by the component.
this.registry.merge({ clickCount: clickCount++ })
})
// This will trigger the scene as now being ready.
this.game.events.emit("READY", true)
}
}
I started from scratch and created my own boilerplate from the phaser 3 template. I wrote about the specific steps to add React to the Phaser 3 template here.
It seems like you could eject from Create-React-App and add in Phaser 3 from there, but the warnings not to eject turned me away from that solution.
In my case I use the following component and it works fine:
import Phaser from 'phaser';
import * as React from 'react';
import { HTML_DIV_ID, gameConfig } from './gameConfig';
export const GameWrapper = () => {
const [game, setGame] = React.useState<Phaser.Game>();
React.useEffect(() => {
const _game = new Phaser.Game(gameConfig());
setGame(_game);
return (): void => {
_game.destroy(true);
setGame(undefined);
};
}, []);
return (
<>
<div id={HTML_DIV_ID} />
</>
);
};
With create-react-app and React.StrictMode:
Also I deleted React.StrictMode (default option with create-react-app) because it mounts
and unmounts all components so I had unexpected behavior with phaser
sometimes
You can use react hook for the code above as:
// usePhaser.js
export function userPhaser(config) {
const [game, setGame] = React.useState();
React.useEffect(() => {
const _game = new Phaser.Game(config);
setGame(_game);
return (): void => {
_game.destroy(true);
setGame(undefined);
};
}, []);
return game;
}
You need to put images inside the folder public!
For me, I see the best practice to use both of them properly is to create phaser project separately and host it separately using firebase or whatever hosting service you prefer, and then take the link and put it in an iframe tag inside react.
in this way you can manage them efficiently and you can manipulate react website in more comfortable way especially the mobile width compatibility.
The React component, which can be considered as third-party component, looks as following:
import * as React from 'react';
import classnames from 'classnames';
import { extractCommonClassNames } from '../../utils';
export const Tag = (props: React.ElementConfig): React$Node =>{
const{
classNames,
props:
{
children,
className,
...restProps
},
} = extractCommonClassNames(props);
const combinedClassNames = classnames(
'tag',
className,
...classNames,
);
return (
<span
className={combinedClassNames}
{...restProps}
>
{children}
<i className="sbicon-times txt-gray" />
</span>
);
};
The component where I use the component above looks as following:
import React from 'react';
import * as L from '#korus/leda';
import type { KendoEvent } from '../../../types/general';
type Props = {
visible: boolean
};
type State = {
dropDownSelectData: Array<string>,
dropDownSelectFilter: string
}
export class ApplicationSearch extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {
dropDownSelectData: ['Имя', 'Фамилия', 'Машина'],
dropDownSelectFilter: '',
};
this.onDropDownSelectFilterChange = this.onDropDownSelectFilterChange.bind(this);
}
componentDidMount() {
document.querySelector('.sbicon-times.txt-gray').classList.remove('txt-gray');
}
onDropDownSelectFilterChange(event: KendoEvent) {
const data = this.state.dropDownSelectData;
const filter = event.filter.value;
this.setState({
dropDownSelectData: this.filterDropDownSelectData(data, filter),
dropDownSelectFilter: filter,
});
}
// eslint-disable-next-line class-methods-use-this
filterDropDownSelectData(data, filter) {
// eslint-disable-next-line func-names
return data.filter(element => element.toLowerCase().indexOf(filter.toLowerCase()) > -1);
}
render() {
const {
visible,
} = this.props;
const {
dropDownSelectData,
dropDownSelectFilter,
} = this.state;
return (
<React.Fragment>
{
visible && (
<React.Fragment>
<L.Block search active inner>
<L.Block inner>
<L.Block tags>
<L.Tag>
option 1
</L.Tag>
<L.Tag>
option 2
</L.Tag>
<L.Tag>
...
</L.Tag>
</L.Block>
</L.Block>
</React.Fragment>
)}
</React.Fragment>
);
}
}
Is it possible to remove "txt-gray" from the component from outside and if so, how?
Remove the class from where you're using the Tag component:
componentDidMount() {
document.querySelector('.sbicon-times.txt-gray').classList.remove('txt-gray')
}
Or more specific:
.querySelector('span i.sbicon-times.txt-gray')
As per your comment,
I have multiple components with "txt-gray", but when I use your code, "txt-gray" has been removed from first component only. How to remove it from all components?
I will suggest you to use the code to remove the class in the parent component of using the Tag component. And also use querySelectorAll as in this post.
Refactoring
A clean way is to modify the component to allow it to conditionally add txt-gray through a prop:
<i className={classnames('sbicon-times', { 'txt-gray': props.gray })} />
If the component cannot be modified because it belongs to third-party library, this involves forking a library or replacing third-party component with its modified copy.
Direct DOM access with findDOMNode
A workaround is to access DOM directly in parent component:
class TagWithoutGray extends React.Component {
componentDidMount() {
ReactDOM.findDOMNode(this).querySelector('i.sbicon-times.txt-gray')
.classList.remove('txt-gray');
}
// unnecessary for this particular component
componentDidUpdate = componentDidMount;
render() {
return <Tag {...this.props}/>;
}
}
The use of findDOMNode is generally discouraged because direct DOM access is not idiomatic to React, it has performance issues and isn't compatible with server-side rendering.
Component patching with cloneElement
Another workaround is to patch a component. Since Tag is function component, it can be called directly to access and modify its children:
const TagWithoutGray = props => {
const span = Tag(props);
const spanChildren = [...span.props.children];
const i = spanChildren.pop();
return React.cloneElement(span, {
...props,
children: [
...spanChildren,
React.cloneElement(i, {
...i.props,
className: i.props.className.replace('txt-gray', '')
})
]
});
}
This is considered a hack because wrapper component should be aware of patched component implementation, it may break if the implementation changes.
No, it is not possible
The only way is to change your Tag component
import * as React from 'react';
import classnames from 'classnames';
import { extractCommonClassNames } from '../../utils';
export const Tag = (props: React.ElementConfig): React$Node =>{
const{
classNames,
props:
{
children,
className,
...restProps
},
} = extractCommonClassNames(props);
const combinedClassNames = classnames(
'tag',
className,
...classNames,
);
const grayClass = this.props.disableGray ? 'sbicon-times' : 'sbicon-times txt-gray';
return (
<span
className={combinedClassNames}
{...restProps}
>
{children}
<i className={grayClass} />
</span>
);
};
Now, if you pass disableGray={true} it will suppress the gray class, otherwise of you pass false or avoid passing that prop at all it will use the gray class. It is a small change in the component, but it allows you not to change all the points in your code where you use this component (and you are happy with grey text)
I read some users mentioning that they are using this library with Typescript support, but I cannot find any documentation anywhere nor I cannot seem to make it work on my own.
I am using typescript 2 and I can't manage to create a really simple working example that simply allows me to drag an existing component. I tried several possibilities but I always get stuck into some problems with typings either when calling DragSource (as a decorator or function) or when rendering the resulting component.
In short I would like an example that shows the usage of react-dnd in typescript that allows me how to make an existing component draggable, possibly without modifiying the component itself (it shouldn't be aware that it is draggable)
Thank you for any help!
I've got it working with the DT types package on 2.1. It doesn't compile with strictNullChecks and I haven't been able to track down the reason why. (When you decorate your components with #DragSource and #DropTarget, you somehow change the return type of the render function from Element to Element | null, but I can't see how.)
The other niggle is that all of the props that are inserted by the collecting function are undefined when you first instantiate your components in a render method, so your options are to pass a bunch of {undefined as any} or else declare your collector-injected props as optionals and typeguard them out every where you look at them. Overall the type declaration file is not bad and I found the typing to be more helpful than harmful when getting to know the library.
import {
ConnectDragSource,
DragDropContext,
DragSource,
DragSourceSpec,
DragSourceCollector,
DragSourceConnector,
DragSourceMonitor,
DragElementWrapper,
ConnectDropTarget,
DropTarget,
DropTargetConnector,
DropTargetMonitor,
ClientOffset,
DropTargetSpec } from 'react-dnd';
let HTML5Backend = require('react-dnd-html5-backend');
import { AdjacencyMatrixGraph } from "Geometry/Graph";
import * as React from "react";
import * as Sauce from "Sauce";
import * as ReactDOM from "react-dom";
import * as $ from "jquery";
import { Position2 } from "Geometry";
import * as Rx from "rxjs";
import * as Util from "Util";
require("./GraphBuilder.scss");
interface NodeProps {
label?: string;
position: ClientOffset;
}
/* New node from the node well */
export interface NodeSourceProps {
isDragging : boolean;
connectDragSource: ConnectDragSource;
}
export interface NodeSourceState {
}
// Spec: drag events to handle.
let nodeSourceSpec: DragSourceSpec<NodeSourceProps> = {
beginDrag: (props: NodeSourceProps) => ({}),
};
// Collect: Put drag state into props
let nodeSourceCollector = (connect: DragSourceConnector, monitor: DragSourceMonitor) => {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
};
#DragSource("new-node", nodeSourceSpec, nodeSourceCollector)
export class NodeSource extends React.Component<NodeSourceProps, NodeSourceState> {
constructor(props: NodeSourceProps) {
super(props);
}
render() {
const { connectDragSource, isDragging } = this.props;
return connectDragSource(<span className="node-source">{'\u2683'}</span>);
}
}
/* main graph area */
interface GraphCanvasProps {
connectDropTarget: ConnectDropTarget,
isOver: boolean,
graph: AdjacencyMatrixGraph<NodeProps>;
}
interface GraphCanvasState {}
const canvasDropTargetSpecification: DropTargetSpec<GraphCanvasProps> = {
drop(props: GraphCanvasProps, monitor: DropTargetMonitor, component: React.Component<GraphCanvasProps, any>) {
// console.log("Handling drop", print_monitor(monitor));
let pos = monitor.getSourceClientOffset();
if (monitor.getItemType() === "main-node-move") {
let node = (monitor.getItem() as any);
graph.setData(node.id, { position: pos });
}
else if (monitor.getItemType() === "new-node") {
graph.addNode("node-" + graph.order(), { position: pos });
}
},
};
function canvasDropTargetCollectingFunction(connect: DropTargetConnector, monitor: DropTargetMonitor) {
let rv = {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
};
return rv;
}
/* ... here's a DropTarget ... */
#DropTarget(["main-node-move", "new-node"], canvasDropTargetSpecification, canvasDropTargetCollectingFunction)
export class GraphCanvas extends React.Component<GraphCanvasProps, GraphCanvasState> {
constructor(props: GraphCanvasProps) {
super(props);
}
render(): JSX.Element | null {
const { connectDropTarget, graph } = this.props;
let nodes = graph.nodes();
let nodeEls = Object.keys(nodes).map(k => {
let node = nodes[k];
return <CanvasNode
key={k}
id={k}
node={node}
graph={graph}
connectNodeDrop={null as any}
connectMoveNodeDragger={(null)}/>
});
return connectDropTarget(<div className="graph-canvas">
{nodeEls}
</div>);
}
}
/* ... Here's a the DragContext decorator ... */
#DragDropContext(HTML5Backend)
class Markov extends React.Component<MarkovProps, MarkovState> {