Error in showing the tooltip for Scatter chart (React ApexCharts) - reactjs

My scatter chart is rendered correctly. I have a problem with the custom tooltip function I have created to show the details of the data points on the chart. When I hover the tooltip, it shows an error as
"Uncaught TypeError: Cannot read properties of undefined "
I get the response data for the selected dates from my server using api call and my chart is rendered accordingly. From the received response I have created a dropdown with all employeeName as options to select. When selecting an employeeName I want to show the data point of that person in the scatter chart. My code is working fine for all these functions. But the problem is with the chart options configured. I have given the property type="numeric" for both the x-axis and y-axis in the options object.
If this type="numeric" property is specified, my chart is working fine when I select individual employees from the dropdown but the tooltip shows the error as I mentioned.
If the type="numeric" property is not specified, my chart does not show the data points correctly when I select the employee name, but the tooltip works fine. When I hover over the tooltip for the shown data points does not throw any error.
Below I have given sample data.
import React, { useState, useEffect } from "react";
import axios from "axios";
import ApexCharts from "react-apexcharts";
import "./styles.css";
const ScatterChart = () => {
const[chartData,setChartData]=useState([]);
const [employeeName, setEmployeeName] = useState("");
const [employeeNames, setEmployeeNames] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const data = [
{
employeeName: "John Doe",
percentageOfTasksCompleted: 35,
percentageOfTasksCompletedInTime: 25
},
{
employeeName: "Jane Doe",
percentageOfTasksCompleted: 45,
percentageOfTasksCompletedInTime: 30
},
{
employeeName: "Bob Smith",
percentageOfTasksCompleted: 55,
percentageOfTasksCompletedInTime: 40
}
];
const newData = data.map((d) => {
return {
x: d.percentageOfTasksCompleted,
y: d.percentageOfTasksCompletedInTime,
employeeName: d.employeeName,
};
});
const employeeNames = [...new Set(newData.map((d) => d.employeeName))];
setEmployeeNames(employeeNames);
setChartData(newData);
};
useEffect(() => {
setFilteredData(
employeeName !== null && employeeName !== ""
? chartData.filter((d) => d.employeeName === employeeName)
: chartData
);
}, [employeeName, employeeNames, chartData]);
const options = {
chart: {
type: "scatter",
toolbar: {
show: true,
tools: {
download: true,
selection: false,
zoom: false,
zoomin: false,
zoomout: false,
pan: false,
reset: false | '<img src="/static/icons/reset.png" width="20">',
customIcons: [],
},
export: {
csv: {
filename: "Scatter plot by supervisor",
columnDelimiter: ",",
headerCategory: "category",
headerValue: "value",
dateFormatter(timestamp) {
return new Date(timestamp).toDateString();
},
},
svg: {
filename: "Scatter plot by supervisor",
},
png: {
filename: "Scatter plot by supervisor",
},
},
},
},
xaxis: {
title: {
text: "Percentage of Tasks Completed",
align: "center",
verticalAlign: "top",
floating: true,
offsetY: 100,
style: {
fontSize: "14px",
fontWeight: "bold",
color: "#666",
},
},
tickAmount: 6,
min: 0,
max: 100,
type: "numeric",
},
yaxis: {
title: {
text: "Percentage of Tasks Completed In Time",
align: "center",
verticalAlign: "top",
floating: true,
offsetX: -5,
style: {
fontSize: "14px",
fontWeight: "bold",
color: "#666",
},
},
tickAmount: 6,
min: 0,
max: 100,
type: "numeric",
},
tooltip: {
custom: function ({ dataPointIndex }) {
return (
'<div class="tooltip">' +
"<p class='employee-name'>Employee Name : " +
filteredData[dataPointIndex].employeeName +
"</p>" +
"<p class='percentage-complete'>Percentage of Tasks Completed : " +
filteredData[dataPointIndex].x +
"%</p>" +
"<p class='percentage-in-time'>Percentage of Tasks Completed In Time : " +
filteredData[dataPointIndex].y +
"%</p>" +
"</div>"
);
},
},
};
return (
<div>
<div style={{ display: "flex", alignItems: "center", padding: "10px" }}>
<input
type="date"
value={fromDate}
onChange={(e) => setFromDate(e.target.value)}
style={{ marginRight: "10px", padding: "5px" }}
/>
<input
type="date"
value={toDate}
onChange={(e) => setToDate(e.target.value)}
style={{ marginRight: "10px", padding: "5px" }}
/>
<select
id="employee-name"
value={employeeName}
onChange={(e) => setEmployeeName(e.target.value)}
style={{ marginRight: "10px", padding: "5px" }}
>
<option value="">All</option>
{employeeNames.map((name) => (
<option key={name} value={name}>
{name}
</option>
))}
</select>
<button
onClick={() => {
handleRefresh();
setEmployeeName("");
}}
style={{ padding: "5px 10px" }}
>
Refresh
</button>
</div>
{filteredData !== undefined && filteredData !== null ? (
<ApexCharts
options={options}
series={[{ data: filteredData }]}
type="scatter"
width="600"
height="500"
/>
) : null}
</div>
);
};
export default ScatterChart;

