React and Flux - how to tie view to store data - javascript

I started learning React with Flux and I am having some trouble.
I have a store and two views (components) and I would like to update one view when the store data changes, but I'm not sure how to tie the two together.
What happens is this:
I click a button in view 1, this modifies data in the store, then this should update view two.
view1 onClick => dispatch action => modify store data => update view2
I have everything except the last part, tying the view and the store when changes are made to the store. All this currently does is modify a class name, but I can see other functions for it later on.
So my question is, how can I tie the store to the state of the view?
view2 (Homepage)
var React = require('react');
var DefaultLayout = React.createFactory(require('../layouts/Default'));
var ReactGridLayout = React.createFactory(require('react-grid-layout'));
var desktopStore = require("../stores/DesktopStore");
// var classNames = require('classnames');
var HomePage = React.createClass({
displayName: 'Index.jsx',
getInitialState: function(){
return {zoomed: desktopStore.get('zoom')};
},
getDefaultProps: function() {
return {
layout: DefaultLayout
};
},
render: function() {
var parentClassString = "desktop"; // base class
if(this.state.zoomed){
parentClassString += " zoomed"; // add a class based on the zoomed property
}
return (
<div className={parentClassString}>
<ReactGridLayout className="layout" cols={80} rowHeight={30} verticalCompact={false}
initialWidth={10} autoSize={false} isResizable={false}>
<div key={1} _grid={{x: 0, y: 0, w: 1, h: 1}} >1</div>
<div key={2} _grid={{x: 0, y: 0, w: 1, h: 1}} >2</div>
<div key={3} _grid={{x: 0, y: 0, w: 1, h: 1}} >3</div>
<div key={4} _grid={{x: 0, y: 0, w: 1, h: 1}} >4</div>
<div key={5} _grid={{x: 0, y: 0, w: 1, h: 1}} >5</div>
<div key={6} _grid={{x: 0, y: 0, w: 1, h: 1}} >6</div>
</ReactGridLayout>
</div>
);
}
});
module.exports = HomePage;
View1 (Header)
var _name = 'TopHeader.jsx';
var React = require('react');
var DesktopApi = require('../utilities/DesktopApi');
var TopHeader = React.createClass({
displayName: _name,
handleClick: function(){
DesktopApi.toggleZoom(); // this goes to the dispatcher and emits a change to update the desktopStore
},
render() {
return (
<div className="top-header">
<span>Header</span>
<span className="plus" onClick={this.handleClick}> [+] </span>
</div>
);
}
});
module.exports = TopHeader;

The official facebook guide helped out here, I can create listeners with componentDidMount and remove them with componentWillUnmount
componentDidMount: function() {
desktopStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
desktopStore.removeChangeListener(this._onChange);
},
_onChange: function(){
this.setState({zoom: true});
}

If you want to create a mixin to bind the views to stores, you can read fluxxor's source. Here's the sample of storeWatchMixin. Or you can use higher order component to wrap your component.

Related

Gridstack.js + Vue 3 components

