Null check still throws Flow error - reactjs

What is the rule for where you need to put your null check to avoid Flow errors? This code surprised me by giving an error:
// #flow
import React, { Component } from "react";
type Props = {|
apples: ?Array<Array<string>>,
oranges: ?Array<Array<string>>,
|};
class FruitBasket extends Component<Props> {
render() {
if (this.props.oranges == null || this.props.apples == null) {
return null;
}
var dummyVariable = 16;
var apples = this.props.apples.map((ask) => {
return null;
})
var oranges = this.props.oranges.map((bid) => {
return null;
})
return null;
}
}
export default FruitBasket;
The error is:
Cannot call this.props.oranges.map because property map is missing in null or undefined [1].
It seems silly that the Flow compiler would "forget" the null check after the var apples = ... declaration.

This is an validation/ordering issue. Flow doesn't know what this.props.apples.map() does, so it is technically possible that it could end up setting this.props.oranges = null. Since that could happen, by the time this.props.oranges.map() is called, the if (this.props.oranges == null) refinement is no longer in effect, and this.props.oranges could be null, from the standpoint of the typechecker. You have two solutions:
Move the if (this.props.oranges == null) return null; check to just before the this.props.oranges.map() call. The downside being that then you split up your bail-out logic, and end up mapping apples for no reason.
Assign the values to temporary variables, so that Flow can tell that their type won't change later. This would be my recommendation.
render() {
if (this.props.oranges == null || this.props.apples == null) {
return null;
}
const { oranges, apples } = this.props;
var dummyVariable = 16;
var applesItems = apples.map((ask) => {
return null;
})
var orangesItems = oranges.map((bid) => {
return null;
})
return null;
}

Related

What is the reasoning behind "The final argument passed to useCallback changed size between renders ..." warning

Opening this question here, because StackOverflow is listed as a recommended place for asking React-related questions in React docs.
I am looking for a reasoning behind throwing a The final argument passed to useCallback changed size between renders. The order and size of this array must remain constant. warning.
After looking into React code it looks like React does not properly compare prevDeps and nextDeps arrays when they have different lengths.
The comparison function looks like this (some checks were omitted for brevity):
function areHookInputsEqual(prevDeps,nextDeps) {
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (Object.is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
Which means:
areHookInputsEqual( ['a','b'], ['c','d'] ) === false - correct
areHookInputsEqual( ['a','b','c'], ['a','b'] ) === true - wrong
areHookInputsEqual( ['a','b'], ['a','b','c'] ) === true - wrong
areHookInputsEqual( [], ['a'] ) === true - wrong
areHookInputsEqual( ['a'], [] ) === true - wrong
Why not to write this function as following and remove warning from the codebase?
function areHookInputsEqual(prevDeps,nextDeps) {
if (prevDeps.length !== nextDeps.length) {
return false;
}
for (let i = 0; i < prevDeps.length; i++) {
if (Object.is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
The use case which led to this question
We have a custom hook like this:
function useLoadMessageKeys(messageKeys: string[]) {
return React.useCallback(
() => {
return load(messageKeys)
},
messageKeys
)
}
Because of current React implementation, load does not get called when messageKeys change from [] to ['a'].
Update (how we currently solved this)
function areArraysEqual<T>(prevDeps: T[], nextDeps: T[]): boolean {
if (prevDeps === nextDeps) {
return true
}
if (prevDeps.length !== nextDeps.length) {
return false
}
for (let i = 0; i < prevDeps.length; i++) {
if (!Object.is(nextDeps[i], prevDeps[i])) {
return false
}
}
return true
}
export function useLoadMessageKeys(messageKeys: string[]) {
const messageKeysRef = React.useRef(messageKeys)
if (!areArraysEqual(messageKeys, messageKeysRef.current)) {
messageKeysRef.current = messageKeys
}
const currentMessageKeys = messageKeysRef.current
return React.useCallback(
() => load(currentMessageKeys),
[currentMessageKeys]
)
}
If the code linked in the question properly compared 2 arrays we'd avoid having this complexity.
I think the reasoning is that the array of dependencies should always be exactly the list of variables used inside the effect. So in particular it can be statically determined and cannot change size. If it does change size, you're probably doing something more than just listing the dependencies, so it is warning you that you are not using the dependency list as intended.
You could use instead use a version of messageKeys that does not change if it is only shallow equal to the previous one (untested):
const useMemoizedArray = (array: string[]) => {
const [memoizedArray, setMemoizedArray] = React.useState(array);
React.useEffect(() => {
// Define `isShallowEqual` yourself somewhere
if (!isShallowEqual(array, memoizedArray)) {
setMemoizedArray(array);
}
}, [array, memoizedArray]);
return memoizedArray;
};
function useLoadMessageKeys(messageKeys: string[]) {
const memoizedMessageKeys = useMemoizedArray(messageKeys);
return React.useCallback(
() => {
return load(memoizedMessageKeys)
},
[memoizedMessageKeys]
)
}

Scientific Calculator with TypeScript & React crashes when I try to do more than one calculation

The GitHub repo has all the code: https://github.com/isZachariah/scientific-calculator
Don't judge the styles its not anywhere near done, just the basics so far.
I built a custom hook that uses useReducer and when you press the '=' button it calls another function that parses the current string and returns the answer. If the input is something simple with two operands and an operation it works just fine, I can also chain operations like that, but as soon as I input something like, '3 + 3 - 2' it freezes or fails.
One error says 'Uncaught out of memory' while another one suggests adding an error boundary? I'm pretty new to react. Adding one more operation/ operand shouldn't create a bunch more memory.
This is the parser/evaluator:
export const useParser = (expr: string) => {
let queue: Token[] = []
parse(expr, queue) //queue
return resolve(queue) //queue
}
const last = (stack: string[]) => stack[stack.length-1];
export const parse = (expr: string, queue: object[] ) => { //
const stack: string[] = []
expr.split(' ').forEach((token) => {
if (/\d/.test(token)
|| /^-\d+.\d+$/.test(token)
|| /^\d+.\d+$/.test(token)) {
queue.push({type: 'number', value: parseFloat(token)})
}
if (token === '(') stack.push('(')
if (token === ')') {
while (last(stack) !== '(') {
let el = getProperty(OPERATORS, stack.pop())
queue.push({type: 'operation', value: stack.pop()})
if (stack.length <= 1 && stack[0] !== '(') {
throw new Error(`useParser: Mismatched Parens`)
}
}
stack.pop()
}
if (token in OPERATORS) {
if (stack.length !== 0) {
let curr = getProperty(OPERATORS, token)
let prev = getProperty(OPERATORS, last(stack))
while (greaterPrecedence(prev, curr) || equalPrecedence(prev, curr)) {
queue.push({ type: 'operation', value: last(stack) })
if (stack.length === 0) break;
}
}
stack.push(token)
}
});
while (stack.length !== 0) {
if (last(stack) === '(' || last(stack) === ')') stack.pop()
// let el = getProperty(OPERATORS, stack.pop())
queue.push({ type: 'operation', value: stack.pop() })
}
}
export type Token = {
type: string;
value: number | string
}
export const resolve = (tokens: Token[]) => {
let result = 0
let stack: number[] = []
tokens.forEach((element) => {
switch (element.type) {
case 'number':
if (typeof element.value === 'number') stack.push(element.value);
break;
case 'operation':
let a = stack.pop();
let b = stack.pop();
if (typeof element.value === "string") {
const operator = getProperty(OPERATORS, element.value)
result = operator.binaryFunction(a, b);
}
stack.push(result);
break;
}
});
return result.toString();
}
I didn't include the helper functions but they do what they are supposed to and the calculating functions as it should, it only crashes beyond x * y -
I have tried moving the queue on to localStorage, that didn't work. I tried adding state in to the useParser function but then I had no idea how to call it from the reducer function because at that point it is a hook of its own and will need to be called at the top of a react function component. I tried adding more node memory space but I feel like that doesn't solve the problem, if I have a memory leak I need to figure it out. Maybe I don't fully understand how state works in React and there is something I am missing. I would love some help cuz I am stuck. Thank you.

Expected an assignment or function call and instead saw an expression no-unused-expressions- ReactJs

I am trying to create a reservation app. I am quite new to React, and I tried searching and I can't seem to find what the problem is.
As my code has over 400 lines I will post only the ones I'm getting an error at.
I am following this article https://medium.com/#kris101/building-appointment-scheduler-app-in-react-and-nodejs-d01f7294a9fd
Error screenshot
The specific errors are:
Line 136:11: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 139:11: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 147:7: Expected an assignment or function call and instead saw an expression no-unused-expressions
I tried disabling eslint but when I do so I'm getting more errors.
handleDBReponse(response) {
const reservations = response;
const today = moment().startOf("day"); //start of today 12 am
const initialSchedule = {};
initialSchedule[today.format("YYYY-DD-MM")] = true;
const schedule = !reservations.length
? initialSchedule
: reservations.reduce((currentSchedule, reservation) => {
const { slot_date, slot_time } = reservation;
const dateString = moment(slot_date, "YYYY-DD-MM").format(
"YYYY-DD-MM"
);
!currentSchedule[slot_date]
? (currentSchedule[dateString] = Array(8).fill(false))
: null;
Array.isArray(currentSchedule[dateString])
? (currentSchedule[dateString][slot_time] = true)
: null;
return currentSchedule;
}, initialSchedule);
for (let day in schedule) {
let slots = schedule[day];
slots.length
? slots.every(slot => slot === true) ? (schedule[day] = true) : null
: null;
}
this.setState({
schedule: schedule
});
}
I can see lots of linting issue in the above code.As of now I have fixed Expected an assignment or function call issue.
Please check the below code and let me know if it works
handleDBReponse(response) {
const reservations = response;
const today = moment().startOf('day'); // start of today 12 am
const initialSchedule: any = {};
initialSchedule[today.format('YYYY-DD-MM')] = true;
const schedule = !reservations.length
? initialSchedule
: reservations.reduce((currentSchedule: any, reservation: any) => {
const tempSchedule = currentSchedule;
const { slot_date, slot_time } = reservation;
const dateString = moment(slot_date, 'YYYY-DD-MM').format('YYYY-DD-MM');
tempSchedule[dateString] = !tempSchedule[slot_date] ? (tempSchedule[dateString] = Array(8).fill(false)) : null;
if (Array.isArray(tempSchedule[dateString])) {
tempSchedule[dateString][slot_time] = true;
}
return test;
}, initialSchedule);
for (const day in schedule) {
const tempSchedule = schedule;
const slots = tempSchedule[day];
const checkEverySlot = slots.every((slot: boolean) => slot === true);
if (slots.length && checkEverySlot) {
tempSchedule[day] = true;
}
}
this.setState({
schedule,
});
}
Note: It is good practice to write identifier in camel case.
This issue is around your use of ternary statements;
!currentSchedule[slot_date] ? (currentSchedule[dateString] = Array(8).fill(false)) : null;
Array.isArray(currentSchedule[dateString]) ? (currentSchedule[dateString[slot_time] = true) : null
slots.length ? slots.every(slot => slot === true) ? (schedule[day] = true) : null : null;
You can't use ternary statements in this way, you should replace these with individual if statements
e.g
if(currentSchedule[slot_date]) currentSchedule[dateString] = Array(8).fill(false)
An example of a proper use of ternaries would be when assigning a variable:
let myVar = x === 10 ? 'x is 10' : 'x is not 10'
The function handleDBReponse doesn't seems to be a well defined function. Try adding public before its name. Like public handleDBReponse(response) {...}.

jsPsych integration with React

I've been trying to put a custom experiment made with jsPsych, a library for running behavioral experiments, with custom plugins into a React container but i've run into some issues.
The first thing i tried to do was use makebrainwave's jspsych-react package, but when i try to run the example, after having to change the routes of the files in the import section and replacing the use of 'fs', i'm still running into this error:
Trial level node is missing the "type" parameter. The parameters for the node are: {} experiment.js:924
TypeError: jsPsych.plugins[trial.type] is undefined[Learn More] experiment.js:1051
Could anyone help me with it? It looks like it's trying to initialize something and failing, but otherwise I'm kinda lost in the error.
The first error occurs in the constructor function:
// constructor
var _construct = function() {
// store a link to the parent of this node
parent_node = parent;
// create the ID for this node
if (typeof parent == 'undefined') {
relative_id = 0;
} else {
relative_id = relativeID;
}
// check if there is a timeline parameter
// if there is, then this node has its own timeline
if ((typeof parameters.timeline !== 'undefined') || (typeof jsPsych.plugins[trial_type] == 'function')) {
// create timeline properties
timeline_parameters = {
timeline: [],
loop_function: parameters.loop_function,
conditional_function: parameters.conditional_function,
sample: parameters.sample,
randomize_order: typeof parameters.randomize_order == 'undefined' ? false : parameters.randomize_order,
repetitions: typeof parameters.repetitions == 'undefined' ? 1 : parameters.repetitions,
timeline_variables: typeof parameters.timeline_variables == 'undefined' ? [{}] : parameters.timeline_variables
};
self.setTimelineVariablesOrder();
// extract all of the node level data and parameters
var node_data = Object.assign({}, parameters);
delete node_data.timeline;
delete node_data.conditional_function;
delete node_data.loop_function;
delete node_data.randomize_order;
delete node_data.repetitions;
delete node_data.timeline_variables;
delete node_data.sample;
node_trial_data = node_data; // store for later...
// create a TimelineNode for each element in the timeline
for (var i = 0; i < parameters.timeline.length; i++) {
timeline_parameters.timeline.push(new TimelineNode(Object.assign({}, node_data, parameters.timeline[i]), self, i));
}
}
// if there is no timeline parameter, then this node is a trial node
else {
// check to see if a valid trial type is defined
var trial_type = parameters.type;
if (typeof trial_type == 'undefined') {
console.error('Trial level node is missing the "type" parameter. The parameters for the node are: ' + JSON.stringify(parameters));
} else if ((typeof jsPsych.plugins[trial_type] == 'undefined') && (trial_type.toString().replace(/\s/g,'') != "function(){returntimeline.timelineVariable(varname);}")) {
console.error('No plugin loaded for trials of type "' + trial_type + '"');
}
// create a deep copy of the parameters for the trial
trial_parameters = Object.assign({}, parameters);
}
}();
And the second one in the first line of the folowing function
function setDefaultValues(trial){
var trial_parameters = Object.keys(jsPsych.plugins[trial.type].info.parameters);
for(var i=0; i<trial_parameters.length; i++){
if(typeof trial[trial_parameters[i]] == 'undefined' || trial[trial_parameters[i]] === null){
if(typeof jsPsych.plugins[trial.type].info.parameters[trial_parameters[i]].default == 'undefined'){
console.error('You must specify a value for the '+trial_parameters[i]+' parameter in the '+trial.type+' plugin.');
} else {
trial[trial_parameters[i]] = jsPsych.plugins[trial.type].info.parameters[trial_parameters[i]].default;
}
}
}
}
I installed jspsych-react with yarn into the project and the test container is the following:
import React, { Component } from 'react'
import { Experiment } from "jspsych-react";
import { visualOddball } from "./examples/timelines/visualOddball";
import { callbackHTMLDisplay } from "./examples/plugins/callbackHTMLDisplay";
import { callbackImageDisplay } from "./examples/plugins/callbackImageDisplay";
export default class ExperimentComponent extends Component {
render() {
return (
<div>
<Experiment
settings={{ timeline: visualOddball }}
plugins={{
"callback-html-display": callbackHTMLDisplay,
"callback-image-display": callbackImageDisplay
}}
/>
</div>
);
}
}
Has anyone integrated something similar without using the jspsych-react package? which approach did you take?
Thanks in advance!

Is it possible to retrieve a component's instance from a React Fiber?

Before v16 of React -- that is, before the introduction of React fibers -- it was possible to take a DOM element and retrieve the React component instance as follows:
const getReactComponent = dom => {
let found = false;
const keys = Object.keys(dom);
keys.forEach(key => {
if (key.startsWith('__reactInternalInstance$')) {
const compInternals = dom[key]._currentElement;
const compWrapper = compInternals._owner;
const comp = compWrapper._instance;
found = comp;
}
});
return found || null;
};
This no longer works for React v16, which uses the new Fiber implementation. Specifically, the above code throws an error at the line const comparWrapper = compInternals._owner because there is no _owner property anymore. Thus you cannot also access the _instance.
My question here is how would we retrieve the instance from a DOM element in v16's Fiber implementation?
You may try the function below (updated to work for React <16 and 16+):
window.FindReact = function(dom) {
let key = Object.keys(dom).find(key=>key.startsWith("__reactInternalInstance$"));
let internalInstance = dom[key];
if (internalInstance == null) return null;
if (internalInstance.return) { // react 16+
return internalInstance._debugOwner
? internalInstance._debugOwner.stateNode
: internalInstance.return.stateNode;
} else { // react <16
return internalInstance._currentElement._owner._instance;
}
}
Usage:
var someElement = document.getElementById("someElement");
FindReact(someElement).setState({test1: test2});
React 17 is slightly different:
function findReact(dom) {
let key = Object.keys(dom).find(key => key.startsWith("__reactFiber$"));
let internalInstance = dom[key];
if (internalInstance == null) return "internalInstance is null: " + key;
if (internalInstance.return) { // react 16+
return internalInstance._debugOwner
? internalInstance._debugOwner.stateNode
: internalInstance.return.stateNode;
} else { // react <16
return internalInstance._currentElement._owner._instance;
}
}
the domElement in this is the tr with the data-param-name of the field you are trying to change:
var domElement = ?.querySelectorAll('tr[data-param-name="<my field name>"]')

Resources