How to defined component /binding when using React ref in Reasonml? - ffi

I am having issues integrating react-system-notification module in my app, having read the documentation about Reason React Ref I am not sure why the reference is not passed down the stack; a hint would be much appreciated.
I keep getting the error below, I have used this component in the past in React but it seems that there is some issue when used in ReasonML/React. I suspect a null reference is passed down which breaks the component.
Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: undefined. You
likely forgot to export your component from the file it's defined in,
or you might have mixed up default and named imports.
Check the render method of Notifications.
Binding:
module NotificationSystem = {
[#bs.module "react-notification-system"] external reactClass : ReasonReact.reactClass = "default";
let make = ( children ) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props=Js.Obj.empty(),
children
)
};
Component
type action =
| AddNotification(string);
type state = {
_notificationSystem: ref(option(ReasonReact.reactRef)),
};
let setNotificationSystemRef = (notificationRef, {ReasonReact.state: state}) =>
state._notificationSystem := Js.toOption(notificationRef) ;
let component = ReasonReact.reducerComponent("Notifications");
let addNotification = (message, state) => {
switch state._notificationSystem^ {
| None => ()
| Some(r) => ReasonReact.refToJsObj(r)##addNotification({"message": message, "level": "success"});
}
};
let make = (_children) => {
...component,
initialState: () => {_notificationSystem: ref(None) },
reducer: (action, state) =>
switch action {
| AddNotification(message) => ReasonReact.SideEffects(((_) => addNotification(message, state)))
},
render: ({handle, reduce}) => (
<div>
<NotificationSystem ref=(handle(setNotificationSystemRef)) />
<button onClick=(reduce( (_) => AddNotification("Test Notification Test"))) > (ReasonReact.stringToElement("Click")) </button>
</div>
)
};

