Async/Await in Redux - reactjs

So, I have a function, which convert images to base64. This function is asynchronous, and it converts 4 images with the help of Promise.all(), and then I return object with received strings. So, I export async function. Here is the code:
import IMAC from '../assets/Images/devices/mac_monitor.png';
import MACBOOK from '../assets/Images/devices/macbook_pro.png';
import IPHONE_8 from '../assets/Images/devices/iphone_8.png';
import MSI_LAPTOP from '../assets/Images/devices/msi_laptop.png';
function loadImage(img) {
return new Promise((resolve, reject) => {
toDataURL(img, function (dataUrl) {
resolve(dataUrl);
})
});
}
function toDataURL(url, callback) {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
let reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
const IMAC_IMG_BASE64 = loadImage(IMAC);
const MACBOOK_IMG_BASE64 = loadImage(MACBOOK);
const MSI_IMG_BASE64 = loadImage(MSI_LAPTOP);
const PHONE_IMG_BASE64 = loadImage(IPHONE_8);
export async function loadAllImages() {
const result = await Promise.all([IMAC_IMG_BASE64, MACBOOK_IMG_BASE64, MSI_IMG_BASE64, PHONE_IMG_BASE64]);
return [
{
id: 0,
device: "Apple iMac",
image: result[0],
styles: {
carousel_item: {
width: "41.6vw",
height: "auto",
top: "-4.095vw",
left: "-0.13vw"
},
carousel: {
height: "38vw",
margin: "50px 0"
},
device: {
width: "46.5vw",
height: "38vw",
marginLeft: "-23.25vw"
}
}
},
{
id: 1,
device: "Apple Macbook Pro",
image: result[1],
styles: {
carousel_item: {
width: "37vw",
height: "auto",
top: "-4.4vw",
left: ".6vw"
},
carousel: {
height: "38vw",
margin: "50px 0"
},
device: {
width: "55vw",
height: "30vw",
marginLeft: "-27.5vw"
}
}
},
{
id: 2,
device: "MSI GP72VR 7RFX",
image: result[2],
styles: {
carousel_item: {
width: "35vw",
height: "auto",
top: "-5.8vw",
left: ".5vw"
},
carousel: {
height: "38vw",
margin: "50px 0"
},
device: {
width: "50vw",
height: "34vw",
marginLeft: "-25vw"
}
}
},
{
id: 3,
device: "Iphone 8",
image: result[3],
styles: {
carousel_item: {
width: "14vw",
height: "auto",
top: "-8.2vw",
left: "0"
},
carousel: {
height: "38vw",
margin: "50px 0"
},
device: {
width: "17.7vw",
height: "34vw",
marginLeft: "-8.85vw"
}
}
},
];
}
Then, I have this action creator, which is async, where I received data from this function (loadAllImages()), and then I call dispatch (p.s. - I am using redux-thunk)
export const loadConfigs = () => async dispatch => {
const data = await loadAllImages();
dispatch({type: "LOAD_DATA", payload: data});
};
Also, I have reducer, where I return payload with the object, received from the called dispatch
export default (sliderConfig = null, action) => {
const {type, payload} = action;
switch(type){
case "LOAD_DATA":
return payload;
}
return sliderConfig;
}
Inside the main container App.js, I call this AC inside the componentDidMount()
(Don't look at fetchUser(), it does not matter in this context)
componentDidMount() {
this.props.fetchUser();
this.props.loadConfigs();
}
And, then I have component, where I am using this data, which asynchronously received from the AC. (Do not look at appDesign(), it does not matter in this context)
import React, {Component, PureComponent} from 'react';
import appDesign from '../../../decorators/scroll_resize_decorator';
import Slider from './Slider';
import {connect} from 'react-redux';
import * as actions from '../../../actions';
//Hint: Use container for the images in the slider
//Because errors with movement is appeared
class BlockFour extends Component {
render() {
if (this.props.sliderElements) {
const {sliderElements, width, config, selectConfig} = this.props;
return (
<div className="blockfive">
<div className="blockfive--inner">
<div className="blockfive__container">
<div className="blockfive__container__header">
<div className="blockfive__container__header__container">
<h1>Application Gallery</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
A aliquid blanditiis consequuntur debitis deserunt eaque eligendi
</p>
<div className="blockfive__header--divider"></div>
</div>
</div>
<div className="blockfive__container__device">
<h2>
Choose your device to what screenshots
</h2>
<ul className="tabs">
{
sliderElements.map(item =>
<li
key={item.id}
className="tab"
>
<a href="#"
onClick={
() => selectConfig(item.id)
}
>
{item.device}
</a>
</li>
)
}
</ul>
</div>
<div className="blockfive__container__gallery">
{
<Slider
width={width}
styles={sliderElements[config].styles}
device_image={sliderElements[config].image}
/>
}
</div>
</div>
</div>
</div>
);
}
return null
}
}
const mapStateToProps = ({sliderElements, config}) => {
return {
sliderElements,
config
}
};
export default connect(mapStateToProps, actions)(appDesign(BlockFour));
So, this syntax is working, everything is loading and working. So, I have a question: What is the right way to fetch async data in AC, then pass them to reducer and then load inside the component. I don't want to use if statement inside my component.
I read couple of guides about async/await AC and how use them, but I do not completely understand how to use it in my situation. Could you please give me a brief direction how to implement it here. Thank you!

I personally and most people follow this approach. Its completely personal and wont change much in your app, but might make your life easier.
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
This way your UI and other parts of your app connected to the store can act accordingly depending on the state.
Exemples includes : showing a loading icon or message when FETCH_SMTH_REQUEST is fired and your state changes to fetching and showing an error when FETCH_SMTH_FAILURE and you get the error in your state.

I personally prefer to use constants with the same name of the content, as a Type. Like this:
export const Types = {
FETCH_DATA_START: "FETCH_DATA_START",
FETCH_DATA_SUCCESS: "FETCH_DATA_SUCCESS",
FETCH_DATA_FAIL: "FETCH_DATA_FAIL",
};
Just to be clear, I've hadn't seen nothing wrong and different of it in other projects that I've worked on. At least in my opinion, your action is great. I probably would wrap it all in a try...catch clause to have a best control of my flux of data.
Just to know, if you need, when you using redux-thunk, you have the actual state as a second parameter and, if you need, you can pass extra arguments in the middleware configuration as the third parameter, like an API for exemple. So, your code, could seem like this:
export const fetchMySpecialData = () => async(dispatch, getState, API) => {
try {
dispatch({type: "FETCH_DATA_START"});
const data = await API.fetchData();
dispatch({type: "FETCH_DATA_SUCCESS", payload: data});
} catch (err) {
dispatch({type: "FETCH_DATA_FAIL", error: err});
}
};

Related

ReactJS and PeerJS (simple-peer) Uncaught (in promise) TypeError: Cannot set properties of null (setting 'srcObject')

I wanted to add an video call feature with WebRTC into my chat webapp. So after writing the code i tested the code. -> No video and in the console the error "Uncaught (in promise) TypeError: Cannot set properties of null (setting 'srcObject')"
screenshot error message
My code:
//CallProvider
import React, { createContext, useState, useRef, useEffect } from 'react';
import Peer from 'simple-peer';
import io from 'socket.io-client';
export const VideoCallContext = createContext();
const socket = io('http://localhost:5001');
export function CallProvider({ conv, id, children }) {
const videoGrid = document.querySelector('#video-grid');
const [stream, setStream] = useState(null);
const myVideoRef = useRef(null);
const peer = new Peer({
trickle: false,
stream,
});
const peers = {};
useEffect(() => {
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((currentStream) => {
setStream(currentStream);
myVideoRef.current.srcObject = currentStream.streams[0];
});
socket.on('user-connected', (userId) => {
connectToNewUser(userId, stream);
});
});
socket.on('user-disconnected', (userId) => {
if (peers[userId]) peers[userId].close();
});
function joinCall() {
peer.on('signal', (data) => {
socket.emit('join-call', {
call: conv,
userId: id,
});
});
}
function leaveCall() {
socket.emit('leave-call', {
call: conv,
userId: id,
});
navigator.mediaDevices.getUserMedia({
video: false,
audio: false,
});
}
function connectToNewUser(userId, stream) {
const call = peer.call(userId, stream);
const video = document.createElement('video');
call.on('stream', (userVideoStream) => {
addVideoStream(video, userVideoStream);
});
call.on('close', () => {
video.remove();
});
}
function addVideoStream(video, stream) {
video.srcObject = stream;
video.addEventListener('loadedmetadata', () => {
video.play();
video.playsInline = true;
});
videoGrid.append(video);
}
const value = {
stream,
myVideoRef,
joinCall,
leaveCall,
};
return (
<VideoCallContext.Provider value={value}>
{children}
</VideoCallContext.Provider>
);
}
// FormCall
//use WebRTC from simplepeer
import React, { useContext } from 'react';
import { Button } from 'react-bootstrap';
import { VideoCallContext } from '../contexts/CallProvider';
export default function FormCall({ id, conv, closeWindow }) {
const { stream, myVideoRef, joinCall, leaveCall } =
useContext(VideoCallContext);
const conversation = conv;
function closeCall() {
leaveCall(id);
closeWindow();
}
return (
<>
<style>
{`
#video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(210px, 230px));
grid-auto-rows: auto;
grid-gap: 5px;
}
`}
{`
video {
width: 100%;
aspect-ratio: 1/1;
object-fit: cover;
object-position: center;
border-radius: 10px;
overflow: hidden;
}
`}
</style>
<div className="pb-2" id="video-grid">
<video playsInline muted ref={myVideoRef} autoPlay></video>
</div>
<div className="d-flex flex-row justify-content-around border-top pt-2">
<Button
onClick={closeCall}
className="rounded-circle position-relative"
style={{ height: '40px', width: '40px' }}
>
<i
className="bi bi-telephone-x-fill position-absolute"
style={{ left: '28%', top: '20%' }}
/>
</Button>
</div>
</>
);
}
My installed packages are:
packages
I have looked for an solution for the error. Nothing that is a solution for my problem.
I also have changed the "currentStream" to "currentStream.streams[0]", nothing changed.
When you set const myVideoRef = useRef(null)), you're setting myVideoRef.current to null.
If you need to the set srcObject property in your ref:
const myVideoRef = useRef({ srcObject: null }).
Now myVideoRef.current.srcObject exists and can be set.
Try
const value = {
stream:stream,
myVideoRef:myVideoRef,
joinCall:joinCall,
leaveCall:leaveCall,
};
Also i would avoid returning *.Provider itself.
Wrap CallProvider and it's children like this:
<VideoCallContext.Provider value={Your value}>
<CallProvider >
<Child1 />
<Child2 />
</CallProvider>
</VideoCallContext.Provider>
If You mreturn instance of Context.Provider as Component, then every state mutation rerenders Your Context . The Context is innescesarry then.
If Your Context fully wraps dependant DOM snippet, then Your Context values are stable.
At least one of these two has to be changed to solve Your problem.
P.S.
As Wesley LeMahieu found ,hence i would use useRef() instead of useRef(null).

Codepen.io won't render React component

I've been driving myself insane trying to figure out why this React component won't render. After checking in a local environment it works fine, but I need this to work on CodePen.io to turn it in for a FreeCodeCamp Challenge. Any help would be greatly appreciated.
I've tried putting in an 'unpkg' source, adding it to the html in script tags (the React packages) and using the integrated npm tool in the codepen.io js tab in the settings. None of this worked and I think the code should work.
import React from 'http://esm.sh/react#18.2.0'
import ReactDOM from 'http://esm.sh/react-dom#18.2.0'
import * as redux from "http://cdn.skypack.dev/redux#4.2.0";
import reduxThunk from "http://cdn.skypack.dev/redux-thunk#2.4.2";
import { Provider, connect } from "http://cdn.skypack.dev/react-redux#8.0.5";
// Initial State
const initialState = {
quote: "Let's go",
author: "Bob J. Baker",
backgroundColor: "#f5f5f5"
};
// Actions: Types
const CHANGE_ALL = "CHANGE_ALL";
// Action: Functions
const changeAll = () => {
return async (dispatch) => {
try {
dispatch({ type: CHANGE_ALL });
const response = await fetch("https://quotes.rest/quote/random");
const data = await response.json();
const color = getRandomColor();
dispatch({
type: CHANGE_ALL,
quote: data.contents.quotes[0].quote,
author: data.contents.quotes[0].author,
backgroundColor: color
});
} catch (error) {
console.log(error);
}
};
};
const getRandomColor = () => {
const randomColor = "#" + Math.random().toString(16).substr(-6);
return randomColor;
};
// Reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_ALL:
return {
...state,
quoteAndAuthor: { quote: action.quote, author: action.author },
backgroundColor: action.backgroundColor
};
default:
return state;
}
};
// Store
const store = redux.createStore(rootReducer, redux.applyMiddleware(reduxThunk));
//**********Component://**********
class RandomQuoteComponent extends React.Component {
// state & props
constructor(props) {
super(props);
}
componentDidMount() {
// running the get all action
this.props.changeAll();
}
render() {
return (
<div
id="main-wrapper"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
width: "100%",
backgroundColor: this.props.backgroundColor
}}
>
<div id="quote-box">
<h2 id="text">{this.props.quote}</h2>
<h6 id="author">{this.props.author}</h6>
<button id="new-quote">New Quote</button>
<a href="#" id="tweet-quote">
<i className="fab fa-twitter"></i>
</a>
</div>
</div>
);
}
}
////**********END OF COMPONENT//**********
// Mapping for state and dispatch actions to props
const mapStateToProps = (state) => {
return {
backgroundColor: state.backgroundColor,
quote: state.quoteAndAuthor.quote,
author: state.quoteAndAuthor.author
};
};
const mapDispatchToProps = (dispatch) => {
return {
changeAll: () => dispatch(changeAll())
};
};
const ConnectedRandomQuoteComponent = connect(
mapStateToProps,
mapDispatchToProps
)(RandomQuoteComponent);
ReactDOM.render(
<Provider store={store}>
<ConnectedRandomQuoteComponent />
</Provider>,
document.getElementById("root")
);