I'm trying to create a gridstack.js dashboard with Vue 3 and I want the grid stack items to contain reactive vue 3 components.
The problem is that these grid stack items can only be passed HTML. The documentation states you should be able to add Vue components as content but the examples are for Vue 2 and I'm struggling to implement this in Vue 3.
I have the following code:
<template>
<div class="p-6 h-full flex flex-col">
<header class="flex flex-row items-center justify-between mb-6">
<div>
<h1 class="text-3xl font-bold">
Workbench
</h1>
<p class="leading-6 text-gray-600 text-sm mt-2">
{{ info }}
</p>
</div>
<div class="flex flex-row items-center">
<button type="button" #click="addPanel()">Add Panel</button>
</div>
</header>
<div class="flex-1">
<section class="grid-stack"></section>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, defineComponent, createApp } from "vue"
import TestPanel from "./../components/panels/TestPanel.vue"
let grid = null;
const items = [
{ x: 0, y: 0, h: 4, w: 6 },
{ x: 7, y: 0, h: 4, w: 6 },
{ x: 0, y: 5, h: 4, w: 4 },
{ x: 4, y: 5, h: 4, w: 4 },
{ x: 8, y: 5, h: 4, w: 4 },
];
onMounted(() => {
grid = GridStack.init({
// float: true,
cellHeight: "70px",
minRow: 1,
});
grid.load(items)
});
function addPanel() {
const div = document.createElement("div")
div.id = Math.random().toString(24).substring(8)
const componentInstance = defineComponent({
extends: TestPanel, data() {
return {
test: "this is a test"
}
}
})
const app = createApp(componentInstance)
app.mount(div)
let widget = grid.addWidget({
x: 0,
y: 0,
w: 6,
h: 3,
content: div.outerHTML,
})
app.mount(div.id)
}
</script>
<style>
.grid-stack-item-content {
background-color: #18BC9C;
}
</style>
This will load the vue component in a stack grid item but the component is no longer reactive.
Any help would be greatly appreciated, thanks in advance!
This is probably not exactly what the gridstack-creators had in mind but here you go:
<template>
<button #click="addNewWidget()">Add Widget</button> {{ info }}
<section class="grid-stack">
<div
v-for="(component, key, index) in components"
:key="'component'+index"
:gs-id="key"
class="grid-stack-item"
:gs-x="component.gridPos.x"
:gs-y="component.gridPos.y"
:gs-h="component.gridPos.h"
:gs-w="component.gridPos.w"
gs-auto-position="true"
>
<div class="grid-stack-item-content">
<component :is="component.name" v-bind="component.props" />
</div>
</div>
</section>
</template>
<script>
import { ref, onMounted, reactive, nextTick } from 'vue';
import 'gridstack/dist/gridstack.min.css';
import { GridStack } from 'gridstack';
import YourRandomComponent1 from '../YourRandomComponent1.vue';
import YourRandomComponent2 from '../YourRandomComponent2.vue';
import YourRandomComponent3 from '../YourRandomComponent3.vue';
export default {
name: "WidgetGrid",
setup() {
let info = ref("");
let grid = null;
let components = reactive({
yourRandomComponent1: {
name: "YourRandomComponent1", props: {}, gridPos: { x: 0, y: 1, w: 4, h: 5 }
},
yourRandomComponent2: {
name: "YourRandomComponent2", props: {}, gridPos: { x: 0, y: 1, w: 2, h: 5 }
},
});
onMounted(() => {
grid = GridStack.init({
float: true,
cellHeight: "70px",
minRow: 1,
});
grid.on("dragstop", (event, element) => {
console.log("move event!", event, element);
const node = element.gridstackNode;
info.value = `you just dragged node #${node.id} to ${node.x},${node.y} – good job!`;
});
});
// this will of course only work once because of the object-key
function addNewWidget() {
components.yourRandomComponent3= {
name: "YourRandomComponent3", props: {}, gridPos: { x: 0, y: 1, w: 2, h: 5 }
};
// we have to wait for vue to update v-for,
// until then the querySelector wont find the element
nextTick(() => {
console.log(grid);
let compEl = document.querySelector('[gs-id="yourRandomComponent3"]');
console.log(compEl);
grid.makeWidget(compEl);
});
console.warn("i will only work once, fix my inputs to reuse me");
}
return {
info,
components,
};
},
components: {
// eslint-disable-next-line vue/no-unused-components
YourRandomComponent1,
// eslint-disable-next-line vue/no-unused-components
YourRandomComponent2,
},
}
</script>
<style>
.grid-stack {
background-color: #FAFAFF;
border-style: dashed;
}
.grid-stack-item {
color: #2c3e50;
text-align: center;
border-style: solid;
overflow: auto;
z-index: 50;
}
</style>
In my case, a missing div with the grid-stack-item-content-class wrapping the component made the widgets immobile.
I have also added an add-new-widget function that demonstrates how to add a new widget to the grid. The key is to use reactive() so that Vue will re-render the page. After re-rendering, the component needs to be registered as a grid element using grid.makeWidget. For this we need the component's Dom element, which we get after Vue has re-rendered with nextTick.
You can use own component in Vue3 like this
<div class="grid-stack" :style="{ 'background-color': hex }" >
<widget v-for="widget in widgets" :widget="widget" :key="widget" />
</div>
Import your component
import Widget from "src/components/GridStackComponent.vue";
Add component to export
export default {
name: 'GridStack',
components: {
Widget
},
data() {
...
},
...
}
And that's all. Result can look like this

Attempting to render JSX element but getting undefined

