Element type is invalid: expected a string (for built-in components), the way I do the import seems good - reactjs

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.
I know this error is quite common and easy to fix but I still cannot find my error.
I got this element when I include the component ProcessModal
on my component
export const CreateProcessButton = ({ selectedIds }) => {
const [showProcessModal, setShowProcessModal] = useState(false);
// const refresh = useRefresh();
// const notify = useNotify();
// const unselectAll = useUnselectAll();
return (
<Button
label="Dupliquer la séléction sur ..."
onClick={() => setShowProcessModal(true)}
>
<ProcessModal open={showProcessModal} {...selectedIds}/>
{/*</ProcessModal>*/}
<SyncIcon />
</Button>
);
};
I import ProcessModal this way
import ProcessModal from './processModal';
from the file processModal.jsx located in the same directory.
The content of processModal.jsx :
import React, {useState} from "react";
import Modal from 'react-modal';
const ProcessModal = (selectedIds, open) => {
const customStyles = {
content : {
top : '50%',
left : '50%',
right : 'auto',
bottom : 'auto',
marginRight : '-50%',
transform : 'translate(-50%, -50%)'
}
};
var subtitle;
const [showProcessModal,setShowProcessModal] = useState(open);
function afterOpenModal() {
// references are now sync'd and can be accessed.
subtitle.style.color = '#f00';
}
function closeModal(){
setShowProcessModal(false);
}
return (
<Modal
isOpen={showProcessModal}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Example Modal"
>
<h2 ref={_subtitle => (subtitle = _subtitle)}>Hello</h2>
<button onClick={closeModal}>close</button>
<div>I am a modal</div>
<form>
<input />
<button>tab navigation</button>
<button>stays</button>
<button>inside</button>
<button>the modal</button>
</form>
{/*<SelectField choices={[*/}
{/* { id: 'M', name: 'Male' },*/}
{/* { id: 'F', name: 'Female' }*/}
{/*]}*/}
/>
</Modal>
);
};
export default ProcessModal;
Do you know why I have this error, or in which way I can find the problem since the error gives me no indication.

React components expect a single props object as a parameter. What you have done is a bit strange in how you define your PostModal component signutare as it is neither expecting a single porps object or destructing it to inside variables but instead expects two arguments.
Try this:
// here we destruct the props object in the variables you expect being inside
const ProcessModal = ({ selectedIds, open }) => { ... }
When using the component also try this:
<ProcessModal open={showProcessModal} selectedIds={selectedIds}/>

Related

When trying to return dynamic component, getting undefined error?

I want to switch between components depending on passed prop. If type === 'check' - render CheckList, otherwise render RadioList. Both of these components accept same props. I tried following example given [here][1]. But when I tried running the code, I got 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.
Check the render method of `List`.
My code in List.tsx is:
import CheckList from './Check/CheckList';
import RadioList from './Radio/RadioList';
import Props from './typings';
const list = {
check: CheckList,
radio: RadioList,
};
const List = ({type = 'radio', selected, options, callback}: Props) => {
const ListType = list[type];
return (
<ListType list={options} selected={selected} callback={callback} />
);
};
export default List;
When in return I replace ListType with either RadioList or CheckList - it works. I don't understand why it breaks when I use ListType. I checked all imports/exports and the fact that components work fine outside of List shows that they are not the problem.
I actually call for List inside RadioList component, so that it can return either radio list or check list for children:
import React from 'react';
import RadioButton from '../../../atoms/RadioButton/RadioButton';
import Props from './typings';
import {StyledSubcategory} from './styles';
import List from '../List';
const RadioList = ({list, selected, group, callback}: Props) => {
return (
<>
{list.map((option, key) => (
<>
<RadioButton
key={key}
checked={false}
label={option.label}
value={option.value}
callback={callback}
/>
{option.sublist && (
<StyledSubcategory $visible={true}>
<List
type={option.sublist.type}
selected={false}
options={option.sublist.options}
callback={callback}
/>
</StyledSubcategory>
)}
</>
))}
</>
);
};
export default RadioList;
My props for list are:
const list = {
type: 'radio',
options: [
{
label: 'all',
value: 'all',
},
{
label: 'painting',
value: 'painting',
sublist: {
type: 'radio',
options: [
{label: 'all', value: 'all'},
{label: 'acrylic', value: 'type-acrylic'},
{label: 'oil', value: 'type-oil'},
{label: 'watercolour', value: 'type-watercolour'},
],
},
},
],
};
UPDATE:
I found what the issue was. My list object with my components was declared outside of my List, however once I brought it inside it worked:
import CheckList from './Check/CheckList';
import RadioList from './Radio/RadioList';
import Props from './typings';
const List = ({type = 'radio', selected, options, callback}: Props) => {
const list = {
check: CheckList,
radio: RadioList,
};
const ListType = list[type];
return (
<ListType list={options} selected={selected} callback={callback} />
);
};
Can someone explain why it's the case?
export default List;
[1]: https://stackoverflow.com/a/40896168/3629015
The problem is with the type property of the List component. You are passing option.sublist.options value to it, which is most probably undefined, and it overrides the default value radio as well.
Please make sure the value is not undefined, or pass it like so;
<List
type={option?.sublist?.type ?? 'radio'}
selected={false}
options={option.sublist.options}
callback={callback}
/>