Related

React component not passing value to correct component

I have a simple React app that uses react-dnd to build a grid of draggable squares that have an initial color and text value and two components to change the color and change the text.
The color change (via ColorPicker2, using react-color library) works okay. The text change (using TextInput from #carbon/react) doesn't work as desired.
I thought I was applying the same logic with both components, but whilst the color-picker updates the color and retains that color when the square is moved, the text seems to render inside the TextInput and not the square itself and I can't figure out the logical difference.
The Code Sandbox is here: https://codesandbox.io/s/wild-waterfall-z8s1de?file=/src/App.js
This is the current code:
ColorPicker2.js
import "./styles.css";
import React from "react";
import { BlockPicker } from "react-color";
const presetColors = ["#9E9E9E", "#4CAF50", "#FFEB3B", "#F44336", "#2196F3"];
const ColorPicker2 = (props) => {
const handleChangeComplete = (color) => {
if (props.updateColor) {
props.updateColor(color.hex);
}
};
return (
<div className="palette">
<BlockPicker
className="palette"
colors={presetColors}
onChangeComplete={handleChangeComplete}
presetColors={Object.values(presetColors)}
color={props.currentColor}
/>
</div>
);
};
export default ColorPicker2;
App.js
import "./styles.css";
import React, { useState } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import edit from "./edit.svg";
import palette from "./palette.svg";
import ColorPicker2 from "./ColorPicker2";
import { TextInput } from "#carbon/react";
const DndWrapper = (props) => {
return <DndProvider backend={HTML5Backend}>{props.children}</DndProvider>;
};
const DraggableSquare = ({ index, text, color, moveSquare }) => {
const [{ isDragging }, drag] = useDrag({
type: "square",
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});
const [isHovered, setIsHovered] = useState(false);
const [, drop2] = useDrop({
accept: "square",
drop: (item, monitor) => {
const didDrop = monitor.didDrop();
if (!didDrop) {
moveSquare(item.index, index);
}
},
hover: (item, monitor) => {
setIsHovered(monitor.isOver());
},
collect: (monitor) => {
setIsHovered(monitor.isOver());
}
});
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
const [isTextInputOpen, setIsTextInputOpen] = useState(false);
const [newText, setNewText] = useState(text);
const opacity = isDragging ? 0.5 : 1;
return (
<div className="square-div" ref={drop2}>
<div
className="grey-square"
ref={drag}
style={{
opacity,
backgroundColor: color,
width: "200px",
height: "200px",
textAlign: "center",
paddingTop: "30px",
position: "relative",
border: isHovered ? "3px solid blue" : "none",
borderRadius: "5px",
}}
onMouseOver={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img
src={edit}
onClick={() => {
setIsTextInputOpen(!isTextInputOpen);
if (!isTextInputOpen) {
moveSquare(index, newText, color, undefined);
}
}}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "5px",
top: "5px"
}}
alt="edit icon"
/>
{isTextInputOpen && (
<TextInput
id="newtext"
labelText=""
value={newText}
onChange={(e) => setNewText(e.target.value)}
/>
)}
<img
src={palette}
onClick={() => setIsPaletteOpen(!isPaletteOpen)}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "25px",
top: "5px"
}}
alt="palette icon"
/>
{isPaletteOpen && (
<ColorPicker2
className="palette"
currentColor={color}
updateColor={(newColor) =>
moveSquare(index, index, newText, newColor)
}
/>
)}
</div>
</div>
);
};
const Grid = () => {
const [grid, setGrid] = useState([
{ text: "1", color: "grey" },
{ text: "2", color: "grey" },
{ text: "3", color: "grey" },
{ text: "4", color: "grey" },
{ text: "5", color: "grey" },
{ text: "6", color: "grey" },
{ text: "7", color: "grey" },
{ text: "8", color: "grey" },
{ text: "9", color: "grey" },
{ text: "10", color: "grey" },
{ text: "11", color: "grey" },
{ text: "12", color: "grey" },
{ text: "13", color: "grey" },
{ text: "14", color: "grey" },
{ text: "15", color: "grey" }
]);
const moveSquare = (fromIndex, toIndex, newText, newColor) => {
setGrid((grid) => {
const newGrid = [...grid];
const item = newGrid[fromIndex];
newGrid.splice(fromIndex, 1);
newGrid.splice(toIndex, 0, {
text: newText || item.text,
color: newColor || item.color
});
return newGrid;
});
};
return (
<>
<DndWrapper>
<div
className="grid"
style={{
display: "grid",
gridTemplateColumns: "repeat(5, 190px)",
gridGap: "15px",
gridColumnGap: "20px",
gridRowGap: "10px",
position: "absolute"
}}
>
{grid.map((square, index) => (
<DraggableSquare
key={index}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>
))}
</div>
</DndWrapper>
</>
);
};
export default Grid;
Any thoughts from fresh eyes would be helpful.
I think this is just a simple issue with using index as the key while mapping. Adjusting your code pen to have a unique key fixed it for me but the input text is not being saved anywhere so returned to the default text={square.text} when moved as expected.
Unique Id in objects:
const [grid, setGrid] = useState([
{ text: "1", color: "grey", id: crypto.randomUUID() },
{ text: "2", color: "grey", id: crypto.randomUUID() },
{ text: "3", color: "grey", id: crypto.randomUUID() },...])
Adding key to mapped object:
{grid.map((square, index) => (
<DraggableSquare
key={square.id}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>}

Trying to get Yup validation working on Controller and custom components based on react-select for overall form validity and error style

New to React here so I do appreciate any insight others may have on this.
I am trying to validate a custom select component based on react-select. I am having trouble with the following:
Showing the error styling for the select field while also having it work for the overall form validity. I am using react-hook-form and Yup validation.
I have a custom component here called CreditAppCustomSelect:
import { TextField } from '#mui/material';
import Image from 'next/image';
import React from 'react';
import Select, { components } from 'react-select';
import Arrow from '../../assets/images/icon/Icon.png';
export interface CustomStyles {
isHover: boolean;
isFocused: boolean;
isSelected: boolean;
}
const colourStyles = (isError: boolean) => ({
control: (styles: object, { isHover, isFocused }: CustomStyles) => {
return {
...styles,
borderRadius: '0px',
backgroundColor: 'white',
border: isFocused
? '0.1875rem solid #00B0C7'
: isError
? '0.1875rem solid #F6AE55'
: '0.1875rem solid #000000',
boxShadow: isHover ? '0.1875rem solid #00B0C7' : undefined,
'&:hover': {
border: isHover ? '0.1875rem solid #00B0C7' : undefined,
},
fontFamily: 'Avenir',
fontStyle: 'normal',
fontWeight: 500,
fontSize: '1.25rem',
/* or 30px */
color: '#969696',
textTransform: 'capitalize',
'#media only screen and (max-width:991px)': {
// #ts-ignore
...styles['#media only screen and (max-width:991px)'],
height: '3rem',
},
minHeight: '3rem',
};
},
dropdownIndicator: (
styles: object,
{ isFocused, isSelected, isHover }: CustomStyles
) => {
return {
color: '#242424',
marginInline: '0.2rem',
width: '0.625rem',
minWidth: '0.5rem',
};
},
option: (styles: object, { isFocused, isSelected }: CustomStyles) => {
return {
...styles,
backgroundColor: isFocused ? '#BBE8EE' : undefined,
color: 'black',
cursor: 'default',
'&:active': {
backgroundColor:
isSelected || isFocused ? 'rgba(176, 117, 251, 0.3)' : undefined,
},
};
},
menu: (styles: object) => {
return {
...styles,
boder: '0.0625rem solid #242424',
outline: '0.0625rem solid #242424',
color: '#242424',
// top: "0rem",
margin: '0 0.0625rem',
boxSizing: 'border-box',
width: 'calc(100% - 2px)',
borderRadius: '0px',
zIndex: 99,
fontFamily: 'Avenir',
fontStyle: 'normal',
fontWeight: 500,
fontSize: '1.25rem',
textTransform: 'capitalize',
};
},
input: (styles: object) => ({
...styles,
fontSize: '1.25rem',
}),
placeholder: (styles: object) => {
return {
...styles,
fontFamily: 'Avenir',
fontStyle: 'normal',
fontWeight: 500,
fontSize: '1.25rem',
/* or 30px */
color: '#969696',
textTransform: 'capitalize',
};
},
container: (
styles: object,
{ isFocused, isSelected, isHover }: CustomStyles
) => {
return {
...styles,
// borderRadius: "4px",
outline: 'none',
height: '3rem',
};
},
singleValue: (styles: object, { isFocused, isSelected }: CustomStyles) => {
return {
...styles,
fontSize: '1.25rem',
color: '#211e3b',
};
},
valueContainer: (styles: object) => {
return {
...styles,
paddingLeft: '0.75rem',
};
},
});
const CreditAppCustomSelect = (props: any) => (
<div className='credit-app-select-container'>
<label className={props.isError ? 'error' : ''}>
{props.label}
<Select
{...props}
styles={colourStyles(props.isError)}
components={{
IndicatorSeparator: () => null,
// eslint-disable-next-line #next/next/no-img-element
DropdownIndicator: (props) => (
<components.DropdownIndicator {...props}>
<Image src={Arrow} alt='down' />
</components.DropdownIndicator>
),
}}
isSearchable={false}
placeholder={props.placeholder}
onChange={props.onChange}
value={props.value} // This can be set like this as a result of the change
/>
{props.isError ? <span>{props.message}</span> : ''}
</label>
</div>
);
export default CreditAppCustomSelect;
in my form schema I have the following - one works for overall form validity and the other works for error styling - I know I can't use both so not sure what to do?:
// this works for overall form validity based on having a selected value (isValid)
employeeNumbers: Yup.object().shape({
label: Yup.string().nullable().required('Required'),
value: Yup.string().nullable().required('Required'),
}),
// the following works for the error styling and message but I know I can't do both this and the above
// employeeNumbers: Yup.string().nullable().required('Required'),
I am using a Controller around the CreditAppCustomSelect component:
<Controller
defaultValue=''
control={control}
{...register('employeeNumbers', { required: true })}
render={({ field }: any) => (
<CreditAppCustomSelect
{...field}
label='Number of Employees/Contractors'
name='employeeNumbers'
placeholder='Select range'
isError={
errors.employeeNumbers &&
errors.employeeNumbers.type === 'required'
}
message={'Required'}
options={numberEmployeesOptions}
/>
)}
/>
The options look like this
const numberEmployeesOptions = [
{ label: '1 - 4', value: '1 - 4' },
{ label: '5 - 9', value: '5 - 9' },
{ label: '10 - 19', value: '10 - 19' },
{ label: '20 - 49', value: '20 - 49' },
{ label: '50 - 99', value: '50 - 99' },
{ label: '100+', value: '100+' },
];
in the interface for the form I have the following for employee numbers:
employeeNumbers: { label: string; value: string };
I have not posted the entire form since it is so long but I am using mode:onBlur for the overall form and working in tsx files. Just trying to reconcile how I can achieve both form validity as well as error styling. I have searched for related issues in StackOverflow and I know there probably are solutions but have not yet managed to solve this and have been spinning my wheels for a while.
Thanks for any suggestions anyone may have!

Material Table rowStyle background color change based on cell value in

I am trying to have the color of the row change colors based on the priority number. For example, if the priority is 4 the color of the entire row should be blue. The problem is that I'm unsure of how to achieve this. I know Material Table assigns ID's to the rows, but I have no way of accessing them. Below is what I have come up with so far. I need options[backgroundColor] to be set based on the priority number. I have a codesandbox here as well https://codesandbox.io/s/notifications-material-ui-spq45
import React from "react";
import { Container } from "react-bootstrap";
import MaterialTable from "material-table";
import FilterListIcon from "#material-ui/icons/FilterList";
import SearchIcon from "#material-ui/icons/Search";
import FirstPage from "#material-ui/icons/FirstPage";
import LastPage from "#material-ui/icons/LastPage";
import ChevronLeft from "#material-ui/icons/ChevronLeft";
import ChevronRight from "#material-ui/icons/ChevronRight";
import CloseIcon from "#material-ui/icons/Close";
function App() {
//loop through the array of priority numbers, and display color based on number
function colors(num) {
for(let i = 0; i < num.length; i++) {
if (num[i] === 4) {
options.rowStyle.backgroundColor = "blue";
}
if (num[i] === 3) {
options.rowStyle.backgroundColor = "red";
}
console.log(num[i]);
}
}
let data = [
{
date: "06-29-2021",
subject: "CANBUS LOSS, Automatic Reboot!",
message:
"SCMs CANBUS LOSS for more than 15 min, Automatic System Reboot!",
category: "System",
priority: 4,
error: "SYS0080"
},
{
date: "06-28-2021",
subject: "Reboot!",
message: "Automatic System Reboot!",
category: "Alarm",
priority: 3,
error: "SYS0090"
},
{
date: "06-25-2021",
subject: "Testing!",
message: "Generator not running!",
category: "Generator",
priority: 2,
error: "SYS0050"
}
];
let columns = [
{ title: "Date", field: "date" },
{ title: "Subject", field: "subject" },
{ title: "Message", field: "message" },
{ title: "Category", field: "category" },
{ title: "Priority Level", field: "priority" },
{ title: "Error Code", field: "error" }
];
let options = {
filtering: false,
sorting: true,
rowStyle: {
fontFamily: "Mulish-Regular",
backgroundColor: ""
},
headerStyle: {
fontFamily: "Mulish-Regular",
fontSize: "1.1em",
fontWeight: "600",
backgroundColor: "#D1D1D8"
},
searchFieldStyle: {
fontFamily: "Mulish-Regular"
}
};
// Loop through all the data and find the priority number, and put it in an array
let map = data.map((x) => x.priority);
console.log(map);
colors(map);
return (
<>
<Container>
<MaterialTable
title=""
columns={columns}
data={data}
options={options}
icons={{
Filter: (props) => <FilterListIcon style={{ fill: "#2D3155 " }} />,
Search: (props) => <SearchIcon style={{ fill: "#2D3155 " }} />,
FirstPage: (props) => <FirstPage style={{ fill: "#2D3155 " }} />,
LastPage: (props) => <LastPage style={{ fill: "#2D3155 " }} />,
NextPage: (props) => <ChevronRight style={{ fill: "#2D3155 " }} />,
PreviousPage: (props) => (
<ChevronLeft style={{ fill: "#2D3155 " }} />
),
SortArrow: (props) => (
<FilterListIcon
style={{ fill: "#2D3155 ", fontSize: "1.4em", margin: ".4em" }}
/>
),
ResetSearch: (props) => <CloseIcon style={{ fill: "#2D3155 " }} />
}}
/>
</Container>
</>
);
}
export default App;
As you can read in the docs, rowStyle accepts an object or a function.
Hence, you can set rowStyle using a function that receives rowData as parameter, an example:
const rowBackgroundColors = {
"2": "yellow", // just for example, remove it if you don't need
"3": "orange",
"4": "red",
};
const options = {
// ...
rowStyle: (rowData) => {
return {
fontFamily: "Mulish-Regular",
backgroundColor: rowBackgroundColors[rowData.priority] ?? "#fff",
};
},
// ...
};

How to create dropdown list with description in React

I am trying to create a dropdown list with description in React. For reference you can see the image below:
Is there any other way using bootstrap or Material-UI so I can achieve this?
I am using react-select npm package for dropdown list. you can find live link and code below:
https://codesandbox.io/embed/react-select-selected-option-background-color-forked-jpu99?fontsize=14&hidenavigation=1&theme=dark
const colourOptions = [
{ value: "red", label: "Red" ,description:"Test description for red"},
{ value: "green", label: "Green", description:"Test description for green" },
{ value: "blue", label: "Blue", description:"Test description for blue" }
];
//for styling
const colourStyles = {
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
// const color = chroma(data.color);
console.log({ data, isDisabled, isFocused, isSelected });
return {
...styles,
backgroundColor: isFocused ? "#00A3BE" : "#191D2F",
font: "Segoe UI",
color: "#F9FAFC"
};
}
};
export default () => (
<Select
defaultValue={colourOptions[1]}
label="Single select"
options={colourOptions}
styles={colourStyles}
/>
);
You can override the Option component and provide your own Option that can display both title and description:
import Select, { components } from "react-select";
const colourStyles = {
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
return {
...styles,
backgroundColor: isFocused ? "#00A3BE" : "",
color: isFocused ? "#F9FAFC" : "#191D2F",
display: "flex",
paddingLeft: 0,
"& .left": {
display: "flex",
justifyContent: "center",
width: 60,
marginTop: 3
},
"& .right": {
width: "100%"
},
"& .right > .title": {
display: "block",
margin: "5px 0"
}
};
}
};
const Option = (props) => {
return (
<components.Option {...props}>
<div className="left">{props.isSelected ? "✔" : ""}</div>
<div className="right">
<strong className="title">{props.data.label}</strong>
<div>{props.data.description}</div>
</div>
</components.Option>
);
};
export default () => (
<Select
defaultValue={colourOptions[1]}
label="Single select"
options={colourOptions}
styles={colourStyles}
components={{ Option }}
/>
);
Live Demo

