I have a simple problem to solve. I've created a react app using npx create-react-app. I made a Map component, which I add to my view with this:
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<p>
<Map />
</p>
</div>
);
}
}
And the Map component appears. Now I want to have a button which has an onClick method which calls Map.addImage. First of all it cannot be static (addImage uses Map members, which have to be initialized in constructor). The problem is I know I would have to do var map = new Map(). And then <button type="button" onClick={map.addImage} /> but how can I make my map appear? I cannot go for:
<p>
<map />
</p>
So the question is how can I make my map (after var map = new Map() in render() method, above the return) appear on the screen?
#Edit
Map implementation:
export default class Map extends React.Component {
constructor(props) {
super(props);
this.stage = null;
this.layer = null;
}
componentDidMount() {
//const tween = null;
this.stage = new Konva.Stage({
container: this.containerRef,
width: 1024,
height: 600
});
this.layer = new Konva.Layer();
//const dragLayer = new Konva.Layer();
this.stage.add(this.layer);
/*stage.on("dragstart", function (evt) {
const shape = evt.target;
// moving to another layer will improve dragging performance
shape.moveTo(dragLayer);
stage.draw();
if (tween) {
tween.pause();
}
shape.setAttrs({
shadowOffset: {
x: 15,
y: 15
},
scale: {
x: shape.getAttr("startScale") * 1.2,
y: shape.getAttr("startScale") * 1.2
}
});
});
stage.on("dragend", function (evt) {
const shape = evt.target;
shape.moveTo(layer);
stage.draw();
shape.to({
duration: 0.5,
easing: Konva.Easings.ElasticEaseOut,
scaleX: shape.getAttr("startScale"),
scaleY: shape.getAttr("startScale"),
shadowOffsetX: 5,
shadowOffsetY: 5
});
});*/
}
render() {
return (
<div
className="container"
ref={ref => {
console.log(ref);
this.containerRef = ref;
}}
/>
);
}
addImage() {
var imageObj = new Image();
imageObj.src = './box.png';
imageObj.misc = { stage: this.stage, layer: this.layer };
console.log(this.stage)
imageObj.onload = function () {
var image = new Konva.Image({
x: Math.random() * this.misc.stage.getWidth(),
y: Math.random() * this.misc.stage.getHeight(),
width: 100,
height: 100,
image: imageObj,
draggable: true
});
this.misc.layer.add(image);
this.misc.layer.draw();
};
}
}
As Oblosys said, you generally don't want to try to do this in React. If you must, go with the ref route. It's there for a reason.
But I would suggest considering lifting the images up to your App state and passing them down as props. It's hard to say what this would look like as you have not provided an implementation for the Map component, but it would look something like this.
class App extends Component {
constructor() {
super();
this.state = { images: [] };
this.addImage = this.addImage.bind(this);
}
addImage() {
const newImage = 5; // I obviously have no idea what this actually looks like
this.setState(({ images }) => ({ images: [...images, newImage] }));
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<p>
<Map images={this.state.images} />
</p>
<button onClick={this.addImage}>Add an image</button>
</div>
);
}
}
This won't work well in all cases (for example, if you're using a third party tool in Map), but this is more The React Way.
Related
I'm still new to React.
I'm making a guessing game. On page load, everything loads properly (on Chrome and Safari, at least). The cat buttons are assigned a random number and when clicked, they send the corresponding value to the game logic. When the target number is met or exceeded, the target number resets.
That's what I want, but I also want the buttons to get new values. I want the Buttons component to reload and assign the buttons new values. I've tried using the updating methods found here: https://reactjs.org/docs/react-component.html#updating. I don't know what to do next.
App.js
import React, { Component } from 'react';
import './App.css';
import Buttons from "./components/Buttons/Buttons";
class App extends Component {
targetNumber = (min, max) => {
const targetNum = Math.floor(Math.random()*(max-min+1)+min);
console.log(`Target number = ${targetNum}`);
return targetNum
};
state = {
targetNumber: this.targetNumber(19, 120),
currentValue: 0,
gamesWon: 0,
};
handleClick = (event) => {
event.preventDefault();
const currentValue = this.state.currentValue;
const newValue = parseInt(event.target.getAttribute("value"));
this.setState(
{currentValue: currentValue + newValue}
)
// console.log(newValue);
}
componentDidUpdate() {
if (this.state.currentValue === this.state.targetNumber) {
this.setState(
{
targetNumber: this.targetNumber(19, 120),
currentValue: 0,
gamesWon: this.state.gamesWon + 1
}
)
}
else {
if (this.state.currentValue >= this.state.targetNumber) {
this.setState(
{
targetNumber: this.targetNumber(19, 120),
currentValue: 0,
gamesWon: this.state.gamesWon,
}
);
}
}
}
render() {
return (
<div className="App">
<img src={require("./images/frame.png")} alt="frame" id="instructFrame" />
<div className="resultsDiv">
<div className="targetNumber">
Target number = {this.state.targetNumber}
</div>
<div className="currentValue">
Current value = {this.state.currentValue}
</div>
<div className="gamesWon">
Games won = {this.state.gamesWon}
</div>
</div>
<div className="buttonGrid">
<Buttons
onClick={this.handleClick}
/>
</div>
</div>
);
}
}
export default App;
Buttons.js
import React, { Component } from "react";
import Button from "../Button/Button";
import black from "../Button/images/black_cat.png";
import brown from "../Button/images/brown_cat.png";
import gray from "../Button/images/gray_cat.png";
import yellow from "../Button/images/yellow_cat.png";
class Buttons extends Component {
generateNumber = (min, max) => {
const rndNumBtn = Math.floor(Math.random()*(max-min+1)+min);
console.log(rndNumBtn);
return rndNumBtn
};
state = {
buttons: [
{
id: "black",
src: black,
alt: "blackBtn",
value: this.generateNumber(1, 12)
},
{
id: "brown",
src: brown,
alt: "brownBtn",
value: this.generateNumber(1, 12)
},
{
id: "gray",
src: gray,
alt: "grayBtn",
value: this.generateNumber(1, 12)
},
{
id: "yellow",
src: yellow,
alt: "yellowBtn",
value: this.generateNumber(1, 12)
}
]
};
render() {
return (
<div>
{this.state.buttons.map(button => {
return (
<Button
className={button.id}
key={button.id}
src={button.src}
alt={button.alt}
value={button.value}
onClick={this.props.onClick.bind(this)}
/>
)
})}
</div>
)
}
}
export default Buttons;
Here's the GitHub repo. https://github.com/irene-rojas/numberguess-react
You can add a key to the Button component linking to the variable targetNumber. That way, React would rerender the Button whenever targetNumber changes.
<div className="buttonGrid">
<Buttons
key={this.state.targetNumber}
onClick={this.handleClick}
/>
</div>
I am currently converting a RGB color guessing game I made in vanilla HTML, CSS, and JavaScript into React.
When I click on one of the six divs with the class coloredSquare, I want it to grab the backgroundColor of that square and compare it to the rgb color displayed on the screen, coming from the element with the mainColor id.
In vanilla JS it is so simple, you just do this.style.backgroundColor inside of the eventListener but for some reason with React I cannot figure it out. I feel really dumb and I am probably overthinking it and it's actually really simple.
Here's the code:
import React, {Component} from "react";
class RGBGuesser extends Component {
constructor(){
super();
this.state = {
correctCount: 0,
displayCorrect: 0,
colors: "",
chosenResult: "",
chosenCorrect: 0,
}
}
componentDidMount = () => {
this.startGame();
}
initialGameState = () => {
this.setState({
colors: this.displayRandom(6)
})
}
restart = () => {
this.initialGameState();
this.setState({
chosenResult: "",
chosenCorrect: 0,
displayCorrect: 0
})
}
pickSquare = () => {
let colorRan = Math.floor(Math.random() * this.state.colors.length);
return this.state.colors[colorRan]
}
displayRandom = amountSquares => {
const colorArr = [];
for(let i = 0; i < amountSquares; i++){
colorArr.push(this.chooseRandom());
}
return colorArr;
}
chooseRandom = () => {
let rColor = Math.floor(Math.random() * 256);
let gColor = Math.floor(Math.random() * 256);
let bColor = Math.floor(Math.random() * 256);
return `rgb(${rColor}, ${gColor}, ${bColor})`;
}
chooseSquare = () => {
//where i would want to do the logic of clicking the square and comparing it with the rgb color displayed on screen
}
startGame = () => {
this.initialGameState();
this.restart();
}
render(){
let correctColor = this.pickSquare();
return(
<div>
<h1 id="header">RGB Color Guesser</h1>
<h3 id="mainColor">{correctColor}</h3>
<h3 id="result"></h3>
<h3 id="showCorrect">Number Correct: <span id="correctCount">0</span></h3>
<button id="startOver" onClick={this.restart}>Start Over</button>
<div id="colorGrid">
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[0]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[1]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[2]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[3]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[4]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[5]}}></div>
</div>
</div>
)
}
}
export default RGBGuesser;
chooseSquare = (e) => {
console.log(e.currentTarget.style.background)
}
I think passing the event into your event handler and currentTarget \ target is what you're missing
Also don't forget to bind your event handler in your constructor!
constructor() {
// snip
this.chooseSquare = this.chooseSquare.bind(this);
}
I have several pictures and onPress I want to make tapped picture bigger(on focus) make background dark with opacity like this.
If I will tap on this picture I wanna do roughly same thing but on mobile (react-native).
I believe you need to zoom the image from center and not from the top left. You can use this approach, and here is the pen for it: Codepen Link
class Profile extends React.Component {
constructor(props){
super(props);
this.state = {
height: '100%',
width: '100%',
flag: 0
};
}
render() {
var { pic } = this.props;
return (
<div>
<img src={pic} id="myImage" height={this.state.height} width={this.state.width} onClick={this.zoomHandler.bind(this)}/>
</div>
);
}
zoomHandler()
{
if(this.state.flag === 0)
{
document.getElementById("myImage").style.transform = "scale(2,2)";
this.setState({flag: 1});
}
else
{
document.getElementById("myImage").style.transform = "scale(1,1)";
this.setState({flag: 0});
}
}
}
React.render(
<Profile
pic="https://www.diariogol.com/uploads/s1/55/38/91/7/messi-leo-getafe-camp-nou_15_970x597.jpeg" />,
document.getElementById('app')
);
I have used states for height and weight, which can also be used for the zoom effect.
Use:
- Scale animation with Animated Component,
Example:
state = {
zoomImage: 1
}
zoomIn=()=>{
Animated.timing(this.state.zoomImage, {
toValue: 2,
duration: 2000,
userNativeDriver: true
})
}
render():
const animatedStyle = {
transform: [
{
scale: this.state.zoomImage
}
]
}
<TouchableOpacity onPress={() => this.zoomIn}>
<Image style={animatedStyle}/>
</TouchableOpacity>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Refer: https://medium.com/react-native-training/react-native-animations-using-the-animated-api-ebe8e0669fae
Edit: I created wrappers for each different component, required the necessary collections and passed them as props to a main, generic component.
We made 3 very similar components using React, and ended up having three really similar files with some minor modifications, depending on the specific component.
My question is really simple (but might be hard to implement), we want to utilize the same file for that base, so i want to dynamically use requires, and initialize variables, how can i do it?
var ProtocolSelectedCollection = require("../collections/ProtocolSelectedCollection");
var selectedCollection = new ProtocolSelectedCollection();
var baseURL = Utils.getSystemUrl();
easyGrid({
gridName: "protocolSelectedCollection"
});
In the code above, for example, i would like to require a different file from /collections/, depending on the component, and i would also like to name the gridName differently.
So, how can i do it? I will post more code if necessary or clear any doubts about it.
So here is the full component, it's really small.
var React = require('react');
const Utils = require("Utils");
var RowCt = require("reactor/src/FlexGrid/components/Layout/Row/Row");
var Col = require("reactor/src/FlexGrid/components/Layout/Col.jsx");
var Row = require("reactor/src/FlexGrid/components/Layout/Row/RowPresentation.jsx");
var Box = require("reactor/src/FlexGrid/components/Layout/Box.jsx");
var ContentLabel = require("reactor/src/Atomic/components/Mols/ContentLabel.jsx");
var AsyncLoading = require("reactor/src/Atomic/components/Helpers/AsyncLoading/AsyncLoading");
var Store = require("reactor/src/store/store");
var Provider = require("react-redux").Provider;
var FormControl = require('reactor/src/Form/components/Atoms/FormControl/FormControl');
var FormGroup = require("reactor/src/Form/components/Mols/FormGroup");
var InputAddon = require("reactor/src/Form/components/Atoms/InputAddon");
var InputGroup = require("reactor/src/Form/components/Mols/InputGroup/InputGroup.jsx");
var easyGrid = require("reactor/src/FlexGrid/components/Helpers/EasyGrid");
var when = require("when");
var ProtocolSelectedCollection = require("../collections/ProtocolSelectedCollection");
var selectedCollection = new ProtocolSelectedCollection();
var baseURL = Utils.getSystemUrl();
easyGrid({
gridName: "protocolSelectedCollection"
});
var cursorPointer = {
cursor: 'pointer'
};
var rowWrapper = {
borderWidth: '0px 0px 1px',
borderStyle: 'solid',
borderColor: 'rgb(204, 204, 204)',
marginBottom: '10px',
cursor: 'pointer'
};
require("./ProtocolAuthorizedWidget.css");
require("reactorCmps/tokens/general");
module.exports = React.createClass({
componentDidMount() {
var me = this;
return me.loadData();
},
filterValue: "",
loadData() {
var me = this;
return me.refs.async.getWrappedInstance().loadData(true);
},
getChildren(data) {
var me = this, protocolsData = Array.isArray(data) ? data : [], protocols = [];
if (!protocolsData.length) {
return (
<span>{SE.t(103092)}</span>
);
}
protocolsData.map(function(element, i) {
protocols.push(
<div style={rowWrapper}
key={i}
onMouseLeave={me.setRowState.bind(me, element.cdproctype, 'leave')}
onMouseOver={me.setRowState.bind(me, element.cdproctype, 'over')}
onClick={me.startProtocol.bind(me, element.cdproctype)}
title={SE.t(104859)}>
<RowCt
oid={element.cdproctype}
selectType={0}
multireducerKey={"protocolSelectedCollection"}>
<Col xs sm md lg>
<Row nested>
<Col xs={1} sm={1} md={1} lg={1} >
<div ref={"protocol" + element.cdproctype}></div>
<img ref={"protocolImage" + element.cdproctype}
src={baseURL + "/common/images/type_icons/64x64/" + element.fgicon + ".png"}
className="rowImage" />
</Col>
<Col xs={11} sm={11} md={11} lg={11}>
<Box>
<ContentLabel title={element.idproctype} text={element.nmtitle}></ContentLabel>
</Box>
</Col>
</Row>
</Col>
</RowCt>
</div>
);
});
return (
<div>
<div>
{protocols}
</div>
</div>
);
},
startProtocol(cdproctype) {
var url = baseURL + "/document/dc_protocol/protocol_data.php?caption=&action=1&cdproctype=" + cdproctype;
var width = 700;
var height = 515;
Utils.openPopUp(url, width, height);
},
setRowState(cdproctype, event) {
var me = this;
if (event === 'over') {
$(me.refs.async.getWrappedInstance().refs['protocolImage' + cdproctype]).hide();
$(me.refs.async.getWrappedInstance().refs['protocol' + cdproctype]).addClass("se-valign btn btn-success seicon-play playButton rowImage");
} else if (event === 'leave') {
$(me.refs.async.getWrappedInstance().refs['protocolImage' + cdproctype]).show();
$(me.refs.async.getWrappedInstance().refs['protocol' + cdproctype]).removeClass();
}
},
filterProtocol(e) {
var me = this;
var enterKeyCode = 13;
if (e.target) {
me.filterValue = e.target.value;
}
if (e.nativeEvent.keyCode === enterKeyCode) {
me.loadData();
}
},
getData() {
var me = this, deferred = when.defer();
var oid = me.props.CardOid;
var searchTerm = me.filterValue;
selectedCollection.fetch({oid: oid, searchTerm: searchTerm}).then(function(results) {
deferred.resolve(results);
});
return deferred.promise;
},
render() {
var me = this, searchFilter;
searchFilter = (
<div>
<div>
<FormGroup>
<InputGroup>
<InputAddon
onClick={me.loadData}
style={cursorPointer}
className="seicon-search"
title={SE.t(214653)}
>
</InputAddon>
<div>
<FormControl
onKeyPress={me.filterProtocol}
/>
</div>
</InputGroup>
</FormGroup>
</div>
</div>
);
return (
<div>
<div>
{searchFilter}
</div>
<Provider store={Store} withRef>
<AsyncLoading
ref="async"
oid={"protocolSelectedCollection" + me.props.CardOid}
fireLoad
loadFn={me.getData}
getChildren={me.getChildren}
/>
</Provider>
</div>
);
}
});
If your components are really similar,you can create one single component which does some actions and renders some result based on props.
Just pass some props. And inside of your component, do your actions like you do in those 3 component separately. Here, i tried to demonstrate how to use props to render different results from same component:
class MixedComponent extends React.Component {
constructor(props) {
super(props)
this.state = {type: ''}
}
componentDidMount() {
if (this.props.prop1) // if prop1 exists
this.setState({type: 'do something'})
else
this.setState({type: 'Something else'})
}
render() {
let result;
if (this.props.prop1) {
result = (
<div>
Render this component based on <strong>{this.props.prop1}</strong>
<p>Type -> {this.state.type}</p>
</div>
)
} else if (this.props.prop2) {
result = (
<div>
Render this component based on <strong>{this.props.prop2}</strong>
<p>Type -> {this.state.type}</p>
</div>
)
}
else if (this.props.prop3) {
result = (
<div>
Render this component based on <strong>{this.props.prop3}</strong>
<p>Type -> {this.state.type}</p>
</div>
)
}
return result
}
}
And use this component with different props in your main component:
class Main extends React.Component {
render() {
return (
<div>
<h4>Render different results based on props with same component!</h4>
<hr/>
<MixedComponent prop1="Hello,"/>
<MixedComponent prop2="Dear"/>
<MixedComponent prop3="World!"/>
</div>
)
}
}
This is just a demonstration of using props. You can derive the idea from here. Here is a working example on codepen:
https://codepen.io/anon/pen/dREqZK?editors=1010
You can play with it.
for the past few hours I have been trying to search for a way to make a react component draggable and resizable. I have found a way to make it draggable with react drag and drop, but I can't find a simple way to make it resizeable :/
Does anyone have any experience on how to make a component draggable and resizable?
Any help or pointers are appreciated!
Using react-grid-layout to solve this problem. Specifically for a scheduling widget where users can scale time blocks and drag and drop them along a horizontal timeline.
react-grid-layout provides a grid with draggable and resizable widgets plus responsive layout, optional auto-packing, among other features.
var ReactGridLayout = require('react-grid-layout');
// React component render function:
render: function() {
return (
<ReactGridLayout className="layout" cols={12} rowHeight={30}>
<div key={1} _grid={{x: 0, y: 0, w: 1, h: 2}}>1</div>
<div key={2} _grid={{x: 1, y: 0, w: 1, h: 2}}>2</div>
<div key={3} _grid={{x: 2, y: 0, w: 1, h: 2}}>3</div>
</ReactGridLayout>
)
}
The child nodes are draggable and resizable. The layout defined in each child "_grid" prop can alternatively be defined directly on the parent "layout" prop:
// React component render function:
render: function() {
// layout is an array of objects, see the demo
var layout = getOrGenerateLayout();
return (
<ReactGridLayout className="layout" layout={layout} cols={12} rowHeight={30}>
<div key={1}>1</div>
<div key={2}>2</div>
<div key={3}>3</div>
</ReactGridLayout>
)
}
Callback functions can be passed to the components as props. Hooking into these should allow you to define any custom behavior:
// Calls when drag starts.
onDragStart: React.PropTypes.func,
// Calls on each drag movement.
onDrag: React.PropTypes.func,
// Calls when drag is complete.
onDragStop: React.PropTypes.func,
// Calls when resize starts.
onResizeStart: React.PropTypes.func,
// Calls when resize movement happens.
onResize: React.PropTypes.func,
// Calls when resize is complete.
onResizeStop: React.PropTypes.func
Code sample from docs:
https://github.com/STRML/react-grid-layout
Demo here:
https://strml.github.io/react-grid-layout/examples/0-showcase.html
I've been using react-rnd and am really happy with it: https://github.com/bokuweb/react-rnd
https://github.com/STRML/react-resizable
This answer is only for resizable component. You can find other answer which has both functionalities.
'use strict';
var React = require('react/addons');
typeof window !== "undefined" && (window.React = React); // for devtools
typeof window !== "undefined" && (window.Perf = React.addons.Perf); // for devtools
var _ = require('lodash');
var ResizableBox = require('../lib/ResizableBox.jsx');
var Resizable = require('../lib/Resizable.jsx');
require('style!css!../css/styles.css');
var TestLayout = module.exports = React.createClass({
displayName: 'TestLayout',
getInitialState() {
return {width: 200, height: 200};
},
onClick() {
this.setState({width: 200, height: 200})
},
onResize(event, {element, size}) {
this.setState({width: size.width, height: size.height});
},
render() {
return (
<div>
<button onClick={this.onClick} style={{'marginBottom': '10px'}}>Reset first element's width/height</button>
<Resizable className="box" height={this.state.height} width={this.state.width} onResize={this.onResize}>
<div className="box" style={{width: this.state.width + 'px', height: this.state.height + 'px'}}>
<span className="text">{"Raw use of <Resizable> element. 200x200, no constraints."}</span>
</div>
</Resizable>
<ResizableBox className="box" width={200} height={200}>
<span className="text">{"<ResizableBox>, same as above."}</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} draggableOpts={{grid: [25, 25]}}>
<span className="text">Resizable box that snaps to even intervals of 25px.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} minConstraints={[150, 150]} maxConstraints={[500, 300]}>
<span className="text">Resizable box, starting at 200x200. Min size is 150x150, max is 500x300.</span>
</ResizableBox>
<ResizableBox className="box box3" width={200} height={200} minConstraints={[150, 150]} maxConstraints={[500, 300]}>
<span className="text">Resizable box with a handle that only appears on hover.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} lockAspectRatio={true}>
<span className="text">Resizable square with a locked aspect ratio.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={120} lockAspectRatio={true}>
<span className="text">Resizable rectangle with a locked aspect ratio.</span>
</ResizableBox>
</div>
);
}
});