Observe (get sized) control (listen to events) over a nested component in the react and typescript application via the forwardRef function

I have a functional component called MyDivBlock
const MyDivBlock: FC<BoxProps> = ({ }) => {
{getting data...}
return (
<>
<div className='divBlock'>
{data.map((todo: { id: string; title: string }) =>
<div key={todo.id}>{todo.id} {todo.title} </div>)}
</div>
</>
);
};
I use it in such a way that MyDivBlock is nested as a child of
const App: NextPage = () => {
return (
<div>
<Box >
<MyDivBlock key="key0" areaText="DIV1" another="another"/>
</Box>
</div>
)
}
Note that MyDivBlock is nested in Box and MyDivBlock has no ref attribute. This is important because I need to write Box code with no additional requirements for my nested children. And anyone who will use my Box should not think about constraints and ref attributes.
Then I need to get the dimensions of MyDivBlock in the code of Box component, and later attach some event listeners to it, such as scrolling. These dimensions and listeners will be used in the Box component. I wanted to use Ref to control it. That is, the Box will later observe changes in the dimensions and events of MyDivBlock by creating a ref-reference to them
I know that this kind of parent-child relationship architecture is implemented through forwardRef
And here is the Box code:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
export interface BoxProps extends React.ComponentProps<any> {
children?: Element[];
className: string;
}
export const Box: React.FC<BoxProps> = ({ children, ...rest }: BoxProps): JSX.Element => {
const childRef = useRef<HTMLDivElement>();
const ChildWithForwardRef = forwardRef<HTMLDivElement>((props, _ref) => {
const methods = {
show() {
if (childRef.current) {
console.log("childRef.current is present...");
React.Children.forEach(children, function (item) {
console.log(item)})
console.log("offsetWidth = " + childRef.current.offsetWidth);
} else {
console.log("childRef.current is UNDEFINED");
}
},
};
useImperativeHandle(_ref, () => (methods));
return <div ref={childRef}> {children} </div>
});
ChildWithForwardRef.displayName = 'ChildWithForwardRef';
return (
<div
className={'BoxArea'}>
<button name="ChildComp" onClick={() => childRef.current.show()}>get Width</button>
<ChildWithForwardRef ref={childRef} />
</div>
);
}
export default Box;
The result of pressing the button:
childRef.current is present...
[...]
$$typeof: Symbol(react.element) key: "key0" props: {areaText: 'DIV1', another: 'another'}
[...] Object
offsetWidth = undefined
As you can see from the output, the component is visible through the created ref. I can even make several nested ones and get the same for all of them.
But the problem is that I don't have access to the offsetWidth and other properties.
The other challenge is how can I add the addEventListener?
Because it works in pure Javascript with their objects like Element, Document, Window or any other object that supports events, and I have ReactChildren objects.
Plus I'm using NextJS and TypeScript.
Didn't dive too deep into the problem, but this may be because you are passing the same childRef to both div inside ChildWithForwardRef and to ChildWithForwardRef itself. The latter overwrites the former, so you have the method .show from useImperativeHandle available but not offsetWidth. A quick fix is to rewrite ChildWithForwardRef to use its own ref:
const ChildWithForwardRef = forwardRef<HTMLDivElement>((props, _ref) => {
const ref = useRef<HTMLDivElement>()
const methods = {
show() {
if (ref.current) {
console.log("ref.current is present...");
React.Children.forEach(children, (item) => console.log(item))
console.log("offsetWidth = " + ref.current.offsetWidth);
} else {
console.log("ref.current is UNDEFINED");
}
},
};
useImperativeHandle(_ref, () => (methods));
// Here ref instead of childRef
return <div ref={ref}> {children} </div>
});
But really I don't quite get why you would need ChildWithForwardRef at all. The code is basically equivalent to this simpler version:
const Box: React.FC<BoxProps> = ({ children, ...rest }: BoxProps): JSX.Element => {
const childRef = useRef<HTMLDivElement>();
const showWidth = () => {
if(childRef.current) {
console.log("childRef.current is present...");
React.Children.forEach(children, item => console.log(item))
console.log("offsetWidth = " + childRef.current.offsetWidth);
} else {
console.log("childRef.current is UNDEFINED");
}
}
return (
<div className={'BoxArea'}>
<button name="ChildComp" onClick={showWidth}>get Width</button>
<div ref={childRef}>{children}</div>
</div>
);
}
You can't solve this completely with React. I solved it by wrapping the child component, making it take the form of the parent.