Click handler issue in React

I have written useState. The console log works, but even though the filter is correct, it doesn't change after the click. May I attach any clickHandler as a constant to an icon or div or section?
The <Task /> is added in the main container.
import React, { useState } from "react";
import { FaTimes } from "react-icons/fa";
const Task = () => {
const deleteTask = (id) => {
setTasks(tasks.filter((task) => task.id !== id));
console.log("deleted");
};
const [tasks, setTasks] = useState([
{ id: 1, text: "Football", reminder: true, day: " -March 22th at 20:30" },
{
id: 2,
text: "Lunch Meeting",
day: " -May 4th at 16:45",
reminder: true,
},
{
id: 3,
text: "Holliday in Bahamas",
reminder: true,
day: " -July 25th at 00:00",
},
]);
return (
<div>
{tasks.map((task) => (
<h3
key={task.id}
style={{
margin: "20px",
backgroundColor: "#cee1f4",
justifyContent: "center",
}}
>
{task.text}
<FaTimes
onClick={deleteTask}
style={{
color: " red",
paddingLeft: "370px",
position: "absolute",
display: "flex",
margin: "-8px 57px",
cursor: "pointer",
}}
/>
<p>{task.day}</p>
</h3>
))}
</div>
);
};
export { Task };
In <FaTimes> component, your onClick does not pass an ID, as is required by that function's parameter input.
This should work:
<FaTimes
onClick={() => deleteTask(task.id)}
style={{
color: " red",
paddingLeft: "370px",
position: "absolute",
display: "flex",
margin: "-8px 57px",
cursor: "pointer",
}}
/>

Resources