Method must be called after React Context render - reactjs

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>
);
}
}

Related

type 'undefined' is not assignable to type, losing type in map()

I have an action in my redux toolkit that's attempting to set some state. Relevant code below:
interfaces
export interface ProposalTag {
id: number;
name: string;
hex: string;
color: string;
}
export interface ProposalSelectedTag {
proposal_id: number;
proposal_tag_id: number;
}
redux
import { ProposalTag, ProposalSelectedTag } from '../../types/proposalTags';
interface ProposalTagsSlice {
proposalTags: ProposalTag[];
selectedProposalTags: ProposalTag[];
}
const initialState: ProposalTagsSlice = {
proposalTags: [],
selectedProposalTags: [],
};
const matchTags = (
selectedTags: ProposalSelectedTag[],
proposalTags: ProposalTag[],
): ProposalTag[] => {
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
return proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id);
});
return tags ?? [];
};
export const proposalTagsSlice = createSlice({
name: 'proposalTags',
initialState,
reducers: {
setSelectedProposalTags: (state, action: PayloadAction<ProposalSelectedTag[]>) => {
if (state.proposalTags === undefined) return;
state.selectedProposalTags =
action.payload === null ? [] : matchTags(action.payload, state.proposalTags);
},
},
});
The goal of matchTags is to convert the payload of ProposalSelectedTag[] to ProposalTag[]. So in theory, ProposalSelectedTag.proposal_tag_id
The type errors I get back are the following:
Did I lose typing somewhere in matchTags?
That's because Array.prototype.find will return undefined if the element is not found.
If you are sure that the item exists in the list, you can make an assertion that will calm TypeScript down.
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
const item = proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id);
if (!item) throw new Error('item was not found')
return item
});
You can also use !
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
return proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id)!;
});
Or you can set a default value
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
return proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id) ?? 10;
});

converted HTML to Editorstate but not be able to use

i writen a function to convert HTML to editorState. it's work as i want but i can't use it when i assign it into the draft-wysiwyg
first function i use to call to change HTML to EditorState this function will call the other function and create EditorState on it own
function convertToEditor(markup: string): EditorState {
if (markup === '<p></p>') {
return EditorState.createEmpty()
}
const root = markupToObject(markup)
const blockContentArray: Array<ContentBlock> = []
_.forEach(root, (node, i) => {
const contentBlock = new ContentBlock({
key: genKey(),
text: node.textContent,
type: 'unstyled',
characterList: Immutable.List(getCharactorList(node.childNodes)),
data: node.getAttribute('style')?.includes('text-align') ? { 'text-align': node.getAttribute('style').split(':')[1].replace(';', '') } : {},
})
blockContentArray.push(contentBlock)
})
const contentState: ContentState = ContentState.createFromBlockArray(blockContentArray)
const editorState: EditorState = EditorState.createWithContent(contentState)
console.log(editorState)
return editorState
}
markupToObject and getCharactorList is just a function that i write to lower the complexity of the coding
function markupToObject(markup): HTMLCollection {
const div = document.createElement('div')
div.innerHTML = markup.trim()
return div.children
}
function getCharactorList(nodes: NodeListOf<ChildNode>, style?: Array<'BOLD' | 'ITALIC' | 'UNDERLINE'>): Array<CharacterMetadata> {
const characterList: Array<CharacterMetadata> = []
_.forEach(nodes, (node) => {
if (node.nodeName === '#text') {
_.forEach(node.textContent, () => {
characterList.push(CharacterMetadata.create({ style: style, entity: null }))
})
} else if (node.nodeName === 'A') {
_.forEach(node.textContent, () => {
characterList.push(CharacterMetadata.create({ style: [...(style || []), 'BOLD'], entity: null })) /* entity ID? */
})
} else {
const newStyle: Array<'BOLD' | 'ITALIC' | 'UNDERLINE'> = []
if (node.nodeName === 'STRONG') {
newStyle.push('BOLD')
} else if (node.nodeName === 'EM') {
newStyle.push('ITALIC')
} else if (node.nodeName === 'INS') {
newStyle.push('UNDERLINE')
}
characterList.push(...(getCharactorList(node.childNodes, [...newStyle, ...(style || [])]) || []))
}
})
return characterList
}
how i use
<RichText
onChange={(e) => {}}
value={convertToEditor(
'<p>text <strong>bold</strong> <strong><em>bold+italic</em></strong> </p> <p style="text-align:center;">center</p> <p><strong> link </strong></p> <p><strong> #name surname </strong></p>'
)}
/>
this is the error i got after use the Editorstate that convert from HTML
Uncaught TypeError: Cannot read properties of undefined (reading 'toList')

Failing to Compile React Native Application

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

Why is my object type not getting updated?

I'm creating a permission service using react typescript and I ran into the following problem. I have the class:
import {IPermission} from "../interfaces/IPermission";
class PermissionService {
private permissions: IPermission[] = [];
constructor(permissions: IPermission[]) {
this.permissions = permissions;
}
public getValue(key: string): IPermission['value'] {
const perm = this.permissions.find(permission => permission.key === key);
if (!perm) {
throw new Error('Could not find the permission');
}
return perm.value;
}
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms = [...this.permissions];
for (let i = 0; i < perms.length; i++) {
perms[i].defaultValue = defaultValue;
perms[i].value = value
}
this.permissions = perms;
console.log(perms);
}
public parseActivePermissions(permissions: IPermission[]): IPermission[] {
this.permissions.forEach(permission => {
permissions.forEach(activePermission => {
if (permission.key === activePermission.key) {
permission.defaultValue = activePermission.defaultValue;
permission.value = activePermission.value;
}
})
})
return this.permissions;
}
public getAll(): IPermission[] {
return this.permissions;
}
}
export default PermissionService;
and an AdminPermissions data file
import PermissionService from "../services/permission.service";
import {IPermission} from "../interfaces/IPermission";
import Permissions from "./Permissions";
const service: PermissionService = new PermissionService(Permissions);
service.modifyPermission('canAccessAcp', true, true);
const AdminPermissions: IPermission[] = service.getAll();
export default AdminPermissions;
The problem is, the service.modifyPermission() does not update the defaultValue and value of the permission. It's still false when console logging. Why is that?
UPDATE #1
Changed the file a bit. Still doesn't work. Now I'm directly changing the values, but they still log as false.
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
service.permissions.forEach(permission => {
if (permission.key === 'canAccessAcp') {
permission.defaultValue = true;
permission.value = true;
}
})
return service.permissions;
}
}
The problem is that with forEach you are not changing the actual value of each items, so you should do something like this:
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
return service.permissions.map(permission => {
if (permission.key === 'canAccessAcp') {
return (
{
...permission,
defaultValue: true,
value: true
}
)
}
return permission
});
}
}
I found a solution.
In the permission.service.ts
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms: IPermission[] = this.permissions.map(permission => {
if (permission.key === key) {
console.log('found permission');
return {
...permission,
defaultValue,
value
}
}
return permission
})
this.permissions = perms;
}

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