Dynamic atom keys in Recoil

I'm trying to make a dynamic form where the form input fields is rendered from data returned by an API.
Since atom needs to have a unique key, I tried wrapping it inside a function, but every time I update the field value or the component re-mounts (try changing tabs), I get a warning saying:
I made a small running example here https://codesandbox.io/s/zealous-night-e0h4jt?file=/src/App.tsx (same code as below):
import React, { useEffect, useState } from "react";
import { atom, RecoilRoot, useRecoilState } from "recoil";
import "./styles.css";
const textState = (key: string, defaultValue: string = "") =>
atom({
key,
default: defaultValue
});
const TextInput = ({ id, defaultValue }: any) => {
const [text, setText] = useRecoilState(textState(id, defaultValue));
const onChange = (event: any) => {
setText(event.target.value);
};
useEffect(() => {
return () => console.log("TextInput unmount");
}, []);
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
};
export default function App() {
const [tabIndex, setTabIndex] = useState(0);
// This would normally be a fetch request made by graphql or inside useEffect
const fields = [
{ id: "foo", type: "text", value: "bar" },
{ id: "hello", type: "text", value: "world" }
];
return (
<div className="App">
<RecoilRoot>
<form>
<button type="button" onClick={() => setTabIndex(0)}>
Tab 1
</button>
<button type="button" onClick={() => setTabIndex(1)}>
Tab 2
</button>
{tabIndex === 0 ? (
<div>
<h1>Fields</h1>
{fields.map((field) => {
if (field.type === "text") {
return (
<TextInput
key={field.id}
id={field.id}
defaultValue={field.value}
/>
);
}
})}
</div>
) : (
<div>
<h1>Tab 2</h1>Just checking if state is persisted when TextInput
is unmounted
</div>
)}
</form>
</RecoilRoot>
</div>
);
}
Is this even possible with recoil. I mean it seems to work but I can't ignore the warnings.
This answer shows how you can manually manage multiple instances of atoms using memoization.
However, if your defaultValue for each usage instance won't change, then Recoil already provides a utility which can take care of this creation and memoization for you: atomFamily. I'll quote some relevant info from the previous link (but read it all to understand fully):
... You could implement this yourself via a memoization pattern. But, Recoil provides this pattern for you with the atomFamily utility. An Atom Family represents a collection of atoms. When you call atomFamily it will return a function which provides the RecoilState atom based on the parameters you pass in.
The atomFamily essentially provides a map from the parameter to an atom. You only need to provide a single key for the atomFamily and it will generate a unique key for each underlying atom. These atom keys can be used for persistence, and so must be stable across application executions. The parameters may also be generated at different callsites and we want equivalent parameters to use the same underlying atom. Therefore, value-equality is used instead of reference-equality for atomFamily parameters. This imposes restrictions on the types which can be used for the parameter. atomFamily accepts primitive types, or arrays or objects which can contain arrays, objects, or primitive types.
Here's a working example showing how you can use your id and defaultValue (a unique combination of values as a tuple) as a parameter when using an instance of atomFamily state for each input:
TS Playground
body { font-family: sans-serif; }
input[type="text"] { font-size: 1rem; padding: 0.5rem; }
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/recoil#0.6.1/umd/recoil.min.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.7/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
// import ReactDOM from 'react-dom';
// import type {ReactElement} from 'react';
// import {atomFamily, RecoilRoot, useRecoilState} from 'recoil';
// This Stack Overflow snippet demo uses UMD modules instead of the above import statments
const {atomFamily, RecoilRoot, useRecoilState} = Recoil;
const textInputState = atomFamily<string, [id: string, defaultValue?: string]>({
key: 'textInput',
default: ([, defaultValue]) => defaultValue ?? '',
});
type TextInputProps = {
id: string;
defaultValue?: string;
};
function TextInput ({defaultValue = '', id}: TextInputProps): ReactElement {
const [value, setValue] = useRecoilState(textInputState([id, defaultValue]));
return (
<div>
<input
type="text"
onChange={ev => setValue(ev.target.value)}
placeholder={defaultValue}
{...{value}}
/>
</div>
);
}
function App (): ReactElement {
const fields = [
{ id: 'foo', type: 'text', value: 'bar' },
{ id: 'hello', type: 'text', value: 'world' },
];
return (
<RecoilRoot>
<h1>Custom defaults using atomFamily</h1>
{fields.map(({id, value: defaultValue}) => (
<TextInput key={id} {...{defaultValue, id}} />
))}
</RecoilRoot>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
I think the problem is from textState(id, defaultValue). Every time you trigger re-rendering for TextInput, that function will be called again to create a new atom with the same key.
To avoid that situation, you can create a global variable to track which atom added. For example
let atoms = {}
const textState = (key: string, defaultValue: string = "") => {
//if the current key is not added, should add a new atom to `atoms`
if(!atoms[key]) {
atoms[key] = atom({
key,
default: defaultValue
})
}
//reuse the existing atom which is added before with the same key
return atoms[key];
}

How can I fix this Unit Test?

I'm fairly new to unit testing my .tsx files and I am currently having trouble testing this (sorry if the format is off)
//this is Banner.tsx
import React, {useCallback} from "react";
type Properties = {
close: () => void;
text: string;
const Banner: React.FC<Properties> = ({close, text}) => {
const onClick = useCallback(() => {
close();},
[close, text]);
return (
<div className = "BannerBox">
<div className = "banner">
<span className = "popup"> onClick={onClick}[x]
</span>
{text}
</div>
</div>
);
};
export default Banner;
//this is App.tsx
import Banner from "./Components/Banner";
function App(): JSX.Element {
const [isOpen, setIsOpen]=useState(false);
const toggleBanner = () => {
SetIsOpen(!isOpen);
};
return (
<div>
<input type = "button"
value = "popup"
onClick={toggleBanner}/>
<p>hi</p>
{isOpen && <Banner text = {"hello"} close={() => isOpen(false)}/>}
</div>
export default App;
this is what i have so far
//Banner.test.tsx
test("Check that all type Properties are being used", () => {
render(<Banner />);
})
it gives this error -> "type {} is missing the following properties from type Banner: close and text"
"type {} is missing the following properties from type Banner: close and text"
Read this error message carefully.
Banner is a functional component. That means it's a function that that takes it's props as an object. And it's typed to receive two props, close and text. These props are required.
But you are providing no props in your test. Since the props argument is always an object, and you have no props, then the props argument is an empty object.
So now that error tells you that your function expects an object, but the one you provided is missing the close and text props.
You need to satisfy the required props of your component. Whether you are in a test or not, the contract of those types must must be fulfilled.
That means you want something like this:
//Banner.test.tsx
test("Check that all type Properties are being used", () => {
render(<Banner text="Hello, World!" close={() => null} />);
})
In additional there several syntax errors in your components. And your code will be much easier to understand if you use proper indenting to inform you of the structure of your code.

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