Rules of Hooks Where is the top level - javascript

I have the following component in React
import React from 'react'
import { Row, Col, Form, Input, Button, Card } from 'antd';
import { FormComponentProps } from 'antd/lib/form/Form';
import Particles from 'react-particles-js';
import { useTranslation } from "react-i18next";
import { connect } from 'react-redux';
import { RootState } from '../../services/store/rootReducer';
import { UsersActions } from '../../services/store';
interface LoginProps extends FormComponentProps {
rootState: RootState
}
class Login extends React.Component<LoginProps> {
state = { email: '', password: ''};
changeHandler = (e: any, name: any) => {
var value = e.target.value;
this.setState({[name]: value})
}
loginUser = () => {
try {
UsersActions.loginRequestAsync(this.state, (token: any) => {
console.log(token);
});
} catch(exception)
{
console.log(exception)
}
}
render() {
const { t } = useTranslation();
const { getFieldDecorator } = this.props.form;
return (
<div>
///blabla
</div>
)
}
}
const mapStateToProps = (state: RootState) => ({
rootState: state
});
const mapDispatchToProps = {}
const createdForm = Form.create()(Login);
export default connect(
mapStateToProps,
mapDispatchToProps
)(createdForm);
When I add the line
const { t } = useTranslation();
The app do not compile with
×
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
fix this problem.
Now, I tried to understand the rule, hooks must be called only on top level of a component in order for react to always load the component the same way. But where is my top level ?
I tried to put outside of render and as a property of the component, I still have the same loading error.

You broke the rules of Hooks, namely: No Hooks in classes.
That should really be the trick here. Try to rewrite it to something like the following:
function Login(props: LoginProps) {
const [data, setData] = useState({ email: '', password: '' });
const { t } = useTranslation();
const loginUser = () => { /* ... */ };
render() {
return <div>
{/*...*/ }
</div>
}
}
On the document pages, there is even a page only on Hook Errors/Warnings: Invalid Hook Call Warning
In Breaking the Rules of Hooks it states:
🔴 Do not call Hooks in class components.
🔴 Do not call in event handlers.
🔴 Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.

Hooks are used in functional components, here you have a class component that's why it's throwing an error here, error is saying it
Hooks can only be called inside of the body of a function component.
Hope it helps

Related

React hook Error: Invalid hook call. Hooks can only be called inside of the body of a function component

When I clicked add to cart button for a individual product then show me error like this:
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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
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.
I have tried like this:
here is my cartScreen.js code
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { addToCart } from "../action/cartAction";
const CartScreen = (props) => {
const productId = props.match.params.id;
const qty = props.location.search
? Number(props.location.search.split("=")[1])
: 1;
const disptach = useDispatch;
useEffect(() => {
if (productId) {
disptach(addToCart(productId, qty));
}
}, [disptach, productId, qty]);
return (
<div>
<h2>Cart Screen</h2>
<p>
Add to Cart : ProductId: {productId} Qty: {qty}
</p>
</div>
);
};
export default CartScreen;
But When I have comment out useEffect hook then does not show me any error:
useEffect(() => {
if (productId) {
disptach(addToCart(productId, qty));
}
}, [disptach, productId, qty]);
Any suggestion please.
You need to change this from:
const disptach = useDispatch;
to:
const disptach = useDispatch(); // useDispatch() is a hook
Let me know if it works, good Luck

Defining reset slectors for memoization across multiple component instances

I am looking to using the redux reselect lib to write my react-redux selectors. From the docs there is this section that describes how to write selectors that can be used by several component instances.
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { makeGetVisibleTodos } from '../selectors'
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos() // here
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
return mapStateToProps
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
makeMapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
I am wondering if the differences in how the makeGetVisibleTodos selector is called(as shown below), has implications on how memoization works. will memoization still work?, if not, why?
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { makeGetVisibleTodos } from '../selectors'
const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => {
return {
todos: makeGetVisibleTodos()(state, props) // and here
}
}
return mapStateToProps
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
makeMapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
That example will never memoize at all, because it's creating a new selector instance every time mapState runs.
The key to correctly using memoized selectors is:
Reuse the same selector instance multiple times
And keep passing it the same arguments
If you create a new selector instance, it has no cached values. If you have the same instance but pass it different values each time you call it, then the memoization never works.
For more details, see my post Using Reselect Selectors for Encapsulation and Performance.

Show loader from a helper function using react context api

