Failing to Compile React Native Application - reactjs

I am building the ABP.IO react-front end(with no modifications other than Environment.js) to deploy however it is failing. I have already tried installing the packages according to some other posts such as.
I am running expo build:web --no-pwa and get the following:
Failed to compile.
C:/Users/INeedHelpPlz/Desktop/myProject/react-native/node_modules/#codler/react-native-keyboard-aware-scroll-view/lib/KeyboardAwareHOC.js 13:12
Module parse failed: Unexpected token (13:12)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| } from 'react-native'
| import { isIphoneX } from 'react-native-iphone-x-helper'
> import type { KeyboardAwareInterface } from './KeyboardAwareInterface'
|
| const _KAM_DEFAULT_TAB_BAR_HEIGHT: number = isIphoneX() ? 83 : 49
Error: C:/Users/INeedHelpPlz/Desktop/myProject/react-native/node_modules/#codler/react-native-keyboard-aware-scroll-view/lib/KeyboardAwareHOC.js 13:12
Module parse failed: Unexpected token (13:12)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| } from 'react-native'
| import { isIphoneX } from 'react-native-iphone-x-helper'
> import type { KeyboardAwareInterface } from './KeyboardAwareInterface'
|
| const _KAM_DEFAULT_TAB_BAR_HEIGHT: number = isIphoneX() ? 83 : 49
at C:\#expo\xdl#59.0.14\src\Webpack.ts:280:23
at finalCallback (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\webpack\lib\Compiler.js:257:39)
at C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\webpack\lib\Compiler.js:273:13
at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:33:1)
at AsyncSeriesHook.lazyCompileHook (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\Hook.js:154:20)
at onCompiled (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\webpack\lib\Compiler.js:271:21)
at C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\webpack\lib\Compiler.js:681:15
at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:6:1)
at AsyncSeriesHook.lazyCompileHook (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\Hook.js:154:20)
at C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\webpack\lib\Compiler.js:678:31
at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:9:1)
at AsyncSeriesHook.lazyCompileHook (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\Hook.js:154:20)
at C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\webpack\lib\Compilation.js:1423:35
at AsyncSeriesHook.eval [as callAsync] (eval at create (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:9:1)
at AsyncSeriesHook.lazyCompileHook (C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\tapable\lib\Hook.js:154:20)
at C:\Users\INeedHelpPlz\AppData\Roaming\npm\node_modules\expo-cli\node_modules\webpack\lib\Compilation.js:1414:32
Edit, adding code for KeyboardAwareHOC.js
/* #flow */
import React from 'react'
import {
Keyboard,
Platform,
UIManager,
TextInput,
findNodeHandle,
Animated
} from 'react-native'
import { isIphoneX } from 'react-native-iphone-x-helper'
import type { KeyboardAwareInterface } from './KeyboardAwareInterface'
const _KAM_DEFAULT_TAB_BAR_HEIGHT: number = isIphoneX() ? 83 : 49
const _KAM_KEYBOARD_OPENING_TIME: number = 250
const _KAM_EXTRA_HEIGHT: number = 75
const supportedKeyboardEvents = [
'keyboardWillShow',
'keyboardDidShow',
'keyboardWillHide',
'keyboardDidHide',
'keyboardWillChangeFrame',
'keyboardDidChangeFrame'
]
const keyboardEventToCallbackName = (eventName: string) =>
'on' + eventName[0].toUpperCase() + eventName.substring(1)
const keyboardAwareHOCTypeEvents = supportedKeyboardEvents.reduce(
(acc: Object, eventName: string) => ({
...acc,
[keyboardEventToCallbackName(eventName)]: Function
}),
{}
)
export type KeyboardAwareHOCProps = {
viewIsInsideTabBar?: boolean,
resetScrollToCoords?: {
x: number,
y: number
},
enableResetScrollToCoords?: boolean,
enableAutomaticScroll?: boolean,
extraHeight?: number,
extraScrollHeight?: number,
keyboardOpeningTime?: number,
onScroll?: Function,
update?: Function,
contentContainerStyle?: any,
enableOnAndroid?: boolean,
innerRef?: Function,
...keyboardAwareHOCTypeEvents
}
export type KeyboardAwareHOCState = {
keyboardSpace: number
}
export type ElementLayout = {
x: number,
y: number,
width: number,
height: number
}
export type ContentOffset = {
x: number,
y: number
}
export type ScrollPosition = {
x: number,
y: number,
animated: boolean
}
export type ScrollIntoViewOptions = ?{
getScrollPosition?: (
parentLayout: ElementLayout,
childLayout: ElementLayout,
contentOffset: ContentOffset
) => ScrollPosition
}
export type KeyboardAwareHOCOptions = ?{
enableOnAndroid: boolean,
contentContainerStyle: ?Object,
enableAutomaticScroll: boolean,
extraHeight: number,
extraScrollHeight: number,
enableResetScrollToCoords: boolean,
keyboardOpeningTime: number,
viewIsInsideTabBar: boolean,
refPropName: string,
extractNativeRef: Function
}
function getDisplayName(WrappedComponent: React$Component) {
return (
(WrappedComponent &&
(WrappedComponent.displayName || WrappedComponent.name)) ||
'Component'
)
}
const ScrollIntoViewDefaultOptions: KeyboardAwareHOCOptions = {
enableOnAndroid: false,
contentContainerStyle: undefined,
enableAutomaticScroll: true,
extraHeight: _KAM_EXTRA_HEIGHT,
extraScrollHeight: 0,
enableResetScrollToCoords: true,
keyboardOpeningTime: _KAM_KEYBOARD_OPENING_TIME,
viewIsInsideTabBar: false,
// The ref prop name that will be passed to the wrapped component to obtain a ref
// If your ScrollView is already wrapped, maybe the wrapper permit to get a ref
// For example, with glamorous-native ScrollView, you should use "innerRef"
refPropName: 'ref',
// Sometimes the ref you get is a ref to a wrapped view (ex: Animated.ScrollView)
// We need access to the imperative API of a real native ScrollView so we need extraction logic
extractNativeRef: (ref: Object) => {
// getNode() permit to support Animated.ScrollView automatically
// see https://github.com/facebook/react-native/issues/19650
// see https://stackoverflow.com/questions/42051368/scrollto-is-undefined-on-animated-scrollview/48786374
if (ref.getNode) {
return ref.getNode()
} else {
return ref
}
}
}
function KeyboardAwareHOC(
ScrollableComponent: React$Component,
userOptions: KeyboardAwareHOCOptions = {}
) {
const hocOptions: KeyboardAwareHOCOptions = {
...ScrollIntoViewDefaultOptions,
...userOptions
}
return class
extends React.Component<KeyboardAwareHOCProps, KeyboardAwareHOCState>
implements KeyboardAwareInterface {
_rnkasv_keyboardView: any
keyboardWillShowEvent: ?Function
keyboardWillHideEvent: ?Function
position: ContentOffset
defaultResetScrollToCoords: ?{ x: number, y: number }
mountedComponent: boolean
handleOnScroll: Function
state: KeyboardAwareHOCState
static displayName = `KeyboardAware${getDisplayName(ScrollableComponent)}`
// HOC options are used to init default props, so that these options can be overriden with component props
static defaultProps = {
enableAutomaticScroll: hocOptions.enableAutomaticScroll,
extraHeight: hocOptions.extraHeight,
extraScrollHeight: hocOptions.extraScrollHeight,
enableResetScrollToCoords: hocOptions.enableResetScrollToCoords,
keyboardOpeningTime: hocOptions.keyboardOpeningTime,
viewIsInsideTabBar: hocOptions.viewIsInsideTabBar,
enableOnAndroid: hocOptions.enableOnAndroid
}
constructor(props: KeyboardAwareHOCProps) {
super(props)
this.keyboardWillShowEvent = undefined
this.keyboardWillHideEvent = undefined
this.callbacks = {}
this.position = { x: 0, y: 0 }
this.defaultResetScrollToCoords = null
const keyboardSpace: number = props.viewIsInsideTabBar
? _KAM_DEFAULT_TAB_BAR_HEIGHT
: 0
this.state = { keyboardSpace }
}
componentDidMount() {
this.mountedComponent = true
// Keyboard events
if (Platform.OS === 'ios') {
this.keyboardWillShowEvent = Keyboard.addListener(
'keyboardWillShow',
this._updateKeyboardSpace
)
this.keyboardWillHideEvent = Keyboard.addListener(
'keyboardWillHide',
this._resetKeyboardSpace
)
} else if (Platform.OS === 'android' && this.props.enableOnAndroid) {
this.keyboardWillShowEvent = Keyboard.addListener(
'keyboardDidShow',
this._updateKeyboardSpace
)
this.keyboardWillHideEvent = Keyboard.addListener(
'keyboardDidHide',
this._resetKeyboardSpace
)
}
supportedKeyboardEvents.forEach((eventName: string) => {
const callbackName = keyboardEventToCallbackName(eventName)
if (this.props[callbackName]) {
this.callbacks[eventName] = Keyboard.addListener(
eventName,
this.props[callbackName]
)
}
})
}
componentDidUpdate(prevProps: KeyboardAwareHOCProps) {
if (this.props.viewIsInsideTabBar !== prevProps.viewIsInsideTabBar) {
const keyboardSpace: number = this.props.viewIsInsideTabBar
? _KAM_DEFAULT_TAB_BAR_HEIGHT
: 0
if (this.state.keyboardSpace !== keyboardSpace) {
this.setState({ keyboardSpace })
}
}
}
componentWillUnmount() {
this.mountedComponent = false
this.keyboardWillShowEvent && this.keyboardWillShowEvent.remove()
this.keyboardWillHideEvent && this.keyboardWillHideEvent.remove()
Object.values(this.callbacks).forEach((callback: Object) =>
callback.remove()
)
}
getScrollResponder = () => {
return (
this._rnkasv_keyboardView &&
this._rnkasv_keyboardView.getScrollResponder &&
this._rnkasv_keyboardView.getScrollResponder()
)
}
scrollToPosition = (x: number, y: number, animated: boolean = true) => {
const responder = this.getScrollResponder()
responder && responder.scrollResponderScrollTo({ x, y, animated })
}
scrollToEnd = (animated?: boolean = true) => {
const responder = this.getScrollResponder()
responder && responder.scrollResponderScrollToEnd({ animated })
}
scrollForExtraHeightOnAndroid = (extraHeight: number) => {
this.scrollToPosition(0, this.position.y + extraHeight, true)
}
/**
* #param keyboardOpeningTime: takes a different keyboardOpeningTime in consideration.
* #param extraHeight: takes an extra height in consideration.
*/
scrollToFocusedInput = (
reactNode: any,
extraHeight?: number,
keyboardOpeningTime?: number
) => {
if (extraHeight === undefined) {
extraHeight = this.props.extraHeight || 0
}
if (keyboardOpeningTime === undefined) {
keyboardOpeningTime = this.props.keyboardOpeningTime || 0
}
setTimeout(() => {
if (!this.mountedComponent) {
return
}
const responder = this.getScrollResponder()
responder &&
responder.scrollResponderScrollNativeHandleToKeyboard(
reactNode,
extraHeight,
true
)
}, keyboardOpeningTime)
}
scrollIntoView = async (
element: React.Element<*>,
options: ScrollIntoViewOptions = {}
) => {
if (!this._rnkasv_keyboardView || !element) {
return
}
const [parentLayout, childLayout] = await Promise.all([
this._measureElement(this._rnkasv_keyboardView),
this._measureElement(element)
])
const getScrollPosition =
options.getScrollPosition || this._defaultGetScrollPosition
const { x, y, animated } = getScrollPosition(
parentLayout,
childLayout,
this.position
)
this.scrollToPosition(x, y, animated)
}
_defaultGetScrollPosition = (
parentLayout: ElementLayout,
childLayout: ElementLayout,
contentOffset: ContentOffset
): ScrollPosition => {
return {
x: 0,
y: Math.max(0, childLayout.y - parentLayout.y + contentOffset.y),
animated: true
}
}
_measureElement = (element: React.Element<*>): Promise<ElementLayout> => {
const node = findNodeHandle(element)
return new Promise((resolve: ElementLayout => void) => {
UIManager.measureInWindow(
node,
(x: number, y: number, width: number, height: number) => {
resolve({ x, y, width, height })
}
)
})
}
// Keyboard actions
_updateKeyboardSpace = (frames: Object) => {
// Automatically scroll to focused TextInput
if (this.props.enableAutomaticScroll) {
let keyboardSpace: number =
frames.endCoordinates.height + this.props.extraScrollHeight
if (this.props.viewIsInsideTabBar) {
keyboardSpace -= _KAM_DEFAULT_TAB_BAR_HEIGHT
}
this.setState({ keyboardSpace })
const currentlyFocusedField = TextInput.State.currentlyFocusedField()
const responder = this.getScrollResponder()
if (!currentlyFocusedField || !responder) {
return
}
UIManager.viewIsDescendantOf(
currentlyFocusedField,
responder.getInnerViewNode(),
(isAncestor: boolean) => {
if (isAncestor) {
// Check if the TextInput will be hidden by the keyboard
UIManager.measureInWindow(
currentlyFocusedField,
(x: number, y: number, width: number, height: number) => {
const textInputBottomPosition = y + height
const keyboardPosition = frames.endCoordinates.screenY
const totalExtraHeight =
this.props.extraScrollHeight + this.props.extraHeight
if (Platform.OS === 'ios') {
if (
textInputBottomPosition >
keyboardPosition - totalExtraHeight
) {
this._scrollToFocusedInputWithNodeHandle(
currentlyFocusedField
)
}
} else {
// On android, the system would scroll the text input just
// above the keyboard so we just neet to scroll the extra
// height part
if (textInputBottomPosition > keyboardPosition) {
// Since the system already scrolled the whole view up
// we should reduce that amount
keyboardSpace =
keyboardSpace -
(textInputBottomPosition - keyboardPosition)
this.setState({ keyboardSpace })
this.scrollForExtraHeightOnAndroid(totalExtraHeight)
} else if (
textInputBottomPosition >
keyboardPosition - totalExtraHeight
) {
this.scrollForExtraHeightOnAndroid(
totalExtraHeight -
(keyboardPosition - textInputBottomPosition)
)
}
}
}
)
}
}
)
}
if (!this.props.resetScrollToCoords) {
if (!this.defaultResetScrollToCoords) {
this.defaultResetScrollToCoords = this.position
}
}
}
_resetKeyboardSpace = () => {
const keyboardSpace: number = this.props.viewIsInsideTabBar
? _KAM_DEFAULT_TAB_BAR_HEIGHT
: 0
this.setState({ keyboardSpace })
// Reset scroll position after keyboard dismissal
if (this.props.enableResetScrollToCoords === false) {
this.defaultResetScrollToCoords = null
return
} else if (this.props.resetScrollToCoords) {
this.scrollToPosition(
this.props.resetScrollToCoords.x,
this.props.resetScrollToCoords.y,
true
)
} else {
if (this.defaultResetScrollToCoords) {
this.scrollToPosition(
this.defaultResetScrollToCoords.x,
this.defaultResetScrollToCoords.y,
true
)
this.defaultResetScrollToCoords = null
} else {
this.scrollToPosition(0, 0, true)
}
}
}
_scrollToFocusedInputWithNodeHandle = (
nodeID: number,
extraHeight?: number,
keyboardOpeningTime?: number
) => {
if (extraHeight === undefined) {
extraHeight = this.props.extraHeight
}
const reactNode = findNodeHandle(nodeID)
this.scrollToFocusedInput(
reactNode,
extraHeight + this.props.extraScrollHeight,
keyboardOpeningTime !== undefined
? keyboardOpeningTime
: this.props.keyboardOpeningTime || 0
)
}
_handleOnScroll = (
e: SyntheticEvent<*> & { nativeEvent: { contentOffset: number } }
) => {
this.position = e.nativeEvent.contentOffset
}
_handleRef = (ref: React.Component<*>) => {
this._rnkasv_keyboardView = ref ? hocOptions.extractNativeRef(ref) : ref
if (this.props.innerRef) {
this.props.innerRef(this._rnkasv_keyboardView)
}
}
update = () => {
const currentlyFocusedField = TextInput.State.currentlyFocusedField()
const responder = this.getScrollResponder()
if (!currentlyFocusedField || !responder) {
return
}
this._scrollToFocusedInputWithNodeHandle(currentlyFocusedField)
}
render() {
const { enableOnAndroid, contentContainerStyle, onScroll } = this.props
let newContentContainerStyle
if (Platform.OS === 'android' && enableOnAndroid) {
newContentContainerStyle = [].concat(contentContainerStyle).concat({
paddingBottom:
((contentContainerStyle || {}).paddingBottom || 0) +
this.state.keyboardSpace
})
}
const refProps = { [hocOptions.refPropName]: this._handleRef }
return (
<ScrollableComponent
{...refProps}
keyboardDismissMode='interactive'
contentInset={{ bottom: this.state.keyboardSpace }}
automaticallyAdjustContentInsets={false}
showsVerticalScrollIndicator={true}
scrollEventThrottle={1}
{...this.props}
contentContainerStyle={
newContentContainerStyle || contentContainerStyle
}
keyboardSpace={this.state.keyboardSpace}
getScrollResponder={this.getScrollResponder}
scrollToPosition={this.scrollToPosition}
scrollToEnd={this.scrollToEnd}
scrollForExtraHeightOnAndroid={this.scrollForExtraHeightOnAndroid}
scrollToFocusedInput={this.scrollToFocusedInput}
scrollIntoView={this.scrollIntoView}
resetKeyboardSpace={this._resetKeyboardSpace}
handleOnScroll={this._handleOnScroll}
update={this.update}
onScroll={Animated.forkEvent(onScroll, this._handleOnScroll)}
/>
)
}
}
}
// Allow to pass options, without breaking change, and curried for composition
// listenToKeyboardEvents(ScrollView);
// listenToKeyboardEvents(options)(Comp);
const listenToKeyboardEvents = (configOrComp: any) => {
if (typeof configOrComp === 'object' && !configOrComp.displayName) {
return (Comp: Function) => KeyboardAwareHOC(Comp, configOrComp)
} else {
return KeyboardAwareHOC(configOrComp)
}
}
export default listenToKeyboardEvents

Related

react-beautiful-dnd: Prevent flicker when drag and drop a lists with API call

I'm using this react-beautiful-dnd library to be able to reorder lists. However, even though I'm able to drag and drop and re-order, there is a flicker when I try to move a card from one list to another list I call API when a card is dragged to the destination list
const onDragEnd = (result: any) => {
if (!result.destination) {
return;
}
const listCopy: any = { ...elements };
const sourceList = listCopy[result.source.droppableId];
const [removedElement, newSourceList] = removeFromList(
sourceList,
result.source.index
);
listCopy[result.source.droppableId] = newSourceList;
const destinationList = listCopy[result.destination.droppableId];
listCopy[result.destination.droppableId] = addToList(
result.destination.droppableId,
destinationList || [],
result.destination.index,
removedElement,
result.source.droppableId
);
setElements(listCopy)};
and in addToList function I am calling API to update order on server
const addToList = (
changedList: string,
list: any[],
index: number,
element: any,
currentListId: string
) => {
let cardOrder;
const result = Array.from(list);
result.splice(index, 0, element);
const cardCurrentIndex = result.findIndex((item) => item.id === element.id);
if (list.length === 0) {
cardOrder = DEFAULT_PIPELINE_ORDER;
} else if (cardCurrentIndex === 0 && result.length !== 0) {
const nextCardOrder = result[1];
cardOrder = nextCardOrder.current_stage_order - STAGE_INCREMENT_AMOUNT;
} else if (cardCurrentIndex === result.length - 1) {
const nextCardOrder = result[result.length - 2];
cardOrder = nextCardOrder.current_stage_order + STAGE_INCREMENT_AMOUNT;
} else if (
Boolean(result[cardCurrentIndex - 1]) &&
Boolean(result[cardCurrentIndex + 1])
) {
cardOrder = Math.round(
(result[cardCurrentIndex - 1].current_stage_order +
result[cardCurrentIndex + 1].current_stage_order) /
2
);
}
let candidatesData: any = elements;
if (candidatesData) {
if (currentListId === changedList) {
candidatesData[changedList as any] = result as unknown as elementsType;
setElements([...candidatesData]);
} else {
candidatesData[currentListId as any] = candidatesData[
currentListId as any
]?.filter((item: any) => item.id !== element.id);
candidatesData[changedList as any] = result as unknown as elementsType;
setElements([...candidatesData]);
console.log("[...candidatesData]", [...candidatesData]);
}
}
const stageId = stagePipeLineLanes?.find(
(item) => item.id.toString() === changedList.toLowerCase()
)?.id;
if (
changedList === "applied" ||
changedList === "sourcing" ||
changedList === "interviewing"
) {
const changedDestination = changedList;
const destinationStages = positionDetails?.candidate_stages.filter(
(item) =>
item.pipeline.toLowerCase() === changedDestination.toLowerCase()
);
const stage = destinationStages.find((item) => item.is_default === true);
mutate(
{
candidateId: element.id.toString(),
data: compressObject({
stage: stage?.id.toString(),
}),
},
{
onSuccess: (response) => {
if (response) {
toast.success(
`Candidate moved to ${capitalizeFirstLetter(
changedDestination
)}`
);
}
},
}
);
} else {
mutate({
candidateId: element.id.toString(),
data: compressObject({
stage: stageId?.toString() || "",
current_stage_order: cardOrder?.toString() || "",
}),
});
}
return result;
};

Get TypeError: Cannot read properties of undefined (reading 'forEach') when pass a params

At first, the params does not have any data yet (blank array), but it will update again after useEffect set the variable.
But for my highchart, it gave me this error.
TypeError: Cannot read properties of undefined (reading 'forEach')
52 | createChart();
53 | } else {
54 | if (props.allowChartUpdate !== false) {
>55 | if (!props.immutable && chartRef.current) {
| ^ 56 | chartRef.current.update(
57 | props.options,
58 | ...(props.updateArgs || [true, true])
I searched some solutions, they suggest can use allowChartUpdate={false} and immutable={false} for solving the problem. After I tried it, yes it does solve my problem, but my highchart doesn't show the data when initial load.
I'm guessing is it the params passing in a blank array at first, then passing a second time with actual values so it causes this problem. If yes, can rerendering the highchart solve the problem? And how can I do that?
Here is the link, please help me on it. Thank you muakzzz.
You can instead just provide the getRouteData function as an initializer function directly to the useState hook to provide the initial state. So long as it's not asynchronous it will provide initial state for the initial render, no need to use the useEffect hook to populate state after the first render.
Additionally, you should initialize routeMapData to have the data property array by default so you don't accidentally pass it through with an undefined data property, which was part of the problem you were seeing.
export default function App() {
const [routeData] = useState(getRouteData()); // <-- initialize state
const mapStation = () => {
const routeMapData = {
data: [], // <-- data array to push into
};
if (routeData.length !== 0) {
for (let i = 0; i < routeData.length; i++) {
const station = routeData[i].station;
for (let j = 0; j < station.length; j++) {
const firstStation = station[j];
const nextStation = station[j + 1];
if (nextStation) {
routeMapData.data.push([ // <-- push into array
firstStation.stationName,
nextStation.stationName
]);
}
}
}
}
return routeMapData;
};
const content = (key) => {
if (key === "map") {
return <RouteMap mapRouteData={mapStation()} />;
}
return null;
};
return <Box className="rightPaper center">{content("map")}</Box>;
}
You don't need to even use the local state as you can directly consume the returned array from getRouteData in the mapStation utility function.
export default function App() {
const mapStation = () => {
return {
data: getRouteData().flatMap(({ station }) => {
return station.reduce((segments, current, i, stations) => {
if (stations[i + 1]) {
segments.push([
current.stationName,
stations[i + 1].stationName
]);
}
return segments;
}, []);
})
};
};
const content = (key) => {
if (key === "map") {
return <RouteMap mapRouteData={mapStation()} />;
}
return null;
};
return <Box className="rightPaper center">{content("map")}</Box>;
}
Thank you for your help. I managed to get my desired output already. The problem was my parent component will pass a blank array of data into my highchart network graph component at first due to the useEffect used in the parent. And after that, they pass another array with actual data into my highchart network graph component.
import React, {useEffect, useState} from 'react';
import {Box} from "#mui/material";
import RouteMap from "./Content/RouteMap";
import {getRouteData} from "../../../API/RouteDataAPI"
import Timetable from "./Content/Timetable";
import _ from 'lodash';
function RightContent({contentKey}) {
const [routeData, setRouteData] = useState([]);
useEffect(() => {
getRouteData().then(res => setRouteData(res));
}, [])
const mapStation = () => {
let arr = [], allStation = [], routeMapData = {}
if (routeData.length !== 0) {
for (let i = 0; i < routeData.length; i++) {
const station = routeData[i].station;
for (let j = 0; j < station.length; j++) {
const firstStation = station[j];
const nextStation = station[j + 1];
allStation.push(firstStation.stationName)
if (nextStation) {
arr.push([firstStation.stationName, nextStation.stationName])
}
}
}
routeMapData.data = arr;
routeMapData.allStation = allStation;
routeMapData.centralStation = "KL Sentral"
}
return routeMapData;
}
// const mapStation = () => {
// let arr = [];
// getRouteData().then(res => {
// arr.push(res.flatMap(({station}) => {
// return station.reduce((segments, current, i, stations) => {
// if (stations[i + 1]) {
// segments.push(
// current.stationName,
// );
// }
// return segments;
// }, []);
// }))
// })
// console.log(arr)
// }
const content = (key) => {
const availableRoute = routeData.map(route => route.routeTitle);
if (
key === 'map'
// && !_.isEmpty(mapStation())
){
// console.log('here', mapRouteData)
return <RouteMap mapRouteData={mapStation()}/>;
}
else if (availableRoute.includes(key)) {
return <Timetable routeData={routeData} currentRoute={key}/>
} else {
return null;
}
}
return (
<Box className="rightPaper center">
{content(contentKey)}
</Box>
);
}
export default RightContent;
^^^This was my parent component.^^^
In the content variable function there, I have an if statement with the requirements provided. If you try to uncomment the lodash (second requirement) in the if statement, I can able to get my desire result.
This was my highchart network component.
import React, {useEffect, useRef, useState} from 'react'
import Highcharts from 'highcharts/highstock'
import HighchartsReact from 'highcharts-react-official'
import networkgraph from 'highcharts/modules/networkgraph'
require('highcharts/modules/exporting')(Highcharts);
require('highcharts/modules/export-data')(Highcharts);
if (typeof Highcharts === "object") {
networkgraph(Highcharts);
}
const RouteMap = ({mapRouteData}) => {
const [seriesData, setSeriesData] = useState(mapRouteData.data);
const [centralStation, setCentralStation] = useState(mapRouteData.centralStation);
const [allStation, setAllStation] = useState(mapRouteData.allStation);
useEffect(() => {
setSeriesData(mapRouteData.data);
setCentralStation(mapRouteData.centralStation);
setAllStation(mapRouteData.allStation);
}, [mapRouteData])
Highcharts.addEvent(
Highcharts.Series,
'afterSetOptions',
function (e) {
let colors = Highcharts.getOptions().colors,
i = 0,
nodes = {};
if (
this instanceof Highcharts.seriesTypes.networkgraph &&
e.options.id === 'lang-tree' &&
e.options.data !== undefined
) {
let lastSecond = '', arry = []
e.options.data.forEach(function (link) {
if (lastSecond !== link[0]) {
nodes[link[0]] = {
id: link[0],
color: colors[++i]
}
} else if (lastSecond === link[0]) {
nodes[link[0]] = {
id: link[0],
color: colors[i]
}
nodes[link[1]] = {
id: link[1],
color: colors[i]
}
arry.push(link[0])
}
lastSecond = link[1];
});
const exchangeStation = allStation.filter((item, index) => allStation.indexOf(item) !== index);
i += 1;
exchangeStation.forEach((station) => {
nodes[station] = {
id: station,
marker: {
radius: 18
},
name: 'Interchange: ' + station,
color: colors[i]
}
})
nodes[centralStation] = {
id: centralStation,
name: 'Sentral Station: ' + centralStation,
marker: {
radius: 25
},
color: colors[++i]
}
e.options.nodes = Object.keys(nodes).map(function (id) {
return nodes[id];
});
}
}
);
const options = {
chart: {
type: 'networkgraph',
},
title: {
text: 'The Route Map'
},
caption: {
text: "Click the button at top right for more options."
},
credits: {
enabled: false
},
plotOptions: {
networkgraph: {
keys: ['from', 'to'],
layoutAlgorithm: {
enableSimulation: true,
// linkLength: 7
}
}
},
series: [
{
link: {
width: 4,
},
marker: {
radius: 10
},
dataLabels: {
enabled: true,
linkFormat: "",
allowOverlap: false
},
id: "lang-tree",
data: seriesData
}
]
};
return <HighchartsReact
ref={useRef()}
containerProps={{style: {height: "100%", width: "100%"}}}
highcharts={Highcharts}
options={options}
/>;
}
export default RouteMap;
Sorry for the lengthy code here. By the way, feel free to let me know any improvements I can make in my code. First touch on react js project and still have a long journey to go.
Once again~ Thank you!
I fixed adding True for allowChartUpdate and immutable
<HighchartsReact
ref={chartRef}
highcharts={Highcharts}
options={options}
containerProps={containerProps}
allowChartUpdate={true}
immutable={true}
/>

Method must be called after React Context render

I've encountered an issue while using React Context. I use something like this to get the selected answer from the context:
const [selectedAnswer, setSelectedAnswer] = React.useState<boolean>(
isSelectedAnswer()
);
function isSelectedAnswer(): boolean {
return QuestionContext!
.getAnswers()
[QuestionContext!.getQuestionIndex(heading)].find(
({ name, value }) => name === heading && value === true
)
? true
: false;
}
But I receive a strange error:
TypeError: this.state.questions.find(...) is undefined
While typeof this.state.questions.find is function, obviously.
I believe the issue is happening, because the QuestionContext's React Component, and in the useState function it's not ready declared. I've used Profiler function, with onRender callback, but I realised that the Profiler function won't be a good candidate for that type of error. But yeah, it worked fine. Anyone knows how to fix that issue?
EDIT: Here's the Context code, which is returning error with the state.
import React, { Component } from "react";
import {
Question,
Questions,
Answers,
Answer,
ParsedAnswer,
Preset,
Presets,
} from "../../typings";
import QuestionsContext from "../QuestionsContext";
import questions from "../../data/questions";
import presets from "../../data/presets";
type QuestionsContextProviderProps = {
children: any;
};
export type QuestionsContextProvidedState = {
questions: Questions;
presets: Presets;
categories: string[];
answers: Answers;
selectedPreset: Preset | null;
getPresets: () => Presets;
getPresetForQuestion: (
questionCategoryIndex: number,
questionIndex: number
) => string;
setPreset: (preset: Preset) => void;
setPresetsInAnswers: (preset: Preset) => void;
getSelectedPreset: () => Preset | null;
getQuestions: () => Questions;
getFieldNameCategory: (fieldName: string) => string;
getQuestionIndex: (fieldName: string) => number;
getQuestionsFromIndex: (index: number) => Question[];
getAnswers: () => Answers;
sendNewAnswer: (
questionIndex: number,
fieldName: string,
answer: string | boolean
) => void | boolean | string;
parseAnswers: () => string;
};
export default class QuestionsContextProvider extends Component<
QuestionsContextProviderProps,
QuestionsContextProvidedState
> {
public readonly state: QuestionsContextProvidedState = {
questions: questions,
presets: presets,
categories: [],
answers: [],
selectedPreset: null,
getPresets: this.getPresets.bind(this),
getPresetForQuestion: this.getPresetForQuestion.bind(this),
setPreset: this.setPreset.bind(this),
setPresetsInAnswers: this.setPresetsInAnswers.bind(this),
getSelectedPreset: this.getSelectedPreset.bind(this),
getQuestions: this.getQuestions.bind(this),
getFieldNameCategory: this.getFieldNameCategory.bind(this),
getQuestionIndex: this.getQuestionIndex.bind(this),
getQuestionsFromIndex: this.getQuestionsFromIndex.bind(this),
getAnswers: this.getAnswers.bind(this),
sendNewAnswer: this.sendNewAnswer.bind(this),
parseAnswers: this.parseAnswers.bind(this),
};
constructor(props: any) {
super(props);
const endingAnswersArray = this.state.answers;
const endingCatagoriesArray = this.state.categories;
const alreadyUsedKeys: string[] = [];
this.state.questions.forEach(({ key }) => {
if (alreadyUsedKeys.find((value) => value === key)) return;
endingAnswersArray.push([]);
endingCatagoriesArray.push(key);
alreadyUsedKeys.push(key);
});
this.state.answers = endingAnswersArray;
this.state.categories = endingCatagoriesArray;
}
public getPresets(): Presets {
return this.state.presets;
}
public getPresetForQuestion(
questionCategoryIndex: number,
questionIndex: number,
preset?: Preset
): string {
return this.state.questions[questionCategoryIndex].values[questionIndex]
.presets[preset || this.state.selectedPreset!];
}
public setPreset(preset: Preset): void {
this.setState({
selectedPreset: preset,
});
}
private getDefaultValueForQuestion(
questionCategoryIndex: number,
questionIndex: number
): string {
return this.state.questions[questionCategoryIndex].values[questionIndex]
.defaultValue;
}
public setPresetsInAnswers(preset: Preset): void {
this.state.categories.forEach((_, key) => {
this.state.questions.forEach(({ values }, idx) => {
values.forEach(({ name }, questionKey) => {
const selectedPreset = this.getPresetForQuestion(
idx,
questionKey,
preset
);
let newPreset: string | boolean = selectedPreset;
if (selectedPreset === "true") newPreset = true;
if (selectedPreset === "false") newPreset = false;
if (
selectedPreset === this.getDefaultValueForQuestion(idx, questionKey)
)
return;
this.sendNewAnswer(key, name, newPreset);
});
});
});
}
public getSelectedPreset(): Preset | null {
return this.state.selectedPreset;
}
public getQuestions(): Questions {
return this.state.questions;
}
public getFieldNameCategory(fieldName: string): string {
return this.state.questions.find(({ values }) =>
values.find(({ name }) => name === fieldName)
)!.key;
}
public getQuestionIndex(fieldName: string): number {
return this.state.categories.findIndex(
(value) => value === this.getFieldNameCategory(fieldName)
);
}
public getQuestionsFromIndex(index: number): Question[] {
return this.state.questions[index].values;
}
public getAnswers(): Answers {
return this.state.answers;
}
public sendNewAnswer(
questionIndex: number,
fieldName: string,
answer: string | boolean
): void | boolean | string {
const alreadyAnsweredField = this.state.answers[questionIndex].find(
({ name }) => name === fieldName
);
if (alreadyAnsweredField) {
return (alreadyAnsweredField.value = answer);
}
const endingAnswersField = this.state.answers;
endingAnswersField[questionIndex].push({
name: fieldName,
value: answer,
});
this.setState({
answers: endingAnswersField,
});
}
public parseAnswers(): string {
const parsedAnswer: ParsedAnswer = {};
this.state.categories.forEach((value, key) => {
parsedAnswer[value] = {};
this.getAnswers()[key].forEach((answer: Answer) => {
parsedAnswer[value][answer.name] = answer.value;
});
});
return JSON.stringify(parsedAnswer, null, 2);
}
public render() {
return (
<QuestionsContext.Provider value={this.state}>
{this.props.children}
</QuestionsContext.Provider>
);
}
}

Flow error: Cannot reference type `updatePreviewSelection` [1] from a value position

I am having an error in my yarn flow console. I am a bit confused about what it actually telling me to do? I am a bit confused about why my reference can not find value in the position.
Cannot reference type updatePreviewSelection 1 from a value position.
import * as React from 'react';
import explicitConnect from '../../utils/connect';
import { FlameGraphCanvas } from './Canvas';
import {
getCategories,
getCommittedRange,
getPreviewSelection,
getScrollToSelectionGeneration,
getProfileInterval,
getPageList,
} from '../selectors/profile';
import { selectedThreadSelectors } from '../selectors/per-thread';
import {
getSelectedThreadsKey,
getInvertCallstack,
} from '../../selectors/url-state';
import { ContextMenuTrigger } from '../components/shared/ContextMenuTrigger';
import { getCallNodePathFromIndex } from '../../profile-logic/profile-data';
import {
changeSelectedCallNode,
changeRightClickedCallNode,
updatePreviewSelection,
} from '../actions/profile-view';
import type {
Thread,
CategoryList,
PageList,
Milliseconds,
StartEndRange,
WeightType,
SamplesLikeTable,
PreviewSelection,
CallTreeSummaryStrategy,
CallNodeInfo,
IndexIntoCallNodeTable,
TracedTiming,
ThreadsKey,
} from '../types';
import type { FlameGraphTiming } from '../profile-logic/flame-graph';
import type { CallTree } from '../profile-logic/call-tree';
import type {
ConnectedProps,
WrapFunctionInDispatch,
} from '../utils/connect';
import './FlameGraph.css';
const STACK_FRAME_HEIGHT = 16;
/**
* How "wide" a call node box needs to be for it to be able to be
* selected with keyboard navigation. This is a fraction between 0 and
* 1, where 1 means the box spans the whole viewport.
*/
const SELECTABLE_THRESHOLD = 0.001;
type StateProps = {|
+thread: Thread,
+weightType: WeightType,
+pages: PageList | null,
+unfilteredThread: Thread,
+previewSelection: PreviewSelection,
+sampleIndexOffset: number,
+maxStackDepth: number,
+timeRange: StartEndRange,
+previewSelection: PreviewSelection,
+flameGraphTiming: FlameGraphTiming,
+callTree: CallTree,
+callNodeInfo: CallNodeInfo,
+threadsKey: ThreadsKey,
+selectedCallNodeIndex: IndexIntoCallNodeTable | null,
+rightClickedCallNodeIndex: IndexIntoCallNodeTable | null,
+scrollToSelectionGeneration: number,
+categories: CategoryList,
+interval: Milliseconds,
+isInverted: boolean,
+callTreeSummaryStrategy: CallTreeSummaryStrategy,
+samples: SamplesLikeTable,
+unfilteredSamples: SamplesLikeTable,
+tracedTiming: TracedTiming | null,
|};
type DispatchProps = {|
+changeSelectedCallNode: typeof changeSelectedCallNode,
+changeRightClickedCallNode: typeof changeRightClickedCallNode,
+updatePreviewSelection: WrapFunctionInDispatch<
typeof updatePreviewSelection
>,
|};
type Props = ConnectedProps<{||}, StateProps, DispatchProps>;
class FlameGraphImpl extends React.PureComponent<Props> {
_viewport: HTMLDivElement | null = null;
_onSelectedCallNodeChange = (
callNodeIndex: IndexIntoCallNodeTable | null
) => {
const { callNodeInfo, threadsKey, changeSelectedCallNode } = this.props;
changeSelectedCallNode(
threadsKey,
getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable)
);
};
_onRightClickedCallNodeChange = (
callNodeIndex: IndexIntoCallNodeTable | null
) => {
const { callNodeInfo, threadsKey, changeRightClickedCallNode } = this.props;
changeRightClickedCallNode(
threadsKey,
getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable)
);
};
_shouldDisplayTooltips = () => this.props.rightClickedCallNodeIndex === null;
_takeViewportRef = (viewport: HTMLDivElement | null) => {
this._viewport = viewport;
};
focus = () => {
if (this._viewport) {
this._viewport.focus();
}
};
/**
* Is the box for this call node wide enough to be selected?
*/
_wideEnough = (callNodeIndex: IndexIntoCallNodeTable): boolean => {
const {
flameGraphTiming,
callNodeInfo: { callNodeTable },
} = this.props;
const depth = callNodeTable.depth[callNodeIndex];
const row = flameGraphTiming[depth];
const columnIndex = row.callNode.indexOf(callNodeIndex);
return row.end[columnIndex] - row.start[columnIndex] > SELECTABLE_THRESHOLD;
};
/**
* Return next keyboard selectable callNodeIndex along one
* horizontal direction.
*
* `direction` should be either -1 (left) or 1 (right).
*
* Returns undefined if no selectable callNodeIndex can be found.
* This means we're already at the end, or the boxes of all
* candidate call nodes are too narrow to be selected.
*/
_nextSelectableInRow = (
startingCallNodeIndex: IndexIntoCallNodeTable,
direction: 1 | -1
): IndexIntoCallNodeTable | void => {
const {
flameGraphTiming,
callNodeInfo: { callNodeTable },
} = this.props;
let callNodeIndex = startingCallNodeIndex;
const depth = callNodeTable.depth[callNodeIndex];
const row = flameGraphTiming[depth];
let columnIndex = row.callNode.indexOf(callNodeIndex);
do {
columnIndex += direction;
callNodeIndex = row.callNode[columnIndex];
if (
row.end[columnIndex] - row.start[columnIndex] >
SELECTABLE_THRESHOLD
) {
// The box for this callNodeIndex is wide enough. We've found
// a candidate.
break;
}
} while (callNodeIndex !== undefined);
return callNodeIndex;
};
_handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const {
threadsKey,
callTree,
callNodeInfo: { callNodeTable },
selectedCallNodeIndex,
changeSelectedCallNode,
} = this.props;
if (selectedCallNodeIndex === null) {
if (
['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'].includes(event.key)
) {
// Just select the "root" node if we've got no prior selection.
changeSelectedCallNode(
threadsKey,
getCallNodePathFromIndex(0, callNodeTable)
);
}
return;
}
switch (event.key) {
case 'ArrowDown': {
const prefix = callNodeTable.prefix[selectedCallNodeIndex];
if (prefix !== -1) {
changeSelectedCallNode(
threadsKey,
getCallNodePathFromIndex(prefix, callNodeTable)
);
}
break;
}
case 'ArrowUp': {
const [callNodeIndex] = callTree.getChildren(selectedCallNodeIndex);
// The call nodes returned from getChildren are sorted by
// total time in descending order. The first one in the
// array, which is the one we pick, has the longest time and
// thus the widest box.
if (callNodeIndex !== undefined && this._wideEnough(callNodeIndex)) {
changeSelectedCallNode(
threadsKey,
getCallNodePathFromIndex(callNodeIndex, callNodeTable)
);
}
break;
}
case 'ArrowLeft':
case 'ArrowRight': {
const callNodeIndex = this._nextSelectableInRow(
selectedCallNodeIndex,
event.key === 'ArrowLeft' ? -1 : 1
);
if (callNodeIndex !== undefined) {
changeSelectedCallNode(
threadsKey,
getCallNodePathFromIndex(callNodeIndex, callNodeTable)
);
}
break;
}
default:
// Other keys are ignored
break;
}
};
render() {
const {
thread,
unfilteredThread,
sampleIndexOffset,
threadsKey,
maxStackDepth,
flameGraphTiming,
updatePreviewSelection,
callTree,
callNodeInfo,
timeRange,
previewSelection,
selectedCallNodeIndex,
scrollToSelectionGeneration,
callTreeSummaryStrategy,
categories,
interval,
isInverted,
pages,
weightType,
samples,
unfilteredSamples,
tracedTiming,
} = this.props;
const maxViewportHeight = maxStackDepth * STACK_FRAME_HEIGHT;
return (
<div className="flameGraphContent" onKeyDown={this._handleKeyDown}>
<ContextMenuTrigger
id="CallNodeContextMenu"
attributes={‌{
className: 'treeViewContextMenu',
}}
>
<FlameGraphCanvas
key={threadsKey}
// ChartViewport props
viewportProps={‌{
timeRange,
maxViewportHeight,
maximumZoom: 1,
previewSelection,
startsAtBottom: true,
disableHorizontalMovement: true,
viewportNeedsUpdate,
marginLeft: 0,
marginRight: 0,
containerRef: this._takeViewportRef,
}}
// FlameGraphCanvas props
chartProps={‌{
thread,
pages,
updatePreviewSelection,
weightType,
unfilteredThread,
sampleIndexOffset,
maxStackDepth,
flameGraphTiming,
callTree,
callNodeInfo,
categories,
selectedCallNodeIndex,
scrollToSelectionGeneration,
callTreeSummaryStrategy,
stackFrameHeight: STACK_FRAME_HEIGHT,
onSelectionChange: this._onSelectedCallNodeChange,
onRightClick: this._onRightClickedCallNodeChange,
shouldDisplayTooltips: this._shouldDisplayTooltips,
interval,
isInverted,
samples,
unfilteredSamples,
tracedTiming,
}}
/>
</ContextMenuTrigger>
</div>
);
}
}
function viewportNeedsUpdate() {
// By always returning false we prevent the viewport from being
// reset and scrolled all the way to the bottom when doing
// operations like changing the time selection or applying a
// transform.
return false;
}
export const FlameGraph = explicitConnect<{||}, StateProps, DispatchProps>({
mapStateToProps: state => ({
thread: selectedThreadSelectors.getFilteredThread(state),
unfilteredThread: selectedThreadSelectors.getThread(state),
weightType: selectedThreadSelectors.getWeightTypeForCallTree(state),
sampleIndexOffset: selectedThreadSelectors.getSampleIndexOffsetFromCommittedRange(
state
),
maxStackDepth: selectedThreadSelectors.getCallNodeMaxDepthForFlameGraph(
state
),
flameGraphTiming: selectedThreadSelectors.getFlameGraphTiming(state),
callTree: selectedThreadSelectors.getCallTree(state),
timeRange: getCommittedRange(state),
previewSelection: getPreviewSelection(state),
callNodeInfo: selectedThreadSelectors.getCallNodeInfo(state),
categories: getCategories(state),
threadsKey: getSelectedThreadsKey(state),
selectedCallNodeIndex: selectedThreadSelectors.getSelectedCallNodeIndex(
state
),
rightClickedCallNodeIndex: selectedThreadSelectors.getRightClickedCallNodeIndex(
state
),
scrollToSelectionGeneration: getScrollToSelectionGeneration(state),
interval: getProfileInterval(state),
isInverted: getInvertCallstack(state),
callTreeSummaryStrategy: selectedThreadSelectors.getCallTreeSummaryStrategy(
state
),
pages: getPageList(state),
samples: selectedThreadSelectors.getSamplesForCallTree(state),
unfilteredSamples: selectedThreadSelectors.getUnfilteredSamplesForCallTree(
state
),
tracedTiming: selectedThreadSelectors.getTracedTiming(state),
}),
mapDispatchToProps: {
changeSelectedCallNode,
changeRightClickedCallNode,
updatePreviewSelection,
},
options: { forwardRef: true },
component: FlameGraphImpl,
});

TypeError: Cannot read property 'length' of null in react component

Okay so I've gotten pretty far in creating the React Component for ChartJS, however when testing I get the following error:
FAIL lib\chart\chart.test.tsx
● renders without crashing
TypeError: Cannot read property 'length' of null
at Object.acquireContext (node_modules/chart.js/src/platforms/platform.dom.js:189:19)
at Chart.construct (node_modules/chart.js/src/core/core.controller.js:72:27)
at new Chart (node_modules/chart.js/src/core/core.js:7:8)
at Chart.Object.<anonymous>.Chart.renderChart (lib/chart/chart.tsx:233:26)
at Chart.Object.<anonymous>.Chart.componentDidMount (lib/chart/chart.tsx:42:10)
at node_modules/react-dom/lib/ReactCompositeComponent.js:264:25
at measureLifeCyclePerf (node_modules/react-dom/lib/ReactCompositeComponent.js:75:12)
at node_modules/react-dom/lib/ReactCompositeComponent.js:263:11
at CallbackQueue.notifyAll (node_modules/react-dom/lib/CallbackQueue.js:76:22)
at ReactReconcileTransaction.close (node_modules/react-dom/lib/ReactReconcileTransaction.js:80:26)
at ReactReconcileTransaction.closeAll (node_modules/react-dom/lib/Transaction.js:209:25)
at ReactReconcileTransaction.perform (node_modules/react-dom/lib/Transaction.js:156:16)
at batchedMountComponentIntoNode (node_modules/react-dom/lib/ReactMount.js:126:15)
at ReactDefaultBatchingStrategyTransaction.perform (node_modules/react-dom/lib/Transaction.js:143:20)
at Object.batchedUpdates (node_modules/react-dom/lib/ReactDefaultBatchingStrategy.js:62:26)
at Object.batchedUpdates (node_modules/react-dom/lib/ReactUpdates.js:97:27)
at Object._renderNewRootComponent (node_modules/react-dom/lib/ReactMount.js:319:18)
at Object._renderSubtreeIntoContainer (node_modules/react-dom/lib/ReactMount.js:401:32)
at Object.render (node_modules/react-dom/lib/ReactMount.js:422:23)
at Object.<anonymous> (lib/chart/chart.test.tsx:7:12)
at Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:169:7)
× renders without crashing (275ms)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 1.314s, estimated 3s
Ran all test suites related to changed files.
However, I've spent a long time looking over the code and haven't been able to figure out why it refuses to work properly. The error starts at the renderChart() function at creating a new chart instance. My first guess would be the for some reason it's not registering the canvas element despite being called by its id. But when the content of renderChart is moved into the render() function it still gives the same error. Here's the code being tested:
import * as React from 'react'
import * as ClassNames from 'classnames'
import * as ChartJS from 'chart.js'
const IsEqual = require('lodash.isequal')
const Find = require('lodash.find')
const subChart = require('chart.js')
interface IChartProps {
/** The user-defined classes */
readonly className?: string
readonly width?: number
readonly height?: number
readonly reRender?: boolean
readonly type: ChartJS.ChartType
readonly data: ChartJS.ChartData
readonly options: ChartJS.ChartOptions
readonly getDatasetAtEvent?: Function
readonly getElementAtEvent?: Function
readonly getElementsAtEvent?: Function
readonly onElementsClick?: Function
readonly datasetKeyProvider?: Function
}
interface IChartState {
/** Add your states here */
}
export class Chart extends React.Component<IChartProps, IChartState> {
// tslint:disable-next-line
private chartInstance: any
private shadowData: {}
constructor(props: IChartProps) {
super(props)
}
public componentWillMount() {
// this.chartInstance = undefined
}
public componentDidMount() {
this.renderChart()
}
// public componentWillReceiveProps(nextProps: IChartProps) {}
public shouldComponentUpdate(nextProps: IChartProps, nextState: IChartState) {
const props = this.props
if (nextProps.reRender === true) {
return true
}
if (props.height !== nextProps.height || props.width !== nextProps.width) {
return true
}
if (props.type !== nextProps.type) {
return true
}
if (!IsEqual(props.options, nextProps.options)) {
return true
}
const nextData = this.transformDataProp(nextProps)
if (!IsEqual(this.shadowData, nextData)) {
return true
}
return false
}
// public componentWillUpdate(nextProps: IChartProps, nextState: IChartState) {}
public componentDidUpdate(prevProps: IChartProps, prevState: IChartState) {
if (this.props.reRender) {
this.chartInstance.destroy()
this.renderChart()
return
}
this.updateChart()
}
public transformDataProp(props: IChartProps) {
const data = props.data
if (typeof data === 'function') {
const node = document.getElementById('bar-chart') as HTMLCanvasElement
return data(node)
} else {
return data
}
}
public memoizeDataProps(props?: IChartProps) {
if (!this.props.data) {
return
}
const data = this.transformDataProp(this.props)
this.shadowData = {
...data,
datasets:
data.datasets &&
data.datasets.map((set: string[]) => {
return { ...set }
})
}
return data
}
public updateChart() {
const options = this.props.options
const data = this.memoizeDataProps(this.props)
if (!this.chartInstance) {
return
}
if (options) {
this.chartInstance.options = subChart.helpers.configMerge(
this.chartInstance.options,
options
)
}
let currentDatasets =
(this.chartInstance.config.data &&
this.chartInstance.config.data.datasets) ||
[]
const nextDatasets = data.datasets || []
const currentDatasetKeys = currentDatasets.map(
this.props.datasetKeyProvider
)
const nextDatasetKeys = nextDatasets.map(this.props.datasetKeyProvider)
const newDatasets = nextDatasets.filter(
(d: object) =>
currentDatasetKeys.indexOf(this.props.datasetKeyProvider(d)) === -1
)
for (let idx = currentDatasets.length - 1; idx >= 0; idx -= 1) {
const currentDatasetKey = this.props.datasetKeyProvider(
currentDatasets[idx]
)
if (nextDatasetKeys.indexOf(currentDatasetKey) === -1) {
// deleted series
currentDatasets.splice(idx, 1)
} else {
const retainedDataset = Find(
nextDatasets,
(d: object) => this.props.datasetKeyProvider(d) === currentDatasetKey
)
if (retainedDataset) {
// update it in place if it is a retained dataset
currentDatasets[idx].data.splice(retainedDataset.data.length)
retainedDataset.data.forEach((point: number, pid: number) => {
currentDatasets[idx].data[pid] = retainedDataset.data[pid]
})
// const { data, ...otherProps } = retainedDataset
currentDatasets[idx] = {
data: currentDatasets[idx].data,
...currentDatasets[idx],
...retainedDataset.otherProps
}
}
}
}
// finally add any new series
newDatasets.forEach((d: object) => currentDatasets.push(d))
const { datasets, ...rest } = data
this.chartInstance.config.data = {
...this.chartInstance.config.data,
...rest
}
this.chartInstance.update()
}
public componentWillUnmount() {
this.chartInstance.destroy()
}
public onClickEvent = (event: React.MouseEvent<HTMLCanvasElement>) => {
// this.props.getDatasetAtEvent &&
this.props.getDatasetAtEvent(
this.chartInstance.getDatasetAtEvent(event),
event
)
// this.props.getElementAtEvent &&
this.props.getElementAtEvent(
this.chartInstance.getElementAtEvent(event),
event
)
// this.props.getElementsAtEvent &&
this.props.getElementsAtEvent(
this.chartInstance.getElementsAtEvent(event),
event
)
// this.props.onElementsClick &&
this.props.onElementsClick(
this.chartInstance.getElementsAtEvent(event),
event
)
}
public render() {
const className = ClassNames('chart', this.props.className)
// bar.update()
return (
<div className={className}>
<canvas
id="chart-instance"
width={this.props.width ? this.props.width : '400'}
height={this.props.height ? this.props.height : '400'}
onClick={this.onClickEvent}
/>
</div>
)
}
public renderChart() {
const { options, type, data } = this.props
const node = document.getElementById('chart-instance') as HTMLCanvasElement
// const data = this.memoizeDataProps()
this.chartInstance = new ChartJS(node, {
type,
data,
options
})
}
}
can someone help me figure out why this won't work properly?
It might because of this:
currentDatasets[idx].data.splice(retainedDataset.data.length)
You should have a check on retainedDataset.data also:
if (retainedDataset && retainedDataset.data) { ... }

Resources