react-vis: dynamic height for charts, when the numbers are increased - reactjs

I have a project, and this project contains several interfaces, and among these interfaces there is an interface to display a set of statistics, meaning that the interface displays a set of charts,
And I use a library called:
react-vis
But the problem is shown in this picture:
When the numbers increase, the numbers are displayed on top of each other:
How can height be dynamic?
import ReactChart from '../../common/chart';
<Row className='mt-8'>
<Col lg={24}>
<ReactChart
type={typeChart.Bar}
data={xtopography}
height={240}
width={1000}
fill={'#1E8D77'}
/>
</Col>
</Row>
BarChart.tsx:
import { CSSProperties, FunctionComponent, useEffect, useState } from 'react';
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
makeHeightFlexible
} from 'react-vis';
import { BaseChart } from './data/interfaces';
interface BarChartProps extends BaseChart {
colorValue?: string;
color?: string;
style?: CSSProperties;
barWidth?: number;
stroke?: string;
fill?: string;
}
const BarChart: FunctionComponent<BarChartProps> = ({
colorRange,
colorValue,
color,
data,
style,
barWidth,
width,
height,
stroke,
fill,
}) => {
var yValues: any = data?.map((y, index) => {
return y?.y;
})
const FlexibleXYPlot = makeHeightFlexible(yValues);
return (
<>
<XYPlot
margin={{ bottom: 30, left: 20, top: 20 }}
xType='ordinal'
width={width ? width : 450}
height={height}
>
<VerticalGridLines marginLeft={2} width={5} />
<HorizontalGridLines tickValues={yValues} />
<XAxis />
<YAxis tickValues={yValues}
tickSize={12} />
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={fill ? fill : '#C6E2DD'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={data}
/>
</XYPlot>
</>
);
};
export default BarChart;

Related

Recharts data not updating on the chart when using the slider

I am trying to display a dot that moves on the line when I use the slider (SliderWidget.js) but nothing is happening. I did the console.log() to see if it works and it does but on the chart (I am using Recharts library) nothing happens.
import SliderWidget from "../SliderWidget";
import { LineChart, Line, XAxis, YAxis, CartesianGrid } from "recharts";
import { useState } from "react";
const LongitudinalCoG = () => {
const [chartValue, setChartValue] = useState([
{
data: [
{ x: 1000, y: 2000 },
{ x: 3000, y: 3500 },
{ x: 4000, y: 4500 },
{ x: 4100, y: 4200 },
],
},
{
//this is the little dot on the chart
data: [{ x: 2000, y: 2500 }],
},
]);
console.log(chartValue);
function sliderData(newValue) {
chartValue[1].data[0].x = newValue;
setChartValue(chartValue);
console.log(chartValue);
}
return (
<div>
<LineChart
width={500}
height={300}
data={chartValue}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="x" />
<YAxis dataKey="y" />
<Line
dataKey="y"
data={chartValue[0].data}
stroke="white"
dot={{
r: 1,
}}
/>
<Line
dataKey="y"
data={chartValue[1].data}
stroke="blue"
dot={{
r: 3,
}}
/>
</LineChart>
<SliderWidget
minValue={chartValue[1].data[0].x}
maxValue={chartValue[1].data[0].y}
sliderTitle={"Override (mm)"}
onValueChanged={sliderData}
/>
</div>
);
};
export default LongitudinalCoG;
this is the SliderWidget component
import { useState } from "react";
import { Slider, InputNumber } from "antd";
import Icon from "#mdi/react";
import { mdiPlus, mdiMinus } from "#mdi/js";
import "../styles/Expanded.css";
const SliderWidget = (props) => {
const [inputValue, setInputValue] = useState(0);
const onChange = (newValue) => {
setInputValue(newValue);
props.onValueChanged(newValue);
};
function increment() {
if (inputValue + 1 < props.maxValue) {
setInputValue(inputValue + 1);
}
}
function decrement() {
if (inputValue - 1 > props.minValue) {
setInputValue(inputValue - 1);
}
}
return (
<div>
<div className="slider-title">
{props.sliderTitle}
<InputNumber
className="input-number-helicopter"
style={{ width: "40px", marginBottom: "10px", marginTop: "10px" }}
value={inputValue}
onChange={onChange}
controls={false}
/>
<div className="increment-decrement-container">
<button className="increment-button" onClick={increment}>
<Icon path={mdiPlus} size={"1rem"} color={"#FFFFFF"} />
</button>
<button className="decrement-button" onClick={decrement}>
<Icon path={mdiMinus} size={"1rem"} color={"#FFFFFF"} />
</button>
</div>
</div>
<div>
<Slider
className="slider-helicopter"
minValue={1}
maxValue={4000}
onChange={onChange}
value={inputValue}
/>
</div>
</div>
);
};
export default SliderWidget;
This is the chart. I want to make the litle dot move on the white lines when I use the slider or the increment/decrement buttons
As I said, I run the console.log() and it is working but it is not showing on the chart!

react-vis: x with multiple y to display chart

I have a project that contains several interfaces, and among these interfaces there is an interface to display a set of statistics,
I am using react-vis library.
But the problem is that I want to display the values as shown in the image:
I have one x, y, and y1
The problem is that I have multiple Y values
How can I solve this problem?
import { CSSProperties, FunctionComponent } from 'react';
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
} from 'react-vis';
import { BaseChart } from './data/interfaces';
interface BarChartProps extends BaseChart {
colorValue?: string;
color?: string;
style?: CSSProperties;
barWidth?: number;
stroke?: string;
fill?: string;
}
const BarChart: FunctionComponent<BarChartProps> = ({
colorRange,
colorValue,
color,
data,
style,
barWidth,
width,
height,
stroke,
fill,
}) => {
console.log('datadfdfdf: ', data);
var yValues: any = data?.map((y, index) => {
console.log('ytr: ', y);
return y?.y;
})
var y1Values: any = data?.map((y1, index) => {
console.log('ytr1: ', y1?.y1);
return y1.y1;
})
console.log('yValues: ', yValues);
return (
<>
<XYPlot
margin={{ bottom: 30, left: 20, top: 15 }}
xType='ordinal'
width={width?width:450}
height={height}
>
<VerticalGridLines marginLeft={2} width={5} />
<HorizontalGridLines tickValues={yValues} />
<HorizontalGridLines tickValues={y1Values} />
<XAxis />
<YAxis tickValues={y1Values}
tickSize={12}/>
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={fill ? fill : '#C6E2DD'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={data}
/>
</XYPlot>
</>
);
};
export default BarChart;
I solve my problem by update the code, the updated code from "the part that i added it" comment, just i duplicate YAxis and VerticalBarSeries:
import { CSSProperties, FunctionComponent, useEffect, useState } from 'react';
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
} from 'react-vis';
import { BaseChart } from '../data/interfaces';
interface BarChartProps extends BaseChart {
colorValue?: string;
color?: string;
style?: CSSProperties;
barWidth?: number;
stroke?: string;
fill?: string;
}
const BarChartMultiY: FunctionComponent<BarChartProps> = ({
colorRange,
colorValue,
color,
data,
style,
barWidth,
width,
height,
stroke,
fill,
}) => {
var yValues: any = data?.map((y, index) => {
return y?.y;
})
var y1Values: any = data?.map((y1, index) => {
return y1?.y1;
})
var topography: any[] = [];
const [xtopography, setXTopography] = useState<any[]>([])
useEffect(() => {
data?.map((xy, index) => {
let x: any = xy?.x;
let y: any = xy?.y1;
let xyData: any = { x: x, y: y }
topography.push(xyData);
setXTopography(topography)
return topography;
})
}, [data])
return (
<>
<XYPlot
margin={{ bottom: 30, left: 20, top: 20 }}
xType='ordinal'
width={width ? width : 450}
height={height}
>
<VerticalGridLines marginLeft={2} width={5} />
<HorizontalGridLines tickValues={yValues} />
<XAxis />
<YAxis tickValues={yValues}
tickSize={12} />
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={fill ? fill : '#C6E2DD'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={data}
/>
{/* /// the part that i added it */}
<YAxis tickValues={y1Values}
tickSize={12} />
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={'#96DED1'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={xtopography}
/>
</XYPlot>
</>
);
};
export default BarChartMultiY;
and this link help me also:
enter link description here

Can i read uploaded excel sheet file and loop through its items to update a SharePoint site column choices inside our SPFx

We have the following Panel to manually add/delete choices inside a SharePoint column named Category:-
and here is the related .tsx code for the above Panel:-
import * as React from "react";
import {
Stack,
ProgressIndicator,
Panel,
PanelType,
DefaultButton,
AnimationStyles,
mergeStyles,
TextField,
PrimaryButton,
Dropdown,
IDropdownOption,
MessageBar,
MessageBarType,
Label,
Text,
ILabelStyles,
Link,
IconButton,
} from "office-ui-fabric-react";
import { _copyAndSort } from "../../controls/helpers";
import * as moment from "moment";
import * as strings from "DocumentsViewerWebPartStrings";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import Dropzone from "../../controls/DropzoneExport";
import { IDocument } from "../../models/IDocument";
export interface ICategoriesPanelProps {
themeVariant: IReadonlyTheme | undefined;
showPanel: boolean;
hidePanel: () => void;
categories: string[];
addCategory: (category: string) => void;
removeCategory: (category: string) => void;
castFiletoIDoc: (file: File) => IDocument;
}
export interface ICategoriesPanelState {
busy: boolean;
newCategory: string;
uploadPlaceholders: IDocument[];
}
export default class CategoriesPanel extends React.Component<ICategoriesPanelProps, ICategoriesPanelState> {
constructor(props: ICategoriesPanelProps) {
super(props);
this.state = { busy: true, newCategory: null ,uploadPlaceholders: []};
}
public componentDidMount(): void {
this.setState({ busy: false });
}
private handleNewCategoryFieldChange = (e, newValue: string) => {
this.setState({ newCategory: newValue });
};
private add = async () => {
this.setState({ busy: true });
await this.props.addCategory(this.state.newCategory);
this.setState({ busy: false, newCategory: null });
};
private remove = async (category: string) => {
this.setState({ busy: true });
if (category) {
this.props.removeCategory(category);
}
this.setState({ busy: false });
};
private onDrop = (moreFiles) => {
const placeholders = [...this.state.uploadPlaceholders];
moreFiles.forEach((file, i) => {
const idoc = this.props.castFiletoIDoc(file);
placeholders.push({
...idoc,
key: i.toString(),
});
});
this.setState({ uploadPlaceholders: [...placeholders] });
// Upload the file
//this.props.uploadFolderIcon(moreFiles[0], this.props.folder);
};
private removeDocument = (document: IDocument) => {
this.setState({ uploadPlaceholders: [] });
};
public render(): React.ReactElement<ICategoriesPanelProps> {
const appearingStyle = mergeStyles(AnimationStyles.scaleDownIn100);
return (
<Panel
headerText={strings.ManageCategories}
type={PanelType.medium}
isOpen={this.props.showPanel}
onDismiss={this.props.hidePanel}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel={strings.Close}
isBlocking={true}
hasCloseButton={true}
>
<Stack tokens={{ childrenGap: 15 }}>
<Stack.Item>
<Dropzone
themeVariant={this.props.themeVariant}
onDrop={this.onDrop}
uploadPlaceholders={this.state.uploadPlaceholders}
removeDocument={this.removeDocument}
/>
{/* <PrimaryButton
text={strings.StartUpload}
onClick={this.uploadDocuments}
disabled={this.state.uploading || this.state.uploadFiles.length === 0}
/> */}
</Stack.Item>
<Stack.Item align="end">
{this.props.categories.length} {strings.Categories.toLowerCase()}
</Stack.Item>
<Stack.Item>
<Stack tokens={{ childrenGap: 24 }}>
<Stack.Item
styles={{
root: {
padding: "10px 20px",
backgroundColor: this.props.themeVariant.palette.neutralLight,
},
}}
>
<Stack tokens={{ childrenGap: 4 }}>
<Stack.Item>
{this.props.categories.map((category, i) => (
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>{category}</Stack.Item>
<IconButton
iconProps={{ iconName: "Delete" }}
title={`${strings.Remove} ${category}`}
onClick={() => this.remove(category)}
disabled={this.state.busy}
/>
</Stack>
))}
</Stack.Item>
<Stack.Item>
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>
<TextField
label={strings.AddNewCategory}
name="newCategory"
value={this.state.newCategory}
onChange={this.handleNewCategoryFieldChange}
disabled={this.state.busy}
styles={{ root: { width: 300 } }}
/>
</Stack.Item>
<IconButton
iconProps={{ iconName: "Add" }}
title={`${strings.Add} ${this.state.newCategory}`}
onClick={this.add}
disabled={this.state.busy}
/>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Panel>
);
}
}
currently the SPFx allow to manually add/edit the choices, but my question is how we can read the uploaded excel sheet file (which will contain the choices) inside the DropZone, loop through the choices >> remove existing choices and add the ones inside the sheet? Can anyone advice please?
Here is the DropZoneExport.tsx:-
import * as React from "react";
import { Stack, IStyle } from "office-ui-fabric-react";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import * as strings from "DocumentsViewerWebPartStrings";
import { IDocument } from "../models/IDocument";
import DocumentRow from "./DocumentRow";
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export interface IDropzoneExportProps {
themeVariant: IReadonlyTheme | undefined;
onDrop: (files) => void;
uploadPlaceholders: IDocument[];
removeDocument: (document: IDocument) => void;
}
export interface IDocumentsDropzoneExportState {
files: any[];
}
export default function DropzoneExport(props: IDropzoneExportProps) {
// https://www.npmjs.com/package/react-dropzone
const onDrop = useCallback(async (acceptedFiles) => {
// Do something with the files
console.log("something dropped");
props.onDrop(acceptedFiles);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
maxFiles: 1,
accept: {
"text/csv*": [".csv"],
//acceptedFiles={[".csv, text/csv, application/vnd.ms-excel, application/csv, text/x-csv, application/x-csv, text/comma-separated-values, text/x-comma-separated-values"]}
},
});
const dropBoxStyle: IStyle = {
border: "1px dashed",
borderColor: props.themeVariant.semanticColors.inputBorder,
padding: "0.5rem 1rem",
marginBottom: ".5rem",
backgroundColor: props.themeVariant.palette.neutralQuaternary,
};
return (
<Stack>
<Stack.Item styles={{ root: dropBoxStyle }}>
<div {...getRootProps()} style={{ outline: "none" }}>
<input {...getInputProps()} />
{isDragActive ? <p>{strings.Item_DropHere}</p> : <p>{strings.Item_DropInfo}</p>}
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{props.uploadPlaceholders.map((placeholder) => {
return <DocumentRow document={placeholder} themeVariant={props.themeVariant} removeDocument={props.removeDocument} />;
})}
</div>
</div>
</Stack.Item>
</Stack>
);
}
You can do that, but you may need to use a third-party library to read the excel sheet in the browser. A common solution for that is sheetjs library. There are no built-in helpers in the SPFx framework to parse Excel files, as far as I know.
But you should be able to install sheetjs using npm and then use it by import.

How to get ref of google-maps-react map and panto to latlng

My objective is to pan google-maps-react map to a latlng position, after getting a latlong from react-places-autocomplete when a user selects an address suggestion.
I am facing difficulty in setting ref of map from a child functional component, so that I can call map.panTo(location) in the parent functional component.
Following is my Google-Maps and PlaceAutoComplete child Component:
import React, { useEffect } from 'react';
import { Map, GoogleApiWrapper, Marker } from 'google-maps-react';
import { FormGroup, Label, Input, Spinner, Container, Row, Col } from 'reactstrap';
import PlacesAutocomplete from 'react-places-autocomplete';
const InputAndMap = React.forwardRef((props, ref) => {
return (
<div>
<PlacesAutocomplete
value={props.address}
onChange={props.handleInputChange}
onSelect={props.handleInputSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<FormGroup>
<Label for="exampleSearch">Search Address</Label>
<Input
{...getInputProps({
className: 'location-search-input',
})}
type="search"
name="search"
id="exampleSearch"
placeholder="Enter Store Location"
/>
</FormGroup>
<div className="autocomplete-dropdown-container">
{loading && (
<div>
<Spinner size="sm" color="primary" />
Loading...
</div>
)}
{suggestions.map(suggestion => {
const className = suggestion.active ? 'suggestion-item--active' : 'suggestion-item';
const style = suggestion.active
? { backgroundColor: '#007bff', cursor: 'pointer', color: 'white' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<Row className="mb-3" style={{ width: '100%', height: '200px' }}>
<Col>
<Map
id="google-map"
ref={ref} // <<=== setting ref here
style={{ width: '100%', height: '200px' }}
google={props.google}
zoom={8}
initialCenter={{ lat: 47.444, lng: -122.176 }}
onClick={(t, map, e) => props.updateMarker(e.latLng, map)}
>
{props.markerLatLong && <Marker position={props.markerLatLong} />}
</Map>
</Col>
</Row>
</div>
);
});
export default GoogleApiWrapper({
apiKey: process.env.REACT_APP_GOOGLE_API_KEY,
libraries: ['places'],
})(InputAndMap);
This is my parent component, where I want to call the map panto function.
import React, { useState, useEffect } from 'react';
import { Button, Form, Spinner, Container } from 'reactstrap';
import { Redirect } from 'react-router-dom';
import { geocodeByAddress, getLatLng } from 'react-places-autocomplete';
import firebase from 'firebase/app';
import NavBarMenu from '../components/NavBarMenu';
import InputAndMap from '../components/InputAndMap';
import fire from '../config/fire';
function StoreScreen(props) {
const [isLoading, setIsLoading] = useState(false);
const [markerLatLong, setMarkerLatLong] = useState(null);
const [city, setCity] = useState('');
const [address, setAddress] = useState('');
const [redirect, setRedirect] = useState(false);
const ref = React.createRef();
const handleInputChange = address => {
setAddress(address);
};
const handleInputSelect = address => {
setAddress(address);
geocodeByAddress(address)
.then(results => {
processCity(results);
getLatLng(results[0])
.then(latLng => {
console.log('Success', latLng);
console.log(ref);// ==============> this return {current: null}
// ref.current.panTo(latLng);// ==> So I am unable to call this
})
.catch(error => console.error('Error', error));
})
.catch(error => console.error('Error', error));
};
return (
<div>
<NavBarMenu isShopKeeper />
<Container className="h-100">
<Form onSubmit={handleSubmit}>
<h5 className="text-center">Add Store</h5>
<InputAndMap
ref={ref}
markerLatLong={markerLatLong}
updateMarker={updateMarker}
handleInputChange={handleInputChange}
handleInputSelect={handleInputSelect}
address={address}
/>
{isLoading ? (
<div className="row mx-auto justify-content-center align-items-center flex-column">
<Spinner color="secondary" />
</div>
) : (
<Button
disabled={!markerLatLong || !city || !address}
className="mb-4"
color="primary"
size="lg"
block
>
Add Store
</Button>
)}
</Form>
</Container>
</div>
);
}
export default StoreScreen;
I am also attaching the image for better visualizing my problem.
Map.panTo changes the center of the map to the given LatLng in Maps JavaScript API. Since you are using google-maps-react library, you can use react states as value of the center parameter of this library to change the value of the Map's center everytime the state changes. In my example code below, I use the code from the getting started docs of react-places-autocomplete and incorporated it with a simple google-maps-react code.
Here's how I declare the state of the center which currently have a value:
state = {
center: {
lat: 40.854885,
lng: -88.081807
},
address: ""
};
Here's the handleSelect event from the react-places-autocomplete library where it geocodes the selected place from the autocomplete. Then you can see that I set the state of the center to the latLng of the geocoded address.
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.setState({ center: latLng }))
.catch(error => console.error("Error", error));
};
Here's how I call the Map component of the google-maps-react library where the value of center parameter is the value of the state named center.
<Map
className="map"
google={this.props.google}
onClick={this.onMapClicked}
center={this.state.center}
style={{ height: "100%", position: "relative", width: "100%" }}
zoom={13}
/>
Here's a complete code snippet and the working code on how I incorporated the 2 libraries you are using to change the center of the map everytime you choose an address from autocomplete:
import React, { Component } from "react";
import { Map, GoogleApiWrapper } from "google-maps-react";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
export class MapContainer extends Component {
state = {
center: {
lat: 40.854885,
lng: -88.081807
},
address: ""
};
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.setState({ center: latLng }))
.catch(error => console.error("Error", error));
};
render() {
if (!this.props.loaded) return <div>Loading...</div>;
return (
<div>
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({
getInputProps,
suggestions,
getSuggestionItemProps,
loading
}) => (
<div>
<input
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input"
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<Map
className="map"
google={this.props.google}
center={this.state.center}
style={{ height: "100%", position: "relative", width: "100%" }}
zoom={13}
/>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey: "YOUR_API_KEY"
})(MapContainer);

React Konva: onClick() not firing in functional component

I am trying to create an onClick event on an image in a functional component which is the grand-child of the component where I have my Konva Stage.
However nothing seems to fire.
I have even tried putting the onClick on a div in the component with the stage. Still nothing.
The only way I can handle clicks is if I put an onMouseDown on my stage. But that is not practical in this instance, since I need the click to trigger a change in the grandchild component.
Why will nothing trigger?
Here is the code. I have cut away irrelevant code and functions.
Look in the PanelRect.js component to find the onClick() that is supposed to trigger the select() function.
First off, this is the grandparent, with the Konva stage.
Map.js
import React, { useState } from 'react';
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { keyHandler, KEYPRESS } from 'react-key-handler';
import MapRoomRect from './MapRoomRect';
import { PanelRect } from './PanelRect';
const Map = () => {
return (
<section className='map'>
<h1>Projekt X</h1>
<div className='map_box'>
<div className='map_box_container' >
<div className='map_box_container_stage'>
<Stage name='Stage' width={stageWidth} height={stageHeight} onMouseDown={handleStageMouseDown} onKeyPress={handleKeyDown}>
<Layer
onMouseEnter={handleLayerMouseEnter}
onMouseLeave={handleLayerMouseLeave}
>
<MapRoomRect
name='MapRoomRect'
draggable
visible={false}
/>
<Image name='IconImage' draggable visible={false}/>
</Layer>
</Stage>
</div>
</div>
</div>
</section>
);
}
export default Map;
This is the parent component
MapRoomRect.js
import React from 'react';
import { Rect, Group, Image } from 'react-konva';
import useImage from 'use-image';
import { PanelRect } from './PanelRect';
const MapRoomRect = (props) => {
return (
<Group
name='RoomGroup'
roomType='RoomType'
id='0002'
onDragEnd={() => { }}
position={{ x: 200, y: 200 }}
draggable
visible={false}
size={{ width: roomSize, height: roomSize }}
>
<Rect
name='RoomGroupBackground'
size={{ width: roomSize, height: roomSize }}
opacity={0.3}
fill='red'
/>
<PanelRect
name='PanelGroup'
onDragEnd={() => { }}
size={{ width: iconSize, height: iconSize2 }}
x={0}
y={0} />
</Group>
)
}
export default MapRoomRect;
The child / grand-child component:
PanelRect.js
import React, { useState } from 'react';
import { Rect, Group, Image } from 'react-konva';
import useImage from 'use-image';
// Images
import backgroundImage from '../../images/tp6icon.png';
import iconTestImage from '../../images/lo1.png';
import iconTestImage2 from '../../images/lo2.png';
import iconTestImage3 from '../../images/lo3.png';
import iconTestImage4 from '../../images/lo4.png';
import iconTestImage5 from '../../images/lo5.png';
import MapContext from './MapContext';
const PanelRect = (props) => {
// Hooks
const [mapState, setMapState] = useState(mapContext);
let los = [0, 0, 0, 3, 4];
// Hooks
const [testImage] = useImage(iconTestImage);
const [testImage2] = useImage(iconTestImage2);
const [testImage3] = useImage(iconTestImage3);
const [testImage4] = useImage(iconTestImage4);
const [testImage5] = useImage(iconTestImage5);
var images = [testImage,testImage2,testImage3,testImage4,testImage5];
// Constants that does not change
const roomSize = 5;
const roomSize2 = 7;
const iconSize = 2;
const iconSize2 = 2;
const select = (e) => {
console.log('Hi! "e" is now: ' + e + ' hmmm');
}
return (
<Group
name='PanelGroup'
roomType='PanelType'
id='0003'
onDragEnd={() => { }}
position={{ x: 0, y: 0 }}
draggable
size={{ width: roomSize, height: roomSize2 }}
>
<Rect
name='PanelGroupBackground'
size={{ width: roomSize, height: roomSize2 }}
opacity={0.9}
fill='blue'
/>
<Image name='IconImage' lid='LO1' lo='0' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[0]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={0} y={0} />
<Image name='IconImage' lid='LO2' lo='1' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[1]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={2} y={0} />
<Image name='IconImage' lid='LO3' lo='2' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[2]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={0} y={2} />
<Image name='IconImage' lid='LO4' lo='3' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[3]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={2} y={2} />
<Image name='IconImage' lid='LO5' lo='4' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[4]]} size={{ width: iconSize, height: iconSize2 }} draggable={false} x={0} y={4} />
</Group>
)
}
export { PanelRect }
I don't get any error messages or warnings. It's just that nothing happens when I click. No matter where I put the onClick().
Edit 7th aug 2019
I have created a sandbox that shows the problem.
Click "create red square"
Click on the red square.
Click "edit red square"
Click on the blue square inside
Click "edit blue square"
Click on the tiles with numbers on in the blue square.
It is the onClick() on these that I want to fire, and get access to the synthetic event object so I can access the target.
https://codesandbox.io/s/quirky-ramanujan-p53b9?fontsize=14

Resources