I am pretty new to React Context API. What I am trying to do is set a loader when I perform an API call and stop the loader after an API call is done. But if I do these dispatch actions from helper function, I am getting an 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
fix this problem.
// ApiCalls.js
export const LoginService = (username, password) => {
//to show loader when api call starts
const [dispatch] = useContext(LoaderContext);
dispatch({
type: "SHOWLOADER",
payload: true
});
}
// Hello.js
export default function Hello(props) {
useEffect(() => {
LoginService();
}, []);
return(
<h2>Hello</h2>
)
}
Reproducible example.
You have two mistakes:
useContext(LoaderContext) returns a tuple (with your implementation), so you need the setter function:
// Not [dispatch]
const [,dispatch] = useContext(LoaderContext);
LoginService is actually a custom hook, and shouldn't be called inside other hook (See Rules of hooks).
import { LoaderContext } from './LoaderContext';
import {useContext, useEffect} from 'react';
// Use username,password
export const useLoginService = (username, password) => {
const [, dispatch] = useContext(LoaderContext);
useEffect(() => {
dispatch({
type: "SHOWLOADER",
payload: true,
});
}, []);
};
// Usage
export default function Hello(props) {
useLoginService();
return(
<h2>Hello</h2>
)
}
See fixed example.
You need to change your component this way:
export default function Hello(props) {
const [state, dispatch] = useContext(LoaderContext);
useEffect(() => {
dispatch({
type: "SHOWLOADER",
payload: true
});
}, []);
return(
<h2>Hello</h2>
)
}

React component is re-rendering infinitely

First, I am really new to react; so, apologies, for beginner questions.
I have a React app with Redux and Redux Saga.
One of the components looks like this:
import { TableContainer, TableHead, TableRow } from '#material-ui/core';
import Paper from '#material-ui/core/Paper';
import makeStyles from '#material-ui/core/styles/makeStyles';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { ProgramCategory } from '../model/program-category';
import { ProgramCategoryItemRow } from '../ProgramGategoryItemRow/ProgramCategoryItemRow';
import { ProgramCategoryActions } from '../store/program-category.actions';
import { ProgramCategorySelectors } from '../store/program-category.selectors';
const useStyles = makeStyles({
table: {
width: '100%',
},
tableHeadCell: {
fontWeight: 'bold',
},
});
export interface ProgramCategoriesTableProps {
isLoaded: boolean;
categories: ProgramCategory[];
fetchAllCategories: () => void;
}
export const PureProgramCategoriesTable: React.FC<ProgramCategoriesTableProps> = ({
isLoaded,
categories,
fetchAllCategories,
}) => {
useEffect(() => {
console.error('in useEffect');
fetchAllCategories();
});
const styles = useStyles();
return (
<TableContainer component={Paper}>
// the rest
<TableBody>
{categories.map(c => (
<ProgramCategoryItemRow category={c} />
))}
</TableBody>
</TableContainer>
);
};
const mapStateToProps = createSelector(
[ProgramCategorySelectors.isLoaded, ProgramCategorySelectors.getAll],
(isLoaded, categories) => ({ isLoaded, categories }),
);
const mapDispatchToProps = {
fetchAllCategories: ProgramCategoryActions.fetchAll.start,
};
export const ProgramCategoriesTable = connect(mapStateToProps, mapDispatchToProps)(PureProgramCategoriesTable);
The sagas that process ProgramCategoryActions.fetchAll.start is as follows:
import { call, put, takeLatest } from 'redux-saga/effects';
import { ProgramCategoryApi } from '../services/program-category.api';
import { ProgramCategoryActions } from './program-category.actions';
function* handleFetchAll() {
try {
const categories = yield call(ProgramCategoryApi.fetchAll);
yield put(ProgramCategoryActions.fetchAll.success(categories));
} catch (e) {
yield put(ProgramCategoryActions.fetchAll.failure(e));
}
}
export function* programCategorySagas() {
yield takeLatest(ProgramCategoryActions.fetchAll.start.type, handleFetchAll);
}
Everything make sense, but what happens my action code is executed over and over again. Digging into it a bit more, it appears that the effect hook is also executed over and over again.
If I understand it correctly, it happens because the data in state is changing, the component is getting re-rendered again. But, it leads to infinite loop.
What am I doing wrong? What is the correct way to setup this kind of component?
One of the options that I found is to change the saga to:
function* handleFetchAll() {
try {
const alreadyLoaded = select(ProgramCategorySelectors.isLoaded);
if (!alreadyLoaded) {
const categories = yield call(ProgramCategoryApi.fetchAll);
yield put(ProgramCategoryActions.fetchAll.success(categories));
}
} catch (e) {
yield put(ProgramCategoryActions.fetchAll.failure(e));
}
}
So, it only calls the api once; and it seem to work fine this way. But, is it the correct solution?
As suggested in the comments, I tried adding dependency to the effect:
useEffect(() => {
fetchAllCategories();
}, []);
Now, I am getting an error:
./src/program/ProgramCategoriesTable/ProgramCategoriesTable.tsx Line
37:6: React Hook useEffect has a missing dependency:
'fetchAllCategories'. Either include it or remove the dependency
array. If 'fetchAllCategories' changes too often, find the parent
component that defines it and wrap that definition in useCallback
react-hooks/exhaustive-deps
The issue is here,
useEffect(() => {
console.error('in useEffect');
fetchAllCategories();
});
From react docs: Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
https://reactjs.org/docs/hooks-effect.html
You have to pass and array of dependecies at the end.
useEffect(() => {
console.error('in useEffect');
fetchAllCategories();
}, []);
Hope this helps!

React: TypeError: _useContext is undefined

I'm trying my hand at TypeScript and React. I have a functional component (code below) that is supposed to consume a context with useContext, but it is showing me this weird error that I cannot find a solution to.
If I do not use TS, and go with JSX, it works just fine.
Edit: Screenshot>
Code:
AppProvider.tsx
import React, { useState, useEffect } from "react";
// Application's context (for general application-wide usage)
const AppContext: any = React.createContext(null);
// this will be used below in the componet we will export
export const AppContextProvider = AppContext.Provider;
export const AppProvider: React.FC = (props: any) => {
const [appName, setAppName] = useState("Blood Donation");
const [appUser, setAppUser]: any = useState(null);
const [appInfoBusy, setAppInfoBusy] = useState(false); // working to get or set data
useEffect(() => {
getAppInfo();
}, []);
const getAppInfo = () => {
setTimeout(() => {
setAppName("Test");
setAppUser({
name: "Admin",
email: "test#test.com",
role_id: 100
});
}, 3000);
};
return (
<AppContextProvider
value={{
appName: appName,
appInfoBusy: appInfoBusy,
appUser: appUser
}}
>
{props.children}
</AppContextProvider>
);
};
Consumer: Login.tsx
import React, { useState, useEffect, useContext } from "react";
import {
Button,
Card,
Elevation,
FormGroup,
InputGroup,
Drawer,
Classes,
H4,
Callout,
H5
} from "#blueprintjs/core";
//#ts-ignore
import ReCAPTCHA from "react-google-recaptcha";
import logo from "../../assets/images/logo.png";
import "../../scss/Login.scss";
import { RecaptchaKey } from "../../shared/Info";
import { AppContextProvider } from "../../shared/context/AppProvider";
const Login: React.FC = props => {
const [email, setEmail]: React.ComponentState = useState();
const [password, setPassword]: any = useState();
const [isOpen, setIsOpen]: any = useState();
const [resetEmail, setResetEmail]: any = useState();
const [emailSent, setEmailSent]: any = useState();
const [captchaOk, setCaptchaOk]: any = useState(false);
const [working, setWorking]: any = useState(false);
// context
const { appName, appUser, appInfoBusy } = useContext(AppContextProvider);
/**
* Handles lifecycle hooks
*/
useEffect(() => {
// when component is mounted
}, []);
/**
* Handles Captcha change
* #param value
*/
const recaptchaChange = (value: any) => {
setCaptchaOk(value ? true : false);
};
const handleRecoverySubmit = () => {
setWorking(true);
setTimeout(() => {
setEmailSent(true);
setWorking(false);
}, 3000);
};
return (
<div id="loginPage">
... removed for brevity ...
</div>
);
};
export default Login;
Any help is gratefully thanked. React and dependencies are all latest as of date.
I was using the context provider instead of the context itself inside useContext(), I should have used useContext(AppContext) instead.
Commentary removed because stackoverflow.
The error is _useContext not defined. The issue is different than what it is actually referring to.
you created a context called as AppContext
and then you export this as
export const AppContextProvider = AppContext.Provider;
You have done correct till this stage.
The problem lies at consumer part i.e. login.tsx file.
you are importing a name file inside a curly braces which is not correct, because the context is exported as a name variable. You simply need to write
import AppContextProvider from "../../shared/context/AppProvider";
That's it, and when you are calling this context using useContext hooks, then the actual state that you are looking for get accessed and no issue will further persist.
Note: Don't use {} for importing named exports.
reference: When should I use curly braces for ES6 import?

Categories