Invalid hook call. ReacJs - reactjs

I have been troubleshooting this error for hours now.
I'm using the hooks on top of a function (getItems). i don't know what mistake i have done.
How should i clear this?
ERROR:
`
Error: Invalid hook call. Hooks can only be called inside of the body
of a function
component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
`
App.js
import React from "react";
const data = {
list: [
{
id: 0,
title: "A1",
list: [
{
id: 3,
title: "A2",
},
]
},
]
};
function getItems() {
const [menuStack, setStack] = React.useState([data.list]);
const pushState = (list) => {
list && setStack((stack) => [...stack, list]);
};
const popState = () => {
menuStack.length > 1 && setStack((stack) => stack.slice(0, -1));
};
const top = menuStack[menuStack.length - 1];
return (
<button onClick={popState}>BACK</button>
);
}
export default class PopUp extends React.Component {
render() {
return (
<div>
{getItems()}
</div>
);
}
}
Index.js
import React from "react";
export default class Home extends React.Component {
render(){
return (
<App />
);
}
}

You are calling function getItems inside your PopUp component and expecting to print the output. React treat that function as a normal function and hence throws that error.
You need to tell react that this is a component function by doing
<div>
<getItems />
</div>

The issue is with class getItems, You can use that as a Component. <GetItems />
import React from "react";
const data = {
list: [
{
id: 0,
title: "A1",
list: [
{
id: 3,
title: "A2",
},
],
},
],
};
function GetItems() {
const [menuStack, setStack] = React.useState([data.list]);
const pushState = (list) => {
list && setStack((stack) => [...stack, list]);
};
const popState = () => {
menuStack.length > 1 && setStack((stack) => stack.slice(0, -1));
};
const top = menuStack[menuStack.length - 1];
return <button onClick={popState}>BACK</button>;
}
export default class PopUp extends React.Component {
render() {
return (
<div>
<GetItems />
</div>
);
}
}

In react, hooks can be written in functional components. But you called getItems function as a general function, not a functional component.
You should let react treat as a component like following:
<getItems />

Related

Why my render method is react called twice

I am quite new to react and redux. This is a little confusing problem for me. Can someone please explain this why my searchTerm state value printed twice (render method is getting called twice) after every input change. I read react and learnt that on every state change, render is called but in this called render is called twice? Am I getting this wrong?
App.js
import React from 'react';
import Todos from './components/Todos';
import Header from './components/Header';
class App extends React.Component {
state = {
searchTerm : '',
todos: [{
id: 1,
completed: true,
text: "I am number one"
},
{
id: 2,
completed: false,
text: "I am number two"
},
{
id: 3,
completed: false,
text: "I am number three"
}]
}
markComplete = (id) => {
this.setState({
todos: this.state.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
}
deleteTo = (id) => {
this.setState({
todos: [...this.state.todos.filter(todo => todo.id!==id)]
});
}
search = (evt) => {
const value = evt.target.value;
this.setState({
searchTerm: value
});
}
render() {
return (
<div>
<Header />
{ console.log(this.state.searchTerm) }
<input type="text" onChange = {this.search} />
<Todos todos = {this.state.todos} markComplete = {this.markComplete} deleteTo = {this.deleteTo}/>
</div>
);
}
}
export default App;
Todos.js
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
return this.props.todos.map((todo) =>
<TodoItem key={todo.id} todo = {todo} markComplete = {this.props.markComplete} deleteTo={this.props.deleteTo}/>)
}
}
export default Todos;
TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
getStyle = () => {
return { textDecoration: this.props.todo.completed ? "line-through": "none" };
};
getButtonStyle = () => {
return {
backgroundColor: 'red',
border: 'none',
cursor: 'pointer',
float: 'right',
padding: '5px 10px',
borderRadius: '50%'
};
}
render() {
const {id, text} = this.props.todo;
return (
<div style={this.getStyle()}>
<p>
<input type="checkbox" onChange= { () => this.props.markComplete(id) }/> {' '}
{text}
<button style={this.getButtonStyle()} onClick = { () => this.props.deleteTo(id)}> x </button>
</p>
</div>
);
}
}
export default TodoItem;
it's probably because of React StrictMode in your index.js file (if you use create-react-app).
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
strict mode checks for potential problems and caused to run some lifecycle methods twice (like constructor, render, componentShouldUpdate, etc).
Strict mode checks are run in development mode only; they do not impact the production build.
you can read more about it on strict-mode
The render function can be called almost any number of times before the commit phase occurs and updates are flushed to the DOM. The render function should also be a pure function, meaning there are no side-effects, like console logging. Instead use the componentDidUpdate lifecycle function to log when state or props update. Perhaps this diagram would help.
A simple solution to avoid the side effects is to not assign Dynamic values in the Render(), just keep it in the state, and call it before the Render() as example on Click() method.