So I have this JSX element that I am attempting to render in the Class component. It is essentially a visual provided by D3's React library. However, I am receiving this error upon attempting to render the D3 visual:
Unhandled Rejection (Error): Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Below are some relevant code snippets of where the error is occurring:
The builder function to pass all the necessary props to the D3 library
const buildModelVisual = (dataPoints) => {
console.log("category: " + dataPoints[0].category)
console.log("range: " + dataPoints[0].range)
console.log("frequency: " + dataPoints[0].frequency)
dataPoints[0].frequency.forEach(f =>
console.log("f: " + f)
)
const width = 960,
height = 500,
margins = {top: 50, right: 50, bottom: 50, left: 50},
id = "model-visual",
title = "NaiveBayes Model Visual",
svgClassName = "model-visual-class",
titleClassName = "model-visual-title-class",
legendClassName = "model-legend",
showLegend = true,
showXAxis = true,
showYAxis = true,
showXGrid = false,
showYGrid = false,
ranges = [
...dataPoints[0].range
],
frequencies = [
...dataPoints[0].frequency
],
x = () => {
return ranges.forEach(r => {
return r;
})
},
xOrient = 'bottom',
xTickOrient = 'bottom'
const xDomain = dataPoints[0].range.forEach(r => {
return {
category: dataPoints[0].category, range: r
}
}),
xRangeRoundBands = {interval: [0, width - margins.left - margins.right], padding: 0.1},
xScale = 'ordinal',
xAxisClassName = 'x-axis',
xLabel = dataPoints[0].category,
xLabelPosition = 'bottom',
xTickPadding = 3,
xInnerTickSize = 6,
xOuterTickSize = 6,
y = () => {
return frequencies.forEach(freqInRange => {
return freqInRange.forEach(f => {
return f;
});
})
},
yOrient = 'left',
yTickOrient = 'left',
yRange = [height - margins.top - margins.bottom, 0]
const yDomain = [0, d3.max(
dataPoints[0].frequency,
(f) => {
return f.value
}
)],
yScale = 'linear',
yAxisClassName = 'y-axis',
yLabel = "Population",
yTickFormat = d3.format(".2s"),
yLabelPosition = 'left',
yTickPadding = 4,
yInnerTickSize = 6,
yOuterTickSize = 6
return (
<Chart
title={title}
id={id}
width={width}
height={height}
>
<BarStackChart
title= {title}
data= {ranges}
width= {width}
height= {height}
id= {id}
margins= {margins}
svgClassName= {svgClassName}
titleClassName= {titleClassName}
yAxisClassName= {yAxisClassName}
xAxisClassName= {xAxisClassName}
legendClassName= {legendClassName}
categoricalColors= {d3.scaleOrdinal(d3.schemeCategory10)}
chartSeries = {ranges}
showLegend= {showLegend}
showXAxis= {showXAxis}
showYAxis= {showYAxis}
x= {x}
showXGrid= {showXGrid}
xDomain= {xDomain}
xRangeRoundBands= {xRangeRoundBands}
xScale= {xScale}
xOrient= {xOrient}
xTickOrient= {xTickOrient}
xTickPadding = {xTickPadding}
xInnerTickSize = {xInnerTickSize}
xOuterTickSize = {xOuterTickSize}
xLabel = {xLabel}
xLabelPosition = {xLabelPosition}
y= {y}
showYGrid= {showYGrid}
yOrient= {yOrient}
yRange= {yRange}
yDomain= {yDomain}
yScale= {yScale}
yTickOrient= {yTickOrient}
yTickPadding = {yTickPadding}
yInnerTickSize = {yInnerTickSize}
yOuterTickSize = {yOuterTickSize}
yTickFormat= {yTickFormat}
yLabel = {yLabel}
yLabelPosition = {yLabelPosition}
/>
</Chart>
)
}
The HO class component that is rendering the graph and the interface
constructor(props) {
super(props);
this.ref = React.createRef();
this.state = {
input: "",
output: [],
visual: null
}
}
REST API call (within the default class Component) that sets the data for the BarStackChart
callGetModel = () => {
let getModelRanges = getCall(ApiURL.get_model_ranges);
let getModelFrequencies = getCall(ApiURL.get_model_frequencies);
Promise.all([getModelRanges, getModelFrequencies]).then(data => {
this.setState({output: data})
const dataPoints = [];
for (let value in JSON.parse(data[0].toString())) {
dataPoints.push({
category: value,
range: JSON.parse(data[0].toString())[value],
frequency: JSON.parse(data[1].toString())[value]
})
}
console.log(dataPoints)
const ModelVisual = buildModelVisual(dataPoints)
this.setState({ visual: ModelVisual }) // returns JSX element
console.log(this.state.visual)
});
}
The render method for the class Component
render() {
return <div>
<h3>Welcome to Naive Bayes Java!</h3>
<p>The REST API for this project is hosted at</p>
<a style={{'display':'block', 'paddingBottom':'1.5em', 'color':'rgb(0, 150, 196)'}} href="https://naivebayesjava.herokuapp.com/swagger-ui.html#/">https://naivebayesjava.herokuapp.com/</a>
<button style={{'display':'inline', 'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callListOfFiles}>
Get List of Valid Files
</button>
<button style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callGetModel}>
Get Model
</button>
<button style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callGetModelAccuracy}>
Get Model Accuracy
</button>
<div style={{'margin':'auto', 'display':'block'}}>
<input style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} type='text' value={this.state.input} onChange={this.handleChange}/>
<button style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callSetData}>
Set Training Data File
</button>
</div>
{/* <button onClick={this.callGetTrainingData}>
Get Training Data Arff Files
</button> */}
<div style={{'padding-top':'0.5em'}} ref={this.ref}></div>
<output type='textBox' style={{ 'padding':'1.25em', 'display':'block', 'Height': '30px', 'Width': '300px' }}>
{ Object.keys(this.state.output).map(key => {
return this.state.output[key]
}) }
</output>
{ this.state.visual }
</div>
}
There most definitely is a better way to implement this besides setting a "this.state.visual" JSX Element and calling that in the render method, although as I am really new to both React (started learning about a month ago) and JS (started about 3 months ago) I don't quite know all the common practices with either; just the general theory behind how they work.
This interface and my portfolio are hosted at joshuabaroni.github.io . The interface i am attempting to improve is the NaiveBayes Project Interface under the "Projects" section
Any recommendations would be appreciated! the whole JS file is available upon request.
You likely forgot to export your component from the file it's defined
in, or you might have mixed up default and named imports.
You aren't exporting your classes/functions as it is required.
Exporting without default means it's a "named export". You can have multiple named exports in a single file. So if you do this,
class Template {}
class AnotherTemplate {}
export { Template, AnotherTemplate } // named export
then you have to import these exports using their exact names. So to use these components in another file you'd have to do,
import {Template, AnotherTemplate} from './components/templates'
Alternatively if you export as the default export like this,
export default class Template {}
Then in another file you import the default export without using the {}, like this,
import Template from './components/templates'
There can only be one default export per file. In React it's a convention to export one component from a file, and to export it is as the default export.
You're free to rename the default export as you import it,
import TheTemplate from './components/templates'
And you can import default and named exports at the same time,
import Template,{AnotherTemplate} from './components/templates'

React add Component as a variable

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.

React Native PanGesture set pan position from tap

I'm currently working on an HSV-Colorpicker component for a react-native project I have. I have everything setup so that I have a draggable object on the HSV-canvas and a slider next to it to handle the hue change.
However, I now want to be able to set the position of the draggable object by clicking on the hsv-canvas. I got it working so that the draggable element moves to the right position, however, when I then try to drag the element with a pan-gesture again, the offset for the pan-gesture starts from the wrong position.
Here is the corresponding parts of code:
setPanFromTouch(event) {
const x = event.nativeEvent.locationX;
const y = event.nativeEvent.locationY;
const gesture = {
moveX: x,
moveY: y
};
this.state.pan.setOffset({ x: x, y: y });
this.state.pan.setValue({ x: 0, y: 0 });
this.updateColorFromGesture((gesture as any));
}
//inside the render method those are the 2 components that respond to
//touches and pans
//Parent container for the HSV-hue and the draggable element
<TouchableWithoutFeedback
style={{
width: this.props.colorBoxWidth,
height: this.props.height
}}
onPress={this.setPanFromTouch}
>
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), {
height: this.draggableSize,
width: this.draggableSize,
borderRadius: this.draggableSize / 2,
backgroundColor: 'transparent',
borderColor: '#fff',
borderWidth: 1
}]}>
</Animated.View>
//pan-gesture setup inside the constructor method
(this as any).panResponder = PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.state.pan.setOffset({ x: this.state.panValues.x, y: this.state.panValues.y });
this.state.pan.setValue({ x: 0, y: 0 });
},
onPanResponderMove: Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y
}]),
onPanResponderRelease: (e: GestureResponderEvent, gesture: PanResponderGestureState) => {
this.state.pan.flattenOffset();
this.updateColorFromGesture(gesture);
}
});
//component will mount code
componentWillMount() {
this.state.pan.addListener((c) => this.setState({
...this.state,
panValues: c
}));
}
I know that inside the onPanResponderGrant I am resetting the offset again, however this is necessary for the dragging to work correctly. I also tried to set the new values for this.state.panValues.x and this.state.panValues.y after updating the pan position from the touch event but setState does not change the behavior at all.
This is what the colorpicker looks like:
I would be really grateful if someone has an idea what I could do in this situation. Let me know if you need more code or anything else. Thanks in advance :)
I solved the issue by using a normal instance variable instead of the state for detecting if a pan position was set by a touch. Here is what I did:
//At the top of my react component
private setFromTouch = false;
//in the onPanResponderGrant method of the PanResponder.create inside
//the constructor
...
onPanResponderGrant: () => {
if (!this.setFromTouch) {
this.state.pan.setOffset({ x: this.state.panValues.x, y:
this.state.panValues.y });
} else {
this.setFromTouch = false;
}
this.state.pan.setValue({ x: 0, y: 0 });
}
...
//New setFromTouch() method
setPanFromTouch(event) {
const x = event.nativeEvent.locationX;
const y = event.nativeEvent.locationY;
const gesture = {
moveX: x,
moveY: y
};
this.state.pan.setOffset({ x: x, y: y });
this.state.pan.setValue({ x: 0, y: 0 });
this.setFromTouch = true;
this.updateColorFromGesture((gesture as any));
}

reactjs draggable and resizeable component

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>
);
}
});

Categories