Flow - missing type annotation - reactjs

Pretty new to flow and trying to fix my code to include flow. This is my code at the moment and I've added flow type check and now getting errors so I need to annotate my code properly:
// #flow
import React, { Component } from 'react';
import { Manager, Reference, Popper } from 'react-popper';
import cx from 'classnames';
import css from './Tooltip.css';
import animationsCss from './TooltipAnimations.css';
type Props = {
theme: string,
eventsEnabled: boolean,
}
class Tooltip extends Component<Props> {
static defaultProps = {
theme: 'light',
eventsEnabled: true
};
firstOrderPlacement(placement) {
if (!placement) return null;
return placement.split('-')[0];
}
arrowDirectionClass(firstOrderPlacement) {
switch (firstOrderPlacement) {
case 'right':
return css.arrowLeft;
case 'left':
return css.arrowRight;
case 'top':
return css.arrowDown;
case 'bottom':
return css.arrowUp;
default:
return css.arrowUp;
}
}
render() {
const { placement, className, children, fadeIn, theme, eventsEnabled } = this.props;
return (
<Popper placement={placement} eventsEnabled={eventsEnabled}>
{({ ref, style, placement }) => {
const firstOrderPlacement = this.firstOrderPlacement(placement);
const arrowDirectionClass = this.arrowDirectionClass(firstOrderPlacement);
const subContainerStyle = {
display: 'flex',
flexDirection:
firstOrderPlacement === 'top' || firstOrderPlacement === 'bottom' ? 'column' : 'row',
};
const childrenContainerClassName = cx(
css.childrenContainer,
css.wrapper,
theme === "dark" ? css.dark : css.light
);
const content = <div className={childrenContainerClassName}>{children}</div>;
const subContainerClassName = fadeIn ? cx(animationsCss.fadeIn, className) : className;
return (
<div
ref={ref}
className={cx(css.container, css.mobileTooltip)}
style={style}
data-placement={placement}
>
<div className={subContainerClassName} style={subContainerStyle}>
{(firstOrderPlacement === 'left' || firstOrderPlacement === 'top') && content}
<div>
<div className={cx(css.arrow, arrowDirectionClass)} />
</div>
{(firstOrderPlacement === 'right' || firstOrderPlacement === 'bottom') && content}
</div>
</div>
);
}}
</Popper>
);
}
}
export { Manager as TooltipManager, Reference as TooltipReference, Tooltip };
I believe I need to add these to my props. Are these correct?
placement: string,
className?: string,
children?: any,
fadeIn: any,
I'm missing type annotation for these two which I'm not sure how to proceed:
firstOrderPlacement(placement) {..}
arrowDirectionClass(firstOrderPlacement) {..}
Any help?

Annotate Props like:
type Props = {
...
placement: string,
className?: string,
children?: any,
fadeIn: any,
...
}
Placement parameter is string firstOrderPlacement(placement) {..} and return value of function is null or string, so you can use maybe type for annotation:
firstOrderPlacement(placement: string): ?string => {...}
Or with union type because maybe type covers undefined.
type FirstOrder = string | null;
Result of firstOrderPlacement function is parameter of arrowDirectionClass. So type of parameter:
arrowDirectionClass(firstOrderPlacement: ?string): string => {...}
Or:
arrowDirectionClass(firstOrderPlacement: FirstOrder): string => {...}

Are these correct?
Try them and find out!
Keep in mind, though, that using any is a shortcut that basically just turns off typing. For children where you are expecting react elements you should usually just use React.node. This will work if you basically want arbitrary nested DOM-like content like react elements, arrays of elements, strings of text, etc. fadeIn looks like it's probably a string, but it's kind of hard to tell without seeing where it comes from.
I'm missing type annotation for these two which I'm not sure how to proceed:
Flow wants you to type the arguments to the functions. Something like:
firstOrderPlacement(placement: string) {
or whatever.

Related

Adding an image into a ToggleButtonGroup

I want to implement this design:
And I'm currently kind of stuck at the images-answers part.
I have the idea, but there is a problem.
import { useState } from 'react';
import { ToggleButtonGroup, ToggleButton } from 'react-bootstrap';
import './css/GameAnswers.css';
import GameImage from './GameImage';
type GameAnswersProps = {
arrayAnswers: Array<string>,
arrayAnswersImages?: Array<string>,
correctAnswer: string,
setFinishedGameHandler: Function
}
function GameAnswers(props: GameAnswersProps) {
const [selectedAnswer, setSelectedAnswer] = useState("");
const handleAnswerChange = function (e: React.ChangeEvent<HTMLInputElement>) {
setSelectedAnswer(e.target.value);
}
const determineButtonVariant = function (answer: string) {
if (answer !== selectedAnswer) {
return "primary";
} else if (answer === props.correctAnswer) {
props.setFinishedGameHandler(true);
return "success";
} else {
props.setFinishedGameHandler(false);
return "danger";
}
}
return (
<div className="game-answers">
{
props.arrayAnswersImages === undefined ?
<GameImage className="game-details-image" images={[{ imgSrc: "/Untitled.png", imgAlt: 'test' }]} /> :
""
}
<ToggleButtonGroup type="radio" name="answers">
{props.arrayAnswers.map(function (answer, index) {
return <div>
{props.arrayAnswersImages !== undefined ?
<GameImage className="game-togglebutton" images={[{ imgSrc: props.arrayAnswersImages[index], imgAlt: 'test' }]} /> : ""
}
<ToggleButton
id={answer}
value={answer}
onChange={handleAnswerChange}
variant={determineButtonVariant(answer)}
>{answer}</ToggleButton>
</div>
})}
</ToggleButtonGroup>
</div>
);
}
export default GameAnswers;
Because the map function returns a div, my ToggleButton doesn't work properly. If I will click on the buttons, nothing will happen.
If I remove the div wrapping, I will get an error because I return two components.
If I also remove the GameImage component, everything will work fine, but I will have no image above the text.
So, how can I make my code work properly while I still have the image above the ToggleButton?
In react you can use Fragments, it's a common way to exclude that extra node when e.g. returing a list.
Change your wrapping <div></div> tag into ->
<React.Fragment></React.Fragment>.
A shorter syntax for this is <></>.

Type properties so that they are mutually exclusive

How should I type my properties, so that either buttonLink or clickHandler, but not both, could be passed to my component at the same time?
export type ConfirmationProps = {
buttonLink?: string
clickHandler?: OnClick
}
export const Confirmation: FC<ConfirmationProps> = ({
buttonLink,
clickHandler,
}): JSX.Element => {
return (
{clickHandler && (
<Button onClick={clickHandler}>
Button
</Button>
)}
{buttonLink && (
<Link href={buttonLink}>
Link
</Link>
)}
)
}
Yes, it can be achieved by declaring possible variants and using never type:
export type ConfirmationProps = ({
buttonLink: string
clickHandler?: never
} | {
buttonLink?: never
clickHandler: OnClick
});
For your information, if there are other props which are common and acceptable for both variants then you can use the same solution, but firstly declare the common part and then use the & intersection literal:
export type ConfirmationProps = {
commonprop1: number
commonprop2: number
} & ({
buttonLink: string
clickHandler?: never
} | {
buttonLink?: never
clickHandler: OnClick
});

React.cloneElement clone already cloned element to add new props

I have have TestWrapper component that clones element and is supposed to make the background blue. Test is also doing the same, but instead sets color. I was expecting the rendered element to have blue background and red text, but it is only red. Here is the example:
const Test: React.FC<{ element: React.ReactElement }> = ({ element }) => {
return React.cloneElement(element, {
const isSelected = useIsSelected();
style: { ...element.props.style, color: isSelected ? 'red' : 'black' }
});
};
const TestWrapper: React.FC<{ element: React.ReactElement }> = ({
element
}) => {
// BackgroundColor is blue
const { backgroundColor } = useTheme();
return React.cloneElement(element, {
style: { ...element.props.style, background: backgroundColor }
});
};
export function App() {
return <TestWrapper element={<Test element={<h1>Heading</h1>} />} />;
}
How can I achieve this? I could do this differently, but I have to be able to access hook methods from Test and TestWrapper.
Simple codesandbox example: https://codesandbox.io/s/serene-bassi-ve1ym?file=/src/App.tsx
In TestWrapper you are cloning the Test component and applying your style props to it, which are not being passed down to the element that it's cloning itself. Just returning a cloned element doesn't create a referential equality where doing something to the component will affect the element it is cloning. You would need to give a style prop to Test and pass it down to the element being cloned:
const Test: React.FC<{
style?: React.CSSProperties;
element: React.ReactElement;
}> = ({ element, style }) => {
return React.cloneElement(element, {
style: { ...element.props.style, ...style, color: "red" }
});
};
I made a fork here. Hopefully this helps!

Why can I not use string interpolation for classNames in React?

Here's my component:
export interface ButtonProps {
classes?: string[];
}
const Button: FC<ButtonProps> = (props) => {
const classes = props.classes || [];
return (
<button className={`button ${...classes}`}>
{children}
</button>
)
};
export default Button;
I'm getting an error - "property classes does not exist...".
It would be great if someone could explain to me why the above code doesn't work.
Many thanks.
One problem is that classes is an array of strings, not an object, so spreading it into a template literal doesn't make sense. Join by whitespace instead:
<button className={`button ${classes.join(' ')}`}>
Spread can only be used in:
argument lists: fn(...args)
array literals: [...items]
object literals: { ...props }
But it can't be used wherever a generic expression is expected.
you can also use clsx
const { foo, bar, baz } = classes
const style = clsx(foo, bar, baz)
<button className={`button ${style}`}>
you can also use as condition
const style = clsx({
[classes.root] : true, //always apply
[classes.open] : open //only when open === true
})

React - Element type is invalid - how to debug this error?

How can this error message given by react debugged ? To figure out what is really causing it ? I googled the error but it seems to be caused by different things.
invariant.js:38 Uncaught Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
given by this code:
// #flow
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore , combineReducers} from 'redux'
import deepFreeze from 'deepfreeze'
import expect from 'expect'
var _ = require('lodash')
type State$Todo = {
text:string;
completed:boolean;
id:number;
};
class Todo {
static make(t:string,id:number):State$Todo{
return {text:t,id:id,completed:false}
}
static toggle(t:State$Todo):State$Todo {
return {...t, completed:!t.completed};
}
};
type Action$SetVisibilityFilter = {
type:'SET_VISIBILITY_FILTER',
filter:State$VisibilityFilter
};
type Action$ADD_TODO = {
type:'ADD_TODO',
text:string,
id:number
};
type Action$TOGGLE_TODO = { type:'TOGGLE_TODO', id:number }
type Action$Todo = Action$ADD_TODO | Action$TOGGLE_TODO
type Action$App = Action$Todo | Action$SetVisibilityFilter
type State$TodoList = State$Todo[];
type State$VisibilityFilter = 'SHOW_ACTIVE' | 'SHOW_ALL' | 'SHOW_COMPLETED'
type State$App = {
todos:State$TodoList,
visibilityFilter:State$VisibilityFilter
}
const todosReducer = (state: State$TodoList=[], action: Action$App) :State$TodoList=>{
switch (action.type){
case 'ADD_TODO' : return [ ... state, Todo.make(action.text, action.id)];
case 'TOGGLE_TODO':
const id=action.id;
return _.map(state, (td) => (td.id==id) ? Todo.toggle(td) : td );
default : return state;
}
};
const visibilityFilterReducer = (state:State$VisibilityFilter = 'SHOW_ALL', action:Action$App) : State$VisibilityFilter => {
switch(action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default : return state;
}
}
const todoApp = (state : State$App = {todos:[],visibilityFilter:'SHOW_ALL'}, action: Action$App) : State$App => {
return { todos: todosReducer(state.todos, action), visibilityFilter: visibilityFilterReducer(state.visibilityFilter,action) };
}
//const todoApp =combineReducers({todos:todosReducer, visibilityFilter:visibilityFilterReducer})
const store = createStore (todoApp)
type FilterLinkProps={
filter:State$VisibilityFilter,
currentFilter:State$VisibilityFilter,
children:React$Element<*>
};
const FilterLink = ({
filter,
currentFilter,
children
}:FilterLinkProps) => {
if(filter===currentFilter) {
return <span>{children}</span>
}
return (
<a href='#'
onClick={e => {
e.preventDefault();
store.dispatch(({
type: 'SET_VISIBILITY_FILTER',
filter
}:Action$SetVisibilityFilter));
}}
>
{children}
</a>
);
};
const getVisibleTodos = (
todos:State$TodoList,
filter:State$VisibilityFilter
) : State$TodoList => {
switch (filter) {
case ('SHOW_ALL' :State$VisibilityFilter):
return todos;
case ('SHOW_COMPLETED':State$VisibilityFilter):
return todos.filter(
t => t.completed
);
case ('SHOW_ACTIVE':State$VisibilityFilter):
return todos.filter(
t => !t.completed
);
default:
throw "undefined action"
}
}
let nextTodoId = 0;
const TodoReactElement=(props:{onClick:Function,completed:boolean,text:string}) =>(
<li onClick={props.onClick}
style ={{ textDecoration: props.completed ? 'line-through' : 'none'}} >
{props.text}
</li>
);
type TodoListReactComponentProps ={todos:State$TodoList,onTodoClick:Function}
const TodoList =(props:TodoListReactComponentProps) =>(
<ul>
{props.todos.map( todo=>
<TodoReactElement
key ={todo.id}
completed={todo.completed}
onClick={()=> props.onTodoClick(todo.id)}
text= {todo.text} >
</TodoReactElement>)}
</ul>
)
class TodoApp extends React.Component {
render() {
const todos : State$TodoList= this.props.todos;
const visibilityFilter :State$VisibilityFilter=
this.props.visibilityFilter;
const visibleTodos :State$TodoList = getVisibleTodos(
todos, visibilityFilter );
return (
<div>
<input ref ={ node => {this.input=node;} } />
<button onClick={() => {
store.dispatch(({
type: 'ADD_TODO',
text: 'Test'+this.input.value,
id: nextTodoId++
} : Action$ADD_TODO));
this.input.value='';
}}>
Add Todo
</button>
<TodoList todos={visibleTodos}
onTodoClick={id=> store.dispatch(({type:'TOGGLE_TODO',id}:Action$TOGGLE_TODO))}>
</TodoList>
<p>
Show:
{' '}
<FilterLink
filter='SHOW_ALL'
currentFilter={visibilityFilter}
>
All
</FilterLink>
{' '}
<FilterLink
filter='SHOW_ACTIVE'
currentFilter={visibilityFilter}
>
Active
</FilterLink>
{' '}
<FilterLink
filter='SHOW_COMPLETED'
currentFilter={visibilityFilter}
>
Completed
</FilterLink>
</p>
</div>
);
}
}
const root = document.getElementById('root')
const render = () => {
ReactDOM.render(
<TodoApp {...store.getState()} />,root
);
};
store.subscribe(render)
render();
screenshot:
Unfortunately I only speak Javascript, not Typescript (or whatever that was), but the error itself is pretty clear: you tried to render something (an object) that React wasn't expecting (because it expects strings/functions).
I see two possibilities:
1) there is a bug in invariant.js; since the error came from there this could be the problem, but more likely ...
2) one of your render methods includes (in its return value) an object
Unfortunately, as you've discovered, React stack traces are not always particularly helpful. Personally I would recommend just commenting out the render methods of your classes, one at a time, starting with the outermost one (which I think is FilterLink in your case), and replace them temporarily with a simple return <div/>.
Then try to produce the error again. If you still get it, restore the render method and go do the same thing to the next class up the component chain. If not, you've found your problematic render. If you can't immediately see the problem by looking at it, try logging every variable involved in it (or, if you use Lodash/Underscore, _.isObject(thatVariable)) until you find the problem.
I think the error message is quite straight forward, but in what component? That's the question.
Actually, in the callstack, there are some points has some parent components.
Add a break point at such as instantiateReactComponent (instantiateReactComponent.js:74) and retry.
Clicking mountComponent ...reactConciler.js... will lead you to calling internalInstance.mountComponent. and in the internalInstance, you can find some meaningful element type in _currentElement.type.
There you could find the child with invalid type.
You've specified here that FilterLink's "children" prop only accepts React elements:
type FilterLinkProps={
// ... snipped ...
children:React$Element<*>
};
...but you are passing non-React elements (objects, strings, etc). You need change prop type to "React.PropTypes.node" instead of "React.PropTypes.element" (sorry I don't speak that syntax either, but I can see what's going on, basically)
If someone could not relate his/her issue with other answers, this might be because of non-imported components. I had instantiated component and saved as element in a variable and used it in render function of another component.
//mainComp.tsx
const icon = <SomeIcon/>
return <div>{icon}</div>
* Did not get any error that SomeIcon is not imported. while trying to render this element type will be undefined.

Resources