Declare setState via function

Unable to declare setState via the function. And I can’t understand what the mistake is.
import React, { Component } from 'react';
export default class App extends Component {
state = {
text: ' ',
};
render() {
const text = () => {
this.setState({ text: 'Good afternoon' });
};
return(
)
}
}
Functions and setState should be outside the render lifecycle.
More Info: https://reactjs.org/docs/state-and-lifecycle.html
SetState Guide: https://blog.logrocket.com/an-imperative-guide-to-setstate-in-react-b837ceaf8304/
import React, { Component } from 'react';
export default class App extends Component {
state = {
text: " "
}
const text = () => {
this.setState({text: "Good afternoon"})
}
render() {
return(
)
}
}
Please bring the function text outside the render function.
export default class App extends Component {
state = {
text: ' ',
};
//function in here
text = () => {
this.setState({ text: 'Good afternoon' });
}
render() {
return ()
}
}
Anyway, when I pass the text of the function beyond the rendering limits, setState does not work.

React component does not re-render if there is only one state update

I have 2 components -> a) VideoJSPlayer and b) VideosPage.
As the name suggests, VideoJsPlayer has the video player part (using videojs) and VideosPage shows videos using VideoJsPlayer component.
The code for each of the components is:
a) VideoJsPlayer
import React, { useEffect } from "react";
import videojs from "video.js";
const VideoJsPlayer = props => {
let player = null;
let videoNode = null;
// Effect for first render only.
useEffect(() => {
player = videojs(videoNode, props, function onPlayerReady() {
console.log("onPlayerReady");
});
return function cleanup() {
if (player) {
player.dispose();
}
};
}, []);
return (
<div>
<div data-vjs-player>
<video ref={node => (videoNode = node)} className="video-js"></video>
</div>
</div>
);
};
export default VideoJsPlayer;
b) VideosPage
import React, { useState, useEffect } from "react";
import videojs from "video.js";
import VideoJsPlayer from "../../components/VideoJsPlayer";
const VideosPage = props => {
const [videoJsOptions, setVideoJsOptions] = useState({});
// Effect for first render only.
useEffect(() => {
let videoJsOptionsTest = {
autoplay: false,
controls: true,
sources: [
{
src: "/my-video.mp4",
type: "video/mp4"
}
]
};
console.log("test");
setVideoJsOptions(videoJsOptionsTest);
// Cleanup on unmount.
return function cleanup() {};
}, []);
return (
<React.Fragment>
<VideoJsPlayer {...videoJsOptions} />
</React.Fragment>
);
};
export default VideosPage;
When I check the URL which renders the VideosPage component, I don't see any video. I'm assuming the state (videoJsOptions) is not yet updated in VideosPage and hence I don't see the video. I can see the console log - "test" though.
When I change the code of VideosPage to:
import React, { useState, useEffect } from "react";
import videojs from "video.js";
import VideoJsPlayer from "../../components/VideoJsPlayer";
const VideosPage = props => {
const [videoJsOptions, setVideoJsOptions] = useState({
autoplay: false,
controls: true,
sources: [
{
src: "/my-video.mp4",
type: "video/mp4"
}
]
});
// Effect for first render only.
useEffect(() => {
/*
let videoJsOptionsTest = {
autoplay: false,
controls: true,
sources: [
{
src: "/my-video.mp4",
type: "video/mp4"
}
]
};
console.log("test");
setVideoJsOptions(videoJsOptionsTest);
*/
// Cleanup on unmount.
return function cleanup() {};
}, []);
return (
<React.Fragment>
<VideoJsPlayer {...videoJsOptions} />
</React.Fragment>
);
};
export default VideosPage;
Now when I check the URL which renders the VideosPage component, I see the video.
My assumption is - In the first case, the state update is queued and never happens. Because if it did happen, it should have re-rendered the component and I should have seen my video.
I have also tried updating useEffect in VideoJSPlayer to run every time props changes, but it didn't help.
Any insights on this?
Thank you for your time.
Here is a sandbox : codesandbox
You have some mistakes:
You can't use videoNode like that because Video.js uses Ref and since you are using React hooks you need to use useRef
import React, { useRef, useEffect } from "react"
const VideoJsPlayer = props => {
const videoRef = useRef(null)
useEffect(() => {
//...
videojs(
videoRef.current,
props,
function onPlayerReady() {
console.log("onPlayerReady")
}
)
//...
}, [])
return (
<video ref={videoRef} className="video-js" />
)
}
You don't need to use useEffect again in your VideosPage component since its already been used inside your VideoJsPlayer component. Your initial options need to set directly in the state or outside the function like so:
const initialOptions = {
// ...
}
const VideosPage = () => {
return <VideoJsPlayer {...initialOptions} />
}
Here is a working codeSandbox example based on your code.