Mapbox map is getting loaded before useEffect returns data

I have started learning react hooks and I want to load map on initial render with cooridnates which I get from the backend, but somehow my map is getting rendered before my api give the data back,
how do I make sure my map will wait till data is return ? do I need to put any condition on that?
below is api code
import React, { useState, useEffect, useRef } from "react";
import { useDispatch, useSelector } from 'react-redux';
import mapboxgl from 'mapbox-gl';
import { getBikeInfo, mapDetails } from './features/counter/getInfo/getDetails.js'
function App() {
const dispatch = useDispatch();
const dataToDisplay = useSelector(mapDetails);
const mapContainer = useRef(null);
mapboxgl.accessToken = 'pk.eyJ1IjoidmloYW5nMTYiLCJhIjoiY2ttOHowc2ZhMWN2OTJvcXJ0dGpiY21pNyJ9.hK5Wxwby89E7tKWoBoY5bg';
useEffect(() => {
dispatch(getBikeInfo())
var map = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/light-v10',
center: [-96, 37.8],
zoom: 3
});
console.log('mapdetails:' + mapDetails)
console.log('data display:' + dataToDisplay)
map.on('load', function () {
// Add an image to use as a custom marker
map.loadImage(
'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png',
function (error, image) {
if (error) throw error;
map.addImage('custom-marker', image);
// Add a GeoJSON source with 2 points
map.addSource('points', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': dataToDisplay
}
});
// Add a symbol layer
map.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'points',
'layout': {
'icon-image': 'custom-marker',
// get the title name from the source's "title" property
'text-field': ['get', 'title'],
'text-font': [
'Open Sans Semibold',
'Arial Unicode MS Bold'
],
'text-offset': [0, 1.25],
'text-anchor': 'top'
}
});
}
);
});
}, []);
return (
<div className="district-map-wrapper">
<div id="districtDetailMap" className="map">
<div style={{ height: "100%" }} ref={mapContainer}>
</div>
</div>
</div>
);
}
export default App;
below is my updated code:
useEffect(() => {
if (dataToDisplay.length != 0) {
loadtheMap
}
}, []);
but after some refresh I see data is not getting populated and setting dataDisplay to [].
update 1:
based on suggestion I have updated my code like this
if(!dataLoaded) { // do not render anything if you're not ready
return (
<div className="hidden">
<div id="districtDetailMap" className="map">
<div style={{ height: "100%" }} ref={mapContainer}>
</div>
</div>
</div>);
}
where className="hidden" is define in App.css like below
.hidden{
display: none;
}
but still I am on same issue
as requested PFB my sandbox link
codesandbox
Since you're using useEffect you are basically guaranteed that your component will render once before it even initiates the query
If you want to ensure your component won't be rendered, use some flag to indicate that data is ready, set it to true after you set up your Map and conditionally render some fallback ui while this flag is false
function App() {
const [dataLoaded, setDataLoaded] = useState(false); // introduce the flag
const dispatch = useDispatch();
const dataToDisplay = useSelector(mapDetails);
const mapContainer = useRef(null);
mapboxgl.accessToken = 'pk.eyJ1IjoidmloYW5nMTYiLCJhIjoiY2ttOHowc2ZhMWN2OTJvcXJ0dGpiY21pNyJ9.hK5Wxwby89E7tKWoBoY5bg';
useEffect(() => {
dispatch(getBikeInfo())
var map = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/light-v10',
center: [-96, 37.8],
zoom: 3
});
console.log('mapdetails:' + mapDetails)
console.log('data display:' + dataToDisplay)
map.on('load', function () {
// Add an image to use as a custom marker
map.loadImage(
'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png',
function (error, image) {
if (error) throw error;
map.addImage('custom-marker', image);
// Add a GeoJSON source with 2 points
map.addSource('points', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': dataToDisplay
}
});
// Add a symbol layer
map.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'points',
'layout': {
'icon-image': 'custom-marker',
// get the title name from the source's "title" property
'text-field': ['get', 'title'],
'text-font': [
'Open Sans Semibold',
'Arial Unicode MS Bold'
],
'text-offset': [0, 1.25],
'text-anchor': 'top'
}
});
setDataLoaded(true); // set it to true when you're done
}
);
});
}, []);
return (
<div className="district-map-wrapper" style={dataLoaded ? undefined : {display: 'none'}}>
<div id="districtDetailMap" className="map">
<div style={{ height: "100%" }} ref={mapContainer}>
</div>
</div>
</div>
);
}

FineUploader not working after first upload

I've implemented FineUploader in my React app to upload files to my Azure Blob Storage and it's working fine except for one issue.
After uploading a file successfully, if I try uploading another one, FineUploader is not allowing me to select a new file. Clicking the select button opens up the dialog box to let me select a file but clicking the file to select it simply does nothing. I don't see any errors either.
Any idea what could be causing this issue?
Here's my implementation of FineUploader in my app:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import FineUploaderAzure from 'fine-uploader-wrappers/azure'
// Components
import Gallery from './gallery/index';
// Actions
import * as fileActions from '../../../../actions/file-actions';
// Instanciate FineUploader
const uploader = new FineUploaderAzure({
options: {
cors: {
expected: true,
sendCredentials: false
},
signature: {
endpoint: 'https://api.myapp.com/files/get/sas'
},
request: {
endpoint: 'https://myaccount.blob.core.windows.net/my-container'
},
validation: {
itemLimit: 1
}
}
})
class FileUploader extends Component {
constructor(props) {
super(props);
this.saveFileInfo = this.saveFileInfo.bind(this);
}
componentDidMount() {
uploader.on('complete', (id, name, responseJSON, xhr) => {
const originalName = uploader.methods.getName(id);
const blobName = uploader.methods.getBlobName(id);
const fileSize = uploader.methods.getSize(id);
this.saveFileInfo(originalName, blobName, fileSize);
})
}
saveFileInfo(fileName, blobName, fileSize) {
// Gather necessary information
const accountId = this.props.accountId;
const id = this.props.id;
const folderId = this.props.activeFolder.id;
const files = [
{
fileName: blobName,
displayName: fileName,
fileSize: fileSize
}
];
// Call backend API to save file info in database
this.props.actions.createFileRecords(accountId, bizObject, id, privilegeLevel, folderId, files);
// Close modal
const modalId = this.props.modalId;
return this.props.handleClose(modalId, false);
}
render() {
return (
<div style={{ position: 'fixed', zIndex: 250000990 }}>
<div className="modal-wrapper">
<div className="height-100 width-100" style={{ background: 'transparent', position: 'absolute', left: '0', top: '0' }}></div>
<div className="modal-window vertical-center">
<div className="modal-controls padding-right-20 padding-top-10" style={{ height: '50px', position: 'absolute', right: '0', top: '0', lineHeight: '40px', borderColor: 'transparent !important' }}>
<a className="modal-control mc-help"></a>
<a className="modal-control mc-close" onClick={e => this.props.handleClose(this.props.modalId, false)}></a>
</div>
<div className="width-100 height-100 border-radius border-black-w1-1" style={{ overflow: 'hidden', background: 'black !important', borderColor: 'black' }}>
<Gallery uploader={uploader} onComplete={this.handleFileUpload} />
</div>
<div style={{ position: 'absolute', bottom: '17px', left: '17px' }}>
{/* Privilege Level Selector */}
{this.renderPrivilegeLevels()}
<span className="app-btn app-btn-lg margin-left-20">Uploading into Folder: <strong>{this.props.activeFolder.name}</strong></span>
</div>
</div>
</div>
</div>
)
}
}
function mapStateToProps(state, ownProps) {
return {
modalId: ownProps.modalId,
accountId: ownProps.accountId,
id: ownProps.id,
folders: ownProps.folders,
activeFolder: ownProps.activeFolder,
fileUpload: state.files.fileUpload,
errors: state.files.errors,
handleClose: ownProps.handleClose
}
}
function mapDispatchToProps(dispatch, ownProps) {
return {
actions: bindActionCreators(fileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader)
As explained in the comment the itemLimit: 1 option is limiting the total number of upload for that Uploader to 1.
Since what you are trying to achieve is actually avoiding multiple upload at once you can use the option multiple: false to block selection of multiple files.
Also, to avoid users adding more files while others are still uploading, you can use a custom validation where you check if some other files are in progress, for example:
options: {
..., //other options
callbacks: {
onValidate: function (file) {
if(getInProgress() > 0){
return false;
}
return true;
}
}
Note that the onValidate events is called before the default Fine Uploader validators

react-dnd not detecting hover and drop events

I'm having trouble getting react-dnd to to work. Specifically, while I can confirm dragging is being detected properly, my droppable targets are not detecting hover or drop events. I've been following the example at http://survivejs.com/react/implementing-kanban/drag-and-drop/ to create a draggable item. I've tried to use a combination of the same examples and the official examples from the official repo to create a DropTarget to accept the draggable. However, my DropTarget is giving no indication that it is detecting the draggable. My code below has multiple debugger statements to indicate if code is being reached, but none of them ever are.
I suspect that the compose call at the end might be the problem, but I'm following Dan Abramov's example here. Just to add to the problem, the React inspector in Chrome dev tools lets me see the itemType state variable change as I drag an item. However, both the canDrop and isOver state variables remain false. I'd appreciate any help to get this to work.
import { findDOMNode } from 'react-dom';
import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Paper from 'material-ui/Paper';
import FaDelete from 'react-icons/lib/fa/trash-o';
import RaisedButton from 'material-ui/RaisedButton';
import FaEdit from 'react-icons/lib/fa/star';
import actions from '../actions/actions';
import TextField from 'material-ui/TextField';
import { connect } from 'react-redux';
//import EmojiPickerPopup from './EmojiPickerPopup';
import RenderIf from 'render-if';
import globals from '../globals';
import { DropTarget } from 'react-dnd';
import { compose } from 'redux';
const locationItemContainer = {
display: 'flex',
flexDirection: 'column',
backgroundColor: 'lightgoldenrodyellow',
border: '1px solid Moccasin',
width: "33%",
maxHeight: "15em"
}
const controlsContainer = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-around',
width: "100%"
}
const paperStyle = {
padding: '8px 4px',
display: 'flex',
flexDirection: "column",
alignItems: 'center',
justifyContent: 'center',
width: "100%",
height: "100%"
};
class LocationItemComponent extends Component {
constructor(props, context) {
super(props, context);
this.state = {
locationMarkers: []
}
}
componentWillReceiveProps(nextProps) {
if (!this.props.isOver && nextProps.isOver) {
// You can use this as enter handler
debugger
}
if (this.props.isOver && !nextProps.isOver) {
// You can use this as leave handler
debugger
}
if (this.props.isOverCurrent && !nextProps.isOverCurrent) {
// You can be more specific and track enter/leave
// shallowly, not including nested targets
debugger
}
}
nameChanged = (id, event, value) => {
this.props.dispatch(actions.storyMapActions.updateMarkerName(value, id));
}
deleteMarker = (id) => {
this.props.dispatch(actions.storyMapActions.deleteMarker(id));
}
showEmojiPicker = (id, event) => {
this.props.dispatch(actions.modalsActions.showEmojiPicker(id, event.currentTarget))
}
render() {
const { isOver, canDrop, connectDropTarget } = this.props;
if (isOver) {
console.log("is over");
}
return connectDropTarget(
<div style={locationItemContainer}>
<MuiThemeProvider>
<Paper zDepth={5}
style={paperStyle}
rounded={false}>
<TextField
id="markerName"
hintText="marker Name"
onChange={this.nameChanged.bind(this, this.props.marker.id)}
value={this.props.marker.name}
underlineFocusStyle={{ color: globals.textUnderlineColor }}
/>
<div style={controlsContainer}>
<RaisedButton
icon={<FaEdit />}
primary={true}
onClick={this.showEmojiPicker.bind(this, this.props.marker.id)} />
<RaisedButton
icon={<FaDelete />}
secondary={true}
onClick={this.deleteMarker.bind(this, this.props.marker.id)} />
</div>
</Paper>
</MuiThemeProvider>
</div>
);
}
}
const mapStateToProps = (state) => {
return Object.assign({}, { state: state });
}
const locationTarget = {
canDrop(props, monitor) {
debugger;
// You can disallow drop based on props or item
const item = monitor.getItem();
return true;
},
hover(props, monitor, component) {
debugger;
// This is fired very often and lets you perform side effects
// in response to the hover. You can't handle enter and leave
// hereā€”if you need them, put monitor.isOver() into collect() so you
// can just use componentWillReceiveProps() to handle enter/leave.
// You can access the coordinates if you need them
const clientOffset = monitor.getClientOffset();
const componentRect = findDOMNode(component).getBoundingClientRect();
// You can check whether we're over a nested drop target
const isJustOverThisOne = monitor.isOver({ shallow: true });
// You will receive hover() even for items for which canDrop() is false
const canDrop = monitor.canDrop();
},
drop(props, monitor, component) {
debugger;
if (monitor.didDrop()) {
// If you want, you can check whether some nested
// target already handled drop
debugger
return;
}
// Obtain the dragged item
const item = monitor.getItem();
// You can do something with it
//ChessActions.movePiece(item.fromPosition, props.position);
// You can also do nothing and return a drop result,
// which will be available as monitor.getDropResult()
// in the drag source's endDrag() method
return { moved: true };
}
};
const collect = (connect, monitor) => {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
itemType: monitor.getItemType()
};
}
export default compose(
connect(mapStateToProps),
DropTarget(globals.itemTypes.LOCATION_ITEM, locationTarget, collect)
)(LocationItemComponent);

Resources