How to display the graph after it is stabilized (vis.js)? - reactjs

I am rendering a graph using implementation of vis network as shown here. Right now the graph is taking some time to stabilize after being rendered. But I want the network to be stabilized before being displayed. I tried using the stabilization options under the physics module, but I could not achieve it.
The following is my Graph Component.
import {default as React, Component} from 'react';
import isEqual from 'lodash/isEqual';
import differenceWith from 'lodash/differenceWith';
import vis from 'vis';
import uuid from 'uuid';
import PropTypes from 'prop-types';
class Graph extends Component {
constructor(props) {
super(props);
const {identifier} = props;
this.updateGraph = this.updateGraph.bind(this);
this.state = {
identifier : identifier !== undefined ? identifier : uuid.v4()
};
}
componentDidMount() {
this.edges = new vis.DataSet();
this.edges.add(this.props.graph.edges);
this.nodes = new vis.DataSet();
this.nodes.add(this.props.graph.nodes);
this.updateGraph();
}
shouldComponentUpdate(nextProps, nextState) {
let nodesChange = !isEqual(this.nodes.get(), nextProps.graph.nodes);
let edgesChange = !isEqual(this.edges.get(), nextProps.graph.edges);
let optionsChange = !isEqual(this.props.options, nextProps.options);
let eventsChange = !isEqual(this.props.events, nextProps.events);
if (nodesChange) {
const idIsEqual = (n1, n2) => n1.id === n2.id;
const nodesRemoved = differenceWith(this.nodes.get(), nextProps.graph.nodes, idIsEqual);
const nodesAdded = differenceWith(nextProps.graph.nodes, this.nodes.get(), idIsEqual);
const nodesChanged = differenceWith(differenceWith(nextProps.graph.nodes, this.nodes.get(), isEqual), nodesAdded);
this.patchNodes({nodesRemoved, nodesAdded, nodesChanged});
}
if (edgesChange) {
const edgesRemoved = differenceWith(this.edges.get(), nextProps.graph.edges, isEqual);
const edgesAdded = differenceWith(nextProps.graph.edges, this.edges.get(), isEqual);
this.patchEdges({edgesRemoved, edgesAdded});
}
if (optionsChange) {
this.Network.setOptions(nextProps.options);
}
if (eventsChange) {
let events = this.props.events || {}
for (let eventName of Object.keys(events))
this.Network.off (eventName, events[eventName])
events = nextProps.events || {}
for (let eventName of Object.keys(events))
this.Network.on (eventName, events[eventName])
}
return false;
}
componentDidUpdate() {
this.updateGraph();
}
patchEdges({edgesRemoved, edgesAdded}) {
this.edges.remove(edgesRemoved);
this.edges.add(edgesAdded);
}
patchNodes({nodesRemoved, nodesAdded, nodesChanged}) {
this.nodes.remove(nodesRemoved);
this.nodes.add(nodesAdded);
this.nodes.update(nodesChanged);
}
updateGraph() {
let options = this.props.options;
this.Network = new vis.Network(
this.refs.nw,
Object.assign(
{},
this.props.graph,
{
edges: this.edges,
nodes: this.nodes
}
),
options
);
if (this.props.getNetwork) {
this.props.getNetwork(this.Network)
}
// Add user provided events to network
let events = this.props.events || {};
for (let eventName of Object.keys(events)) {
this.Network.on(eventName, events[eventName]);
}
}
render(){
return (<div ref="nw" style={{width:'100%' , height: '480px'}}/>);
}
}
Graph.defaultProps = {
graph: {},
style: { width: '100%', height: '480px' }
};
Graph.propTypes = {
graph: PropTypes.object,
style: PropTypes.object,
getNetwork: PropTypes.func
};
export default Graph;
This is my options object
let options = {
layout: {
hierarchical: false
},
autoResize: false,
edges: {
smooth: false,
color: '#000000',
width: 0.5,
arrows: {
to: {
enabled: true,
scaleFactor: 0.5
}
}
}
};
Any help would be greatly appreciated.
Thanks in advance !

You mention that you tried enabling stabilization without success. The following in the options should work:
physics: {
stabilization: {
enabled: true,
iterations: 5000 // YMMV
}
}
Is this different from what you tried?

Related

How to properly setup Azure Media Player in React.js?

I'm currently integrating a React component with Azure Media Player. I followed the documentation and first, I added the required CDN urls to the index.html file. Then I added the sample code into the App. The problem is, it throws the error 'amp' is not defined no-undef
videoPlayer.js
class videoPlayer extends Component {
render () {
const myOptions = {
"nativeControlsForTouch": false,
controls: true,
autoplay: true,
width: "640",
height: "400",
}
const myPlayer = amp("azuremediaplayer", myOptions);
myPlayer.src([
{
"src": "https://devpflmedia-uswe.streaming.media.azure.net//d5f1a8b6-0d52-4e62-addc-aee7bafe408d/097cee43-6822-49bd-84f5-9f6efb05.ism/manifest",
"type": "application/vnd.ms-sstr+xml"
}
]);
return (
<video id="azuremediaplayer" class="azuremediaplayer amp-default-skin amp-big-play-centered" tabindex="0"></video>
)
}
}
How can I fix this?
When I use the amp this way, the mentioned on.progress callback works for me. Good luck!
import * as React from "react"
import loader from "./loader";
import { RefObject } from "react";
import './videoPlayer.css';
const DEFAULT_SKIN = "amp-flush";
const DEFAULT_RATIO = [16, 9];
const DEFAULT_OPTIONS = {
controls: true,
autoplay: true,
muted: true,
logo: {
enabled: false
},
};
declare const window: any;
export interface IVideoPlayerProps {
readonly src: { src: string; }[];
readonly options: any;
readonly skin: string;
readonly className: string;
readonly adaptRatio: Array<number>;
}
export default class VideoPlayer extends React.PureComponent<IVideoPlayerProps, {}> {
public static defaultProps = {
skin: DEFAULT_SKIN,
className: "",
adaptRatio: DEFAULT_RATIO,
options: DEFAULT_OPTIONS,
}
videoNode: RefObject<any>;
player: any;
initialization: any;
constructor(props: IVideoPlayerProps) {
super(props);
this.videoNode = React.createRef();
}
componentWillUnmount() {
this._destroyPlayer();
}
componentDidMount() {
const { skin } = this.props;
this.initialization = loader(skin).then(() => {
this._createPlayer();
this._setVideo();
});
}
componentDidUpdate(prevProps: IVideoPlayerProps) {
if (prevProps.src !== this.props.src) {
this.initialization.then(() => this._setVideo());
}
}
_destroyPlayer() {
this.player && this.player.dispose();
}
_setVideo() {
const { src } = this.props;
this.player.src(src);
}
_createPlayer() {
this.player = window.amp(this.videoNode.current, this.props.options);
this.player.on("progress", () => alert('on progress called'));
}
render(): JSX.Element {
return (
<div>
<video
ref={this.videoNode}
/>
</div>
);
}
}
Also the loader function - I use it this way since I may need to use the player in the (possible) offline environment.
export default (skin = 'amp-flush') => {
return new Promise((resolve, _) => {
if (document.querySelector('#amp-azure')) {
// video player is already rendered
return resolve()
}
let scriptTag = document.createElement('script')
let linkTag = document.createElement('link')
linkTag.rel = 'stylesheet'
scriptTag.id = 'amp-azure'
scriptTag.src = '//amp.azure.net/libs/amp/2.1.5/azuremediaplayer.min.js'
linkTag.href = `//amp.azure.net/libs/amp/2.1.5/skins/${skin}/azuremediaplayer.min.css`
document.body.appendChild(scriptTag)
document.head.insertBefore(linkTag, document.head.firstChild)
scriptTag.onload = () => resolve({ skin: skin })
})
}

How to execute a function when some item renders in react native?

I have a sectionlist of Contacts where I am displaying both device and online contacts of a user. The online contacts api doesnt give me all the contacts at once. So I have to implement some pagination. I am also fetching all device contacts and first page of online contacts and sorting them to show in sectionlist, but the problem is, to load more contacts, i have to keep track of the last item rendered in my state and in the render function I am calling pagination function to load more contacts. and then i am updating the state of fetched online contact. But its an unsafe operation, is there a better way to achieve this?
I want to execute a function when the specific item renders and it can update the state.
Here is some code: ContactList.tsx
import React, { Component } from "react";
import {
View,
StyleSheet,
SectionListData,
SectionList,
Text
} from "react-native";
import { Contact } from "../../models/contact";
import ContactItem from "./contact-item";
export interface ContactsProps {
onlineContacts: Contact[];
deviceContacts: Contact[];
fetchMoreITPContacts: () => void;
}
export interface ContactsState {
loading: boolean;
error: Error | null;
data: SectionListData<Contact>[];
lastItem: Contact;
selectedItems: [];
selectableList: boolean;
}
class ContactList extends Component<ContactsProps, ContactsState> {
private sectionNames = [];
constructor(props: ContactsProps, state: ContactsState) {
super(props, state);
this.state = {
loading: false,
error: null,
data: [],
lastItem: this.props.onlineContacts[this.props.onlineContacts.length - 1]
};
for (var i = 65; i < 91; ++i) {
this.sectionNames.push({
title: String.fromCharCode(i),
data: []
});
}
}
private buildSectionData = contacts => {
this.sort(contacts);
const data = [];
const contactData = this.sectionNames;
contacts.map(contact => {
const index = contact.name.charAt(0).toUpperCase();
if (!data[index]) {
data[index] = [];
contactData.push({
title: index,
data: []
})
}
data[index].push(contact);
});
for (const index in data) {
const idx = contactData.findIndex(x => x.title === index);
contactData[idx].data.push(...data[index]);
}
this.setState({
loading: false,
error: null,
lastItem: contacts[contacts.length - 1],
data: [...contactData]
});
};
private sort(contacts) {
contacts.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
if (b.name > a.name) {
return -1;
}
return 0;
});
}
componentDidMount() {
const contacts = [].concat(
this.props.deviceContacts,
this.props.onlineContacts
);
this.buildSectionData(contacts);
}
componentDidUpdate(
prevProps: Readonly<ContactsProps>,
prevState: Readonly<ContactsState>,
snapshot?: any
): void {
if (this.props.onlineContacts !== prevProps.onlineContacts) {
const from = this.props.itpContacts.slice(
prevProps.onlineContacts.length,
this.props.onlineContacts.length
);
this.buildSectionData(from);
}
}
renderItem(item: any) {
if (!!this.state.lastItem && !this.state.loading)
if (item.item.id === this.state.lastItem.id) {
this.setState({
loading: true
});
this.props.fetchMoreOnlineContacts();
}
return <ContactItem item={item.item} />;
}
render() {
return (
<View style={styles.container}>
<SectionList
sections={this.state.data}
keyExtractor={(item, index) => item.id}
renderItem={this.renderItem.bind(this)}
renderSectionHeader={({ section }) =>
section.data.length > 0 ? (
<Text style={styles.sectionTitle}>
{section.title}
</Text>
) : null
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
sectionTitle: {
paddingBottom: 30,
paddingLeft: 25,
fontWeight: "bold",
fontSize: 20
}
});
export default ContactList;
Yeah after some thoughts I got the answer may be.
instead of calling fetchMoreContacts from renderItem I passed the lastItem as a prop to the ContactItem component.
and in the constructor I checked If the item is lastItem and called to fetchMoreContact.
and it worked!

Component wont render from mapStateToProps

I am using the events calendar package, https://www.npmjs.com/package/react-native-events-calendar, I am trying to display the EventsCalendar component with events coming from redux.
events={fetchEvents[selected].dots} is an array but the component will not render, however if I copy the array directly into the component it renders.
Any ideas why my component won't render?
import React, { Component } from "react";
import { connect } from "react-redux";
import {
Text,
View,
StyleSheet,
Image,
Dimensions,
ActivityIndicator
} from "react-native";
import Icon from "react-native-vector-icons/FontAwesome";
import * as helpers from "../../helpers";
import {
fetchEvents,
changeMonth,
eventsForAgendaMonth
} from "../../actions/events";
import CustomHeader from "../../navigation/CustomHeader";
import XDate from "xdate";
import AgendaItem from "./AgendaItem";
import AgendaDay from "./AgendaDay";
import EventCalendar from "./DayView";
import propTypes from "prop-types";
class AgendaScreen extends Component {
static navigationOptions = {
title: "",
header: <CustomHeader />
};
constructor(props) {
super(props);
this._eventTapped = this._eventTapped.bind(this);
this.dateChanged = this.dateChanged.bind(this);
this.renderEvent = this.renderEvent.bind(this);
}
dateChanged(date) {
const { eventsForYear, eventsMonth } = this.props;
this.setState({ selectedDay: date });
this.props.dispatch(eventsForAgendaMonth(eventsForYear, date));
}
_eventTapped(event) {
alert(JSON.stringify(event));
}
loadItems(day) {
const { eventsForYear } = this.props;
this.props.dispatch(eventsForAgendaMonth(eventsForYear, day));
}
renderEvent(event) {
console.log("event", event);
return <AgendaItem {...event} />;
}
render() {
const {
fetchEvents,
navigation: {
state: {
params: {
day: { dateString: selected }
}
}
}
} = this.props;
const currentDate = helpers.currentDate;
let { width } = Dimensions.get("window");
return (
<View style={{ flex: 1 }}>
<EventCalendar
eventTapped={this._eventTapped.bind(this)}
events={fetchEvents[selected].dots}
width={width}
initDate={selected}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
fetchEvents: state.fetchEvents
};
};
export default connect(mapStateToProps)(AgendaScreen);
AgendaScreen.propTypes = {
eventsMonth: propTypes.object,
eventsForYear: propTypes.object
};
const styles = {
container: {
backgroundColor: "#00000000"
},
event: {
backgroundColor: "#00000000",
borderColor: "#DDE5FD",
borderWidth: 0,
borderRadius: 0,
paddingTop: 3,
paddingBottom: 2
},
header: {
height: 30,
paddingHorizontal: 30
},
headerText: {}
};
AFAIK I am not mutating state, I am createing a new object with my reducer:
case "EVENTS_SUCCESS":
const events = {};
const filteredEvents = action.data
.filter(event => {
if (
event.status === "cancelled" ||
!event.start.dateTime ||
helpers.checkDate(event.start.dateTime)
) {
return false;
}
return true;
})
.sort((a, b) => a.startTimeString - b.startTimeString);
filteredEvents.map(event => {
const {
start: { dateTime: startTime },
end: { dateTime: endTime },
summary,
id
} = event;
const sDate = new Date(startTime);
const eDate = new Date(endTime);
const startHour = sDate.getHours();
const startMinute = sDate.getMinutes();
const startDate =
sDate.getFullYear() +
"-" +
("0" + (sDate.getMonth() + 1)).slice(-2) +
"-" +
("0" + sDate.getDate()).slice(-2);
const endHour = eDate.getHours();
const endMinute = eDate.getMinutes();
const endDay = eDate.getDay();
const endMonth = eDate.getMonth();
const endYear = eDate.getFullYear();
const eventStartTime = `${startHour}-${startMinute}`;
const eventEndTime = `${endHour}-${endMinute}`;
let newEvent = {
title: summary,
summary,
id,
eventStartTime,
eventEndTime
};
events[startDate] = events[startDate] || { dots: [] };
const dotsEvents = [...events[startDate].dots, newEvent];
dotsEvents.sort(
(a, b) => a.startTimeString - b.startTimeString
);
events[startDate] = {
dots: dotsEvents,
disabled: false,
selected: true,
selectedColor: "#00CCCB",
customStyles: {
text: {
marginTop: 3
}
}
};
});
return {
...events
};

React Component, Highstock: Synchronize multiple charts?

I am working with React and HighCharts. I am relatively new to both these technologies. I need to generate two synchronized HighStock charts. I was able to display the charts with the below layout.
<div class=container>
<div class=chart1>new highcharts.StockChart(newChartOptions) </div>
<div class=chart2>new highcharts.StockChart(newChartOptions)</div>
</div>
The Charts are displayed. I want to synchronize the charts to have a common tool tip, I see the http://www.highcharts.com/demo/synchronized-charts , not sure how to implement with React. I have tried to assign a function(handleEvent(e)) to plotOptions:{line:{ point:{ event:{click: and MouseOver}}}} but it did not help. Not sure how to invoke the handleEvent(e) method. I am not sure how/when to invoke the handleEvent(e). Any help is greatly is appreciated.
Below is the Component code:
import $ from 'jQuery';
import React from 'react';
import highcharts from 'highcharts-release/highstock';
export default class SynchronizedStatusChart extends React.Component {
constructor (props) {
super(props);
this.state = {
chartName: `chart${this.props.chartNum}`,
};
}
handleEvent(e){
let allCharts = highcharts.charts;
console("SynchronizedStatusChart:handleEvent:ChartsLength = " + allCharts.length);
var chart, point, i, event;
for (i = 0; i < allCharts.length; i = i + 1)
{
chart = highcharts.charts[i];
event = chart.pointer.normalize(e.originalEvent); // Find coordinates within the chart
point = chart.series[0].searchPoint(event, true); // Get the hovered point
if (point) {
this.onMouseOver(); // Show the hover marker
this.series.chart.tooltip.refresh(this); // Show the tooltip
this.series.chart.xAxis[0].drawCrosshair(event, this);
}
}
}
componentDidMount () {
}
componentWillUpdate (nextProps) {
for(let i=0; i<nextProps.data.length; i++){
this.generateChart(nextProps.data[i],i+1,nextProps.titles[i]);
}
}
generateChart(data, i, title) {
if(data == null)
{
data = [];
}
let ticksData = [0,1];
let newChartOptions =
{
chart: {
//renderTo: document.getElementById(this.state.chartName),
renderTo: document.getElementById(`SyncChart${i}`),
height:'125'
},
rangeSelector: {
enabled: false
},
credits: {
enabled: false
},
navigator: {
enabled: false
},
scrollbar: {
enabled: false
},
tooltip: {
shared: true,
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y:.2f}</b> <br/>'
},
xAxis:{
},
yAxis: {
offset: 15,
labels: {
align: 'center',
x: -3,
y: 6
},
tickPositioner: function () {
var positions = ticksData;
return positions;
},
opposite:false,
showLastLabel: true,
title:{
text:title
}
},
series: [{
name: title,
type: this.props.status ? 'area' : 'line',
data: data,
showInNavigator: false
}],
};
new highcharts.StockChart(newChartOptions);
}
render () {
return (
<div className="med-chart col-md-9" id={this.state.chartName} style={this.props.chartStyle}>
<div id='SyncChart1'></div>
<div id='SyncChart2'></div>
</div>
);
}
}
I had the same problem recently. Here's what has worked for me.
I am using common parent component to add pure javascript event listeners for 'mousemove' and 'mouseleave' to each chart DOM element.
class ParentComponent extends Component {
...
componentDidMount() {
this.charts = document.querySelectorAll('.chart-container');
[].forEach.call(this.charts, (chart) => {
chart.addEventListener('mousemove', this.handleChartMouseMove);
chart.addEventListener('mouseleave', this.handleChartMouseLeave);
});
Highcharts.Pointer.prototype.reset = () => undefined;
}
componentWillUnmount() {
[].forEach.call(this.charts, (chart) => {
chart.removeEventListener('mousemove', this.handleChartMouseMove);
chart.removeEventListener('mousemove', this.handleChartMouseLeave);
});
}
handleChartMouseMove = (e) => {
const chartIndex = e.currentTarget.dataset.highchartsChart;
const chart = Highcharts.charts[chartIndex];
const event = chart.pointer.normalize(e);
const pointIndex = chart.series[0].searchPoint(event, true).index;
Highcharts.charts.forEach((chart) => {
const xAxis = chart.xAxis[0];
const point = chart.series[0].points[pointIndex];
const points = chart.series.map(s => s.points[pointIndex]); // if more than one series
point.onMouseOver();
xAxis.drawCrosshair(event, point);
// if more than one series, pass an array of points, took a me a long time to figure it out
chart.tooltip.refresh(points, event);
});
};
handleChartMouseLeave = () => {
Highcharts.charts.forEach((chart) => {
chart.tooltip.hide();
chart.xAxis[0].hideCrosshair();
});
};
...
}

react-dnd uncaught typerrors. Trying to follow the simple sortable example

I've been trying to work off of the simple sortable example in the react-dnd examples but I am having trouble trying to convert the es7 code to es6. I've tried using babel but I don't really understand the code that it spits out.
Here is my code that I've tried to translate from es7 to es6:
import React, {PropTypes} from 'react';
import Router from 'react-router';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import { DragSource, DropTarget } from 'react-dnd';
const style= {
border: '1px dashed gray',
padding: '0.5rem 1rem',
marginBottom: '.5rem',
backgroundColor: 'white',
cursor: 'move'
}
const ItemTypes = {
Coursepage: 'coursepage'
};
const coursePageSource = {
beginDrag(props) {
return {
id: props.id,
index: props.index
}
}
}
const coursePageTarget = {
hover(props, monitor, component){
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
//don't replace items with themselves
if(dragIndex === hoverIndex){
return;
}
//Determine rectangle on screen
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
//get vertical middle
const hoverMiddleY = (hoverBoundingRect.Bottom - hoverBoundingRect.Top) /2;
//get top pixels
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
//only perform the move when the mouse has crossed half of the items height
//when dragging downwards, only move when the cursor is below 50%
//when dragging upwards, only move when the cursor is above 50%
//dragging downwards
if(dragIndex < hoverIndex && hoverClientY < hoverMiddleY){
return;
}
//dragging upwards
if(dragIndex > hoverIndex && hoverClientY > hoverMiddleY){
return;
}
//time to actually perform the action
props.moveObject(dragIndex, hoverIndex);
}
}
// const propTypes = {
// connectDragSource: PropTypes.func.isRequired,
// connectDropTarget: PropTypes.func.isRequired,
// index: PropTypes.number.isRequired,
// isDragging: PropTypes.bool.isRequired,
// id: PropTypes.any.isRequired,
// text: PropTypes.string.isRequired,
// moveCard: PropTypes.func.isRequired
// };
function collectDropTarget(connect) {
return {
connectDropTarget: connect.dropTarget(),
};
}
/**
* Specifies which props to inject into your component.
*/
function collectDragSource(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDragSource: connect.dragSource(),
// You can ask the monitor about the current drag state:
isDragging: monitor.isDragging()
};
}
class Coursepage extends React.Component{
render(){
console.log(this.props);
const {text, isDragging, connectDragSource, connectDropTarget} = this.props;
const opacity = isDragging ? 0 : 1;
return connectDragSource(connectDropTarget(
<div style={{opacity}}>
{text}
</div>
));
}
}
// Coursepage.propTypes = propTypes;
export default DragSource(ItemTypes.Coursepage, coursePageSource, collectDragSource)(Coursepage);
export default DropTarget(ItemTypes.Coursepage, coursePageTarget, collectDropTarget)(Coursepage);
Now the error I'm getting from this is
"Uncaught TypeError: connectDropTarget is not a function."
I console logged this.props in render and I see that connectDragSource is showing up in the this.props object but not connectDropTarget.
Can anyone tell me what I'm missing?
By the way, this is the example code I was using:
https://github.com/gaearon/react-dnd/blob/master/examples/04%20Sortable/Simple/Card.js
I know this is a little old but I landed up here through google so I figured I would give it a go. First of all, you can't have two default exports as referenced here in section 3.2 http://www.2ality.com/2014/09/es6-modules-final.html
Instead you need to pass the result of one of your current default exports into the second function call - you'll see below.
This took me a couple of hours to get working as I'm also an Es6/7 newbie - so I invite any criticism!
// Container.js;
import React, { Component } from 'react';
import update from 'react/lib/update';
import Card from './Card';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
const style = {
width: 400
};
class Container extends Component {
constructor(props) {
super(props);
this.moveCard = this.moveCard.bind(this);
this.findCard = this.findCard.bind(this);
this.state = {
cards: [{
id: 1,
text: 'Write a cool JS library'
}, {
id: 2,
text: 'Make it generic enough'
}, {
id: 3,
text: 'Write README'
}, {
id: 4,
text: 'Create some examples'
}, {
id: 5,
text: 'Spam in Twitter and IRC to promote it (note that this element is taller than the others)'
}, {
id: 6,
text: '???'
}, {
id: 7,
text: 'PROFIT'
}]
};
}
findCard(id) {
const { cards } = this.state;
const card = cards.filter(c => c.id === id)[0];
return {
card,
index: cards.indexOf(card)
};
}
moveCard(id, atIndex) {
const { card, index } = this.findCard(id);
this.setState(update(this.state, {
cards: {
$splice: [
[index, 1],
[atIndex, 0, card]
]
}
}));
}
render() {
const { cards } = this.state;
return (
<div style={style}>
{cards.map((card, i) => {
return (
<Card key={card.id}
index={i}
id={card.id}
text={card.text}
moveCard={this.moveCard}
findCard={this.findCard} />
);
})}
</div>
);
}
}
export default DragDropContext(HTML5Backend)(Container)
Then Card.js
// Card.js
import React, { Component, PropTypes } from 'react';
import ItemTypes from './ItemTypes';
import { DragSource, DropTarget } from 'react-dnd';
const style = {
border: '1px dashed gray',
padding: '0.5rem 1rem',
marginBottom: '.5rem',
backgroundColor: 'white',
cursor: 'move'
};
const cardSource = {
beginDrag(props) {
return {
id: props.id,
originalIndex: props.findCard(props.id).index
};
},
endDrag(props, monitor) {
const { id: droppedId, originalIndex } = monitor.getItem();
const didDrop = monitor.didDrop();
if (!didDrop) {
props.moveCard(droppedId, originalIndex);
}
}
};
const cardTarget = {
canDrop() {
return false;
},
hover(props, monitor) {
const { id: draggedId } = monitor.getItem();
const { id: overId } = props;
if (draggedId !== overId) {
const { index: overIndex } = props.findCard(overId);
props.moveCard(draggedId, overIndex);
}
}
};
function collect(connect, monitor) {
console.log( "HERE2", connect );
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
function collect2(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging()
};
}
class Card extends Component {
render() {
const { text, isDragging, connectDragSource, connectDropTarget } = this.props;
const opacity = isDragging ? 0 : 1;
return connectDragSource(connectDropTarget(
<div >
{text}
</div>
));
}
}
Card.propTypes = {
connectDragSource: PropTypes.func.isRequired,
connectDropTarget: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
id: PropTypes.any.isRequired,
text: PropTypes.string.isRequired,
moveCard: PropTypes.func.isRequired,
findCard: PropTypes.func.isRequired
};
const x = DropTarget(ItemTypes.CARD, cardTarget, collect )(Card)
export default DragSource(ItemTypes.CARD, cardSource, collect2 )( x )
And then the types include
// ItemTypes.js
export default {
CARD: 'card'
};

Resources