Create redux reducer to change state nested object value

I am new to React-Redux and i am struggling with a proof-of-concept project (list of movies) in order to learn the basics of redux. I am quite good at React, but Redux is another deal...
So, in my repo and specifically in Smart Component MoviesList.jsx (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/MoviesList.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) i am wondering how to dispatch the correct action to change the rating (increment, decrement) in each SingleMovieItem (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/SingleMovieItem.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) and what would be the correct reducer to achieve that. Could anyone help me with this?
In other words, to show a real example of what i mean...
Lets say that there is a main App and we make use of the
<Provider store={store}>
<App />
</Provider>
And then the App contains the following:
import React, { Component } from 'react';
import MoviesList from './MoviesList'
class App extends Component {
render() {
return (
<div>
<MoviesList />
</div>
)
}
}
export default App;
And then, the MovieList.jsd contains the following:
import React, { Component } from 'react';
import { addRating } from './actions';
import SingleMovieItem from './SingleMovieItem';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
allMovies: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(addRating(id))
};
};
class MovieList extends Component {
constructor(props) {
super(props);
}
handleIncrement = id => {
}
render() {
return (
<div>
<ul className="moviesList">
{this.props.allMovies.movies.map(movie => {
return (
<SingleMovieItem
key={movie.id}
movie={movie}
onIncrement={this.handleIncrement(movie.id)} />
);
})}
</ul>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MovieList);
What would be the reducer to increment rating of each singlemovie
if initialState is
const initialState = {
movies: [
{
id: "1",
title: "Harry Potter 1",
url: "harry-1.jpg",
rating: "2"
},
{
id: "2",
title: "Harry Potter 2",
url: "harry-2.jpg",
rating: "3"
}
]
};
Thanks in advance.

Can a React portal be used in a Stateless Functional Component (SFC)?

I have used ReactDOM.createPortal inside the render method of a stateful component like so:
class MyComponent extends Component {
...
render() {
return (
<Wrapper>
{ReactDOM.createPortal(<FOO />, 'dom-location')}
</Wrapper>
)
}
}
... but can it also be used by a stateless (functional) component?
Will chime in with an option where you dont want to manually update your index.html and add extra markup, this snippet will dynamically create a div for you, then insert the children.
export const Portal = ({ children, className = 'root-portal', el = 'div' }) => {
const [container] = React.useState(() => {
// This will be executed only on the initial render
// https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
return document.createElement(el);
});
React.useEffect(() => {
container.classList.add(className)
document.body.appendChild(container)
return () => {
document.body.removeChild(container)
}
}, [])
return ReactDOM.createPortal(children, container)
}
It can be done like this for a fixed component:
const MyComponent = () => ReactDOM.createPortal(<FOO/>, 'dom-location')
or, to make the function more flexible, by passing a component prop:
const MyComponent = ({ component }) => ReactDOM.createPortal(component, 'dom-location')
can it also be used by a stateless (functional) component
?
yes.
const Modal = (props) => {
const modalRoot = document.getElementById('myEle');
return ReactDOM.createPortal(props.children, modalRoot,);
}
Inside render :
render() {
const modal = this.state.showModal ? (
<Modal>
<Hello/>
</Modal>
) : null;
return (
<div>
<div id="myEle">
</div>
</div>
);
}
Working codesandbox#demo
TSX version based on #Samuel's answer (React 17, TS 4.1):
// portal.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
interface IProps {
className? : string
el? : string
children : React.ReactNode
}
/**
* React portal based on https://stackoverflow.com/a/59154364
* #param children Child elements
* #param className CSS classname
* #param el HTML element to create. default: div
*/
const Portal : React.FC<IProps> = ( { children, className, el = 'div' } : IProps ) => {
const [container] = React.useState(document.createElement(el))
if ( className )
container.classList.add(className)
React.useEffect(() => {
document.body.appendChild(container)
return () => {
document.body.removeChild(container)
}
}, [])
return ReactDOM.createPortal(children, container)
}
export default Portal
IMPORTANT useRef/useState to prevent bugs
It's important that you use useState or useRef to store the element you created via document.createElement because otherwise it gets recreated on every re-render
//This div with id of "overlay-portal" needs to be added to your index.html or for next.js _document.tsx
const modalRoot = document.getElementById("overlay-portal")!;
//we use useRef here to only initialize el once and not recreate it on every rerender, which would cause bugs
const el = useRef(document.createElement("div"));
useEffect(() => {
modalRoot.appendChild(el.current);
return () => {
modalRoot.removeChild(el.current);
};
}, []);
return ReactDOM.createPortal(
<div
onClick={onOutSideClick}
ref={overlayRef}
className={classes.overlay}
>
<div ref={imageRowRef} className={classes.fullScreenImageRow}>
{renderImages()}
</div>
<button onClick={onClose} className={classes.closeButton}>
<Image width={25} height={25} src="/app/close-white.svg" />
</button>
</div>,
el.current
);
Yes, according to docs the main requirements are:
The first argument (child) is any renderable React child, such as an element, string, or fragment. The second argument (container) is a DOM element.
In case of stateless component you can pass element via props and render it via portal.
Hope it will helps.
Portal with SSR (NextJS)
If you are trying to use any of the above with SSR (for example NextJS) you may run into difficulty.
The following should get you what you need. This methods allows for passing in an id/selector to use for the portal which can be helpful in some cases, otherwise it creates a default using __ROOT_PORTAL__.
If it can't find the selector then it will create and attach a div.
NOTE: you could also statically add a div and specify a known id in pages/_document.tsx (or .jsx) if again using NextJS. Pass in that id and it will attempt to find and use it.
import { PropsWithChildren, useEffect, useState, useRef } from 'react';
import { createPortal } from 'react-dom';
export interface IPortal {
selector?: string;
}
const Portal = (props: PropsWithChildren<IPortal>) => {
props = {
selector: '__ROOT_PORTAL__',
...props
};
const { selector, children } = props;
const ref = useRef<Element>()
const [mounted, setMounted] = useState(false);
const selectorPrefixed = '#' + selector.replace(/^#/, '');
useEffect(() => {
ref.current = document.querySelector(selectorPrefixed);
if (!ref.current) {
const div = document.createElement('div');
div.setAttribute('id', selector);
document.body.appendChild(div);
ref.current = div;
}
setMounted(true);
}, [selector]);
return mounted ? createPortal(children, ref.current) : null;
};
export default Portal;
Usage
The below is a quickie example of using the portal. It does NOT take into account position etc. Just something simple to show you usage. Sky is limit from there :)
import React, { useState, CSSProperties } from 'react';
import Portal from './path/to/portal'; // Path to above
const modalStyle: CSSProperties = {
padding: '3rem',
backgroundColor: '#eee',
margin: '0 auto',
width: 400
};
const Home = () => {
const [visible, setVisible] = useState(false);
return (
<>
<p>Hello World <a href="#" onClick={() => setVisible(true)}>Show Modal</a></p>
<Portal>
{visible ? <div style={modalStyle}>Hello Modal! <a href="#" onClick={() => setVisible(false)}>Close</a></div> : null}
</Portal>
</>
);
};
export default Home;
const X = ({ children }) => ReactDOM.createPortal(children, 'dom-location')
Sharing my solution:
// PortalWrapperModal.js
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
const PortalWrapperModal = ({
children,
onHide,
backdrop = 'static',
focus = true,
keyboard = false,
}) => {
const portalRef = useRef(null);
const handleClose = (e) => {
if (e) e.preventDefault();
if (portalRef.current) $(portalRef.current).modal('hide');
};
useEffect(() => {
if (portalRef.current) {
$(portalRef.current).modal({ backdrop, focus, keyboard });
$(portalRef.current).modal('show');
$(portalRef.current).on('hidden.bs.modal', onHide);
}
}, [onHide, backdrop, focus, keyboard]);
return ReactDOM.createPortal(
<>{children(portalRef, handleClose)}</>,
document.getElementById('modal-root')
);
};
export { PortalWrapperModal };

Resources