My guess would be that react-notification-system is not distributed as an es6 component, and therefore does not export default. Try removing default from the external:
[#bs.module "react-notification-system"] external reactClass : ReasonReact.reactClass = "";
You should always start by trying out the simplest implementation first, then build incrementally from there, to minimize possible causes of errors. Especially when dealing with something as error-prone as the js boundary. In this case that would be without the complex ref handling. You'll likely find that it still does not work, because of the above, and that you've been looking in the wrong place because you bit off more than you can chew.

After some further investigation, thanks to glensl hint and some messages exchanged on Discord I am posting the complete answer.
The issue was related to way bsb generated the "require" statement in the javascript output:
[#bs.module "react-notification-system"] external reactClass : ReasonReact.reactClass = "default";
Was being emitted as:
var ReactNotificationSystem = require("react-notification-system");
instead of
var NotificationSystem = require("react-notification-system");
Mightseem a little hacky however I got bsb to emit the correct javascript was using the following statement:
[#bs.module ] external reactClass : ReasonReact.reactClass = "react-notification-system/dist/NotificationSystem";
Then with some minor tweaking to the wrapper component, I was able to get it working with the following code:
module ReactNotificationSystem = {
[#bs.module ] external reactClass : ReasonReact.reactClass = "react-notification-system/dist/NotificationSystem";
let make = ( children ) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props=Js.Obj.empty(),
children
)
};
type action =
| AddNotification(string);
type state = {
_notificationSystem: ref(option(ReasonReact.reactRef)),
};
let setNotificationSystemRef = (notificationRef, {ReasonReact.state}) =>
state._notificationSystem := Js.Nullable.to_opt(notificationRef) ;
let component = ReasonReact.reducerComponent("Notifications");
let addNotification = (message, state) => {
switch state._notificationSystem^ {
| None => ()
| Some(r) => ReasonReact.refToJsObj(r)##addNotification({"message": message, "level": "success"});
}
};
let make = (_children) => {
...component,
initialState: () => {_notificationSystem: ref(None) },
reducer: (action, state) =>
switch action {
| AddNotification(message) => ReasonReact.SideEffects(((_) => addNotification(message, state)))
},
render: ({handle, reduce}) => (
<div>
<ReactNotificationSystem ref=(handle(setNotificationSystemRef)) />
<button onClick=(reduce( (_) => AddNotification("Hello"))) > (ReasonReact.stringToElement("Click")) </button>
</div>
)
};
A full sample working project can be found on Github here:

Related

Typescript allows to call a function with incorrect parameter's type

There is a function with next signature:
const verify = (address?: string) => void
There is a Component with props type:
type VerifyButtonProps = { onClick: () => void; }
There is a Component with props type:
type TButtonProps = { onClick?: React.MouseEventHandler<HTMLButtonElement>; children: React.ReactNode; };
[Codesanbox example]
(https://codesandbox.io/s/react-ts-playground-forked-v24gs7?file=/src/index.tsx/)
I'm getting the runtime error when click on the button and expect typescript points out to it, but compilation passes without any errors.
How can I prevent runtime error with help of typescript on the compiling step?
Your issue is basically following case (playground):
const verify = (address?: string) => address?.toLowerCase()
const verifyEmpty: () => void = verify
const onClick: (event: object) => void = verifyEmpty
onClick({ this: 'is not a string'})
Typescript allows each of these steps, however combined it produces a runtime error. This unsoundness is known, however Typescript does not guarantee soundness (no runtime errors if if there are no type errors) and this is one case where they decided to leave it unsound.
This means it is up to you to catch such errors. In your case, you could use onClick={() => verify()} to fix the error.
To avoid this situation you can replace
() => void
with
(...args: undefined[]) => void;
With that replacement you'll explicitly tell to your component, that function doesn't allow any number of arguments.
So, you can still pass verify function to your component. But inside of the component you can't pass it down to any function props with optional arguments, e.g. <Button onClick={verify} />
From the index.tsx file, the problem with your code is that your trying to run .toLowerCase() on an event.
Here is your code:
const verify = (address?: string) => { console.log("address = ", address?.toLowerCase());};
const App = (props) => {
return <VerifyButton onClick={verify} />;
};
I suggest you look into handlers but passing your function as you have in the onClick handler means that you get every argument passed to the verify function as address.
Log the address to console and see what I mean.
You may write your change handlers this way:
onClick={(e) => yourFunction(e)}
This is useful if you need something from the event, for example a value from an input.
OR
onClick={() => yourFunction()}
This will prevent you from passing unwanted arguments to your functions. Hope this helps.
u need to correctly type the verify function to match the expected onClick prop type in each component.
For VerifyButtonProps, the verify function can be passed like:
const VerifyButton: React.FC<VerifyButtonProps> = ({ onClick }) => (
<button onClick={onClick}>Verify</button>
);
const App = () => {
const handleVerify = () => {
verify();
};
return (
<div>
<VerifyButton onClick={handleVerify} />
</div>
);
};
For TButtonProps, the verify function needs to be converted to a proper React.MouseEventHandler:
const TButton: React.FC<TButtonProps> = ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
);
const App = () => {
const handleVerify = (event: React.MouseEvent<HTMLButtonElement>) => {
verify();
};
return (
<div>
<TButton onClick={handleVerify}>Verify</TButton>
</div>
);
};
when u make these changes, TypeScript will catch the type mismatch and display an error during the compilation step, rather than at runtime.

useswr crashes react component

After adding an SWR data fetch to my react component it crashes, if I comment it out it works fine.
I get the following error after uncommenting line const { data } = useSWR(`/api/views/${slug}`, fetcher)
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
The component
import { useEffect } from 'react'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function ViewCounter({
slug,
addView = false,
}: {
slug: string
addView?: boolean
}) {
const { data } = useSWR(`/api/views/${slug}`, fetcher)
// const viewCount = new Number(data?.total)
useEffect(() => {
console.log('ViewCounter useEffect', slug, addView)
const registerView = () =>
fetch(`/api/views/${slug}`, {
method: 'POST',
})
if (addView) {
registerView()
}
}, [slug])
// return `${viewCount > 0 ? viewCount.toLocaleString() : '–––'} views`
return (
<span className="flex items-center gap-1">
{data && data?.total}
</span>
)
}
Maybe it also has something to do with the fact that this component is included within another component that uses getStaticPaths and getStaticProps?
The full codebase can be found here: https://github.com/SennR-1952135/sennr
The problem seemed to be with the version of swr, i had to use 1.1.2 or lower, still dont know the exact reason but it works.

Why do I get TypeError: undefined is not an object (evaluating 'items.items.map') when removing a component?

I am trying to delete an item component from a list on my React app but I get a TypeError: undefined is not an object (evaluating 'items.items.map') error when the event is triggered. I can't see what is wrong with the code.
function _delete(id) {
return fetchWrapper.delete(`${baseUrl}/${id}`);
}
const [items, setItems] = useState(null);
useEffect(() => {
shoppingService.getAll().then((x) => setItems(x));
}, []);
function deleteItem(id) {
setItems(
items.items.map((x) => {
if (x.id === id) {
x.isDeleting = true;
}
return x;
})
);
shoppingService.delete(id).then(() => {
setItems((items) => items.items.filter((x) => x.id !== id));
});
}
{items &&
items.items.map((item) => (
<ListItem divider={true} key={item.id}>
...
</ListItem>
)
}
<IconButton
onClick={() => deleteItem(item.id)}
>
What could be causing it to go wrong? The item correctly gets removed from the collection on MongoDB.
Sandbox: View sandbox here
Thanks for the sandbox, it provides more context and makes this solvable.
It looks like you are attempting to export your functional component ShoppingList and this is the culprit:
export { ShoppingList };
That is leading to the error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
This is because when you use export { ShoppingList } you are exporting an object, not your functional component. Thus: type error. This can be verified with:
console.log({ShoppingList});
Returns:
Object {ShoppingList: function ShoppingList()}
To solve this, change your export command to:
export default ShoppingList;
This solves your original question, but other errors occur now. This is likely because shoppingService is not connected to the CodeSandbox. If you run into any further issues, I can lend a hand with another question.
I reread your question and took some liberties with removing shoppingService to idenitfy what is going on with your remove function. I believe what is happening is you are trying to edit an immutable variable items. Instead, spread the items into a new array and then filter-out the item with the matching id.
First clean-up your data frame to remove the duplicate items.items reference:
const myItems = [
{
id: "5f516ebbd8f4d11abe5919c8",
itemName: "Bacon"
},
{
id: "5f52e43e4dda46085f008aca",
itemName: "Eggs"
},
{
id: "5f57a3056a71a0143281c1a8",
itemName: "Oranges"
}
];
Here's what your function should look like:
function deleteItem(id) {
let newItems = [...items].filter((item) => item.id !== id);
setItems(newItems);
console.log(newItems);
}
CodeSandbox: https://codesandbox.io/s/stack-removing-items-m8thu?file=/src/App.js

How to query by text string which contains html tags using React Testing Library?

Current Working Solution
Using this html:
<p data-testid="foo">Name: <strong>Bob</strong> <em>(special guest)</em></p>
I can use the React Testing Library getByTestId method to find the textContent:
expect(getByTestId('foo').textContent).toEqual('Name: Bob (special guest)')
Is there a better way?
I would like to simply use this html:
<p>Name: <strong>Bob</strong> <em>(special guest)</em></p>
And use React Testing Library's getByText method like this:
expect(getByText('Name: Bob (special guest)')).toBeTruthy()
But this does not work.
So, the question…
Is there a simpler way to use React Testing Library to find strings of text content with the tags striped out?
Update 2
Having used this many times, I've created a helper. Below is an example test using this helper.
Test helper:
// withMarkup.ts
import { MatcherFunction } from '#testing-library/react'
type Query = (f: MatcherFunction) => HTMLElement
const withMarkup = (query: Query) => (text: string): HTMLElement =>
query((content: string, node: HTMLElement) => {
const hasText = (node: HTMLElement) => node.textContent === text
const childrenDontHaveText = Array.from(node.children).every(
child => !hasText(child as HTMLElement)
)
return hasText(node) && childrenDontHaveText
})
export default withMarkup
Test:
// app.test.tsx
import { render } from '#testing-library/react'
import App from './App'
import withMarkup from '../test/helpers/withMarkup'
it('tests foo and bar', () => {
const { getByText } = render(<App />)
const getByTextWithMarkup = withMarkup(getByText)
getByTextWithMarkup('Name: Bob (special guest)')
})
Update 1
Here is an example where a new matcher getByTextWithMarkup is created. Note that this function extends getByText in a test, thus it must be defined there. (Sure the function could be updated to accept getByText as a parameter.)
import { render } from "#testing-library/react";
import "jest-dom/extend-expect";
test("pass functions to matchers", () => {
const Hello = () => (
<div>
Hello <span>world</span>
</div>
);
const { getByText } = render(<Hello />);
const getByTextWithMarkup = (text: string) => {
getByText((content, node) => {
const hasText = (node: HTMLElement) => node.textContent === text
const childrenDontHaveText = Array.from(node.children).every(
child => !hasText(child as HTMLElement)
)
return hasText(node) && childrenDontHaveText
})
}
getByTextWithMarkup('Hello world')
Here is a solid answer from the 4th of Five Things You (Probably) Didn't Know About Testing Library from Giorgio Polvara's Blog:
Queries accept functions too
You have probably seen an error like this one:
Unable to find an element with the text: Hello world.
This could be because the text is broken up by multiple elements.
In this case, you can provide a function for your text
matcher to make your matcher more flexible.
Usually, it happens because your HTML looks like this:
<div>Hello <span>world</span></div>
The solution is contained inside the error message: "[...] you can provide a function for your text matcher [...]".
What's that all about? It turns out matchers accept strings, regular expressions or functions.
The function gets called for each node you're rendering. It receives two arguments: the node's content and the node itself. All you have to do is to return true or false depending on if the node is the one you want.
An example will clarify it:
import { render } from "#testing-library/react";
import "jest-dom/extend-expect";
test("pass functions to matchers", () => {
const Hello = () => (
<div>
Hello <span>world</span>
</div>
);
const { getByText } = render(<Hello />);
// These won't match
// getByText("Hello world");
// getByText(/Hello world/);
getByText((content, node) => {
const hasText = node => node.textContent === "Hello world";
const nodeHasText = hasText(node);
const childrenDontHaveText = Array.from(node.children).every(
child => !hasText(child)
);
return nodeHasText && childrenDontHaveText;
});
});
We're ignoring the content argument because in this case, it will either be "Hello", "world" or an empty string.
What we are checking instead is that the current node has the right textContent. hasText is a little helper function to do that. I declared it to keep things clean.
That's not all though. Our div is not the only node with the text we're looking for. For example, body in this case has the same text. To avoid returning more nodes than needed we are making sure that none of the children has the same text as its parent. In this way we're making sure that the node we're returning is the smallest—in other words the one closes to the bottom of our DOM tree.
Read the rest of Five Things You (Probably) Didn't Know About Testing Library
If you are using testing-library/jest-dom in your project. You can also use toHaveTextContent.
expect(getByTestId('foo')).toHaveTextContent('Name: Bob (special guest)')
if you need a partial match, you can also use regex search patterns
expect(getByTestId('foo')).toHaveTextContent(/Name: Bob/)
Here's a link to the package
For substring matching, you can pass { exact: false }:
https://testing-library.com/docs/dom-testing-library/api-queries#textmatch
const el = getByText('Name:', { exact: false })
expect(el.textContent).toEqual('Name: Bob (special guest)');
The existing answers are outdated. The new *ByRole query supports this:
getByRole('button', {name: 'Bob (special guest)'})
Update
The solution below works but for some cases, it might return more than one result. This is the correct implementation:
getByText((_, node) => {
const hasText = node => node.textContent === "Name: Bob (special guest)";
const nodeHasText = hasText(node);
const childrenDontHaveText = Array.from(node.children).every(
child => !hasText(child)
);
return nodeHasText && childrenDontHaveText;
});
You can pass a method to getbyText:
getByText((_, node) => node.textContent === 'Name: Bob (special guest)')
You could put the code into a helper function so you don't have to type it all the time:
const { getByText } = render(<App />)
const getByTextWithMarkup = (text) =>
getByText((_, node) => node.textContent === text)
To avoid matching multiple elements, for some use cases simply only returning elements that actually have text content themselves, filters out unwanted parents just fine:
expect(
// - content: text content of current element, without text of its children
// - element.textContent: content of current element plus its children
screen.getByText((content, element) => {
return content !== '' && element.textContent === 'Name: Bob (special guest)';
})
).toBeInTheDocument();
The above requires some content for the element one is testing, so works for:
<div>
<p>Name: <strong>Bob</strong> <em>(special guest)</em></p>
</div>
...but not if <p> has no text content of its own:
<div>
<p><em>Name: </em><strong>Bob</strong><em> (special guest)</em></p>
</div>
So, for a generic solution the other answers are surely better.
The other answers ended up in type errors or non-functional code at all. This worked for me.
Note: I'm using screen.* here
import React from 'react';
import { screen } from '#testing-library/react';
/**
* Preparation: generic function for markup
* matching which allows a customized
* /query/ function.
**/
namespace Helper {
type Query = (f: MatcherFunction) => HTMLElement
export const byTextWithMarkup = (query: Query, textWithMarkup: string) => {
return query((_: string, node: Element | null) => {
const hasText = (node: Element | null) => !!(node?.textContent === textWithMarkup);
const childrenDontHaveText = node ? Array.from(node.children).every(
child => !hasText(child as Element)
) : false;
return hasText(node) && childrenDontHaveText
})}
}
/**
* Functions you use in your test code.
**/
export class Jest {
static getByTextWithMarkup = (textWithMarkup: string) => Helper.byTextWithMarkup(screen.getByText, textWithMarkup);
static queryByTextWith = (textWithMarkup: string) => Helper.byTextWithMarkup(screen.queryByText, textWithMarkup);
}
Usage:
Jest.getByTextWithMarkup("hello world");
Jest.queryByTextWithMarkup("hello world");
Now you can use the 'toHaveTextContent' method for matching text with substrings or markup
for example
const { container } = render(
<Card name="Perro Loko" age="22" />,
);
expect(container).toHaveTextContent('Name: Perro Loko Age: 22');
getByText('Hello World'); // full string match
getByText('llo Worl', { exact: false }); // substring match
getByText('hello world', { exact: false }); // ignore case-sensitivity
source: https://testing-library.com/docs/react-testing-library/cheatsheet/#queries

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