Invalid Hook call in a functional element - javascript

I'm getting this annoying error, but don't know why.
I have 2 functional elements, just starting for test purposes.
Main:
import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom';
import MapaLogisplan from './Layout/MapaLogisplan'
//import DlgInfo from './Layout/DlgInfo'
//import MenuGeneral from './layout/MenuGeneral'
import ReadDlgInfoData from './data/DlgInfoData.js'
const LogisplanMain = () => {
const [TrucksData, setTrucksData] = useState([]);
const [TrucksColumnas, setTrucksColumnas] = useState([]);
//se llama cuando se renderiza. (el return la llama).
useEffect(() => {
const [ datos, columnas ] = ReadDlgInfoData({ Nombredatos: 'Trucks', Matricula: '' });
setTrucksData(datos);
setTrucksColumnas(columnas);
}, []);
return (
<div>
<MapaLogisplan />
</div>
);
}
ReactDOM.render(<LogisplanMain />, document.getElementById('root'));
And
import React, { useState } from 'react';
import soapRequest from 'easy-soap-request';
import XMLParser from 'react-xml-parser';
//PROPS:{Nombredatos(WS),TruckPlate===""->Todos.}
const ReadDlgInfoData = (props) => {
const [datos, setDatos] = useState([]);
const [columnas, setColumnas] = useState([]);
const url = `http://DAVID-PC:800`;
const sampleHeaders = {
'user-agent': 'sampleTest',
'Content-Type': 'text/xml',
'soapAction': 'Da igual',
'Access-Control-Request-Headers': 'Origin, X-Requested-With, Content-Type, Accept, *'
};
.....
I can't find why it complains about:
**Uncaught 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.
at Object.throwInvalidHookError (react-dom.development.js:14906)
at useState (react.development.js:1508)
at ReadDlgInfoData (DlgInfoData.js:23)
at app.tsx:15
at invokePassiveEffectCreate (react-dom.development.js:23487)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
at invokeGuardedCallback (react-dom.development.js:4056)
at flushPassiveEffectsImpl (react-dom.development.js:23574)
at unstable_runWithPriority (scheduler.development.js:646)
throwInvalidHookError # react-dom.development.js:14906
useState # react.development.js:1508
ReadDlgInfoData # DlgInfoData.js:23
(anónimo) # app.tsx:15
Any tip of light on how to get it corrected would be appreciated, as I'm starting to work with React I'm lost.
I've checked the 3 possible causes but didn't find anything.
React and React-Dom are 17.0 versions.
I call useState and useEffect at the top of their respective functional elements.
I think I'm not using React in more than one place. If I delete the second import in the
ReadDlgInfoData functional component nothing changes.
So I'm stuck at this point and I have tried all the things I know I can try.
Invalid Hook:

useEffect(() => {
const [ datos, columnas ] = ReadDlgInfoData({ Nombredatos: 'Trucks', Matricula: '' });
setTrucksData(datos);
setTrucksColumnas(columnas);
}, []);
Here is the problem, you destructure your values inside of useEffect, which is against hook rules.
const [ datos, columnas ] = ReadDlgInfoData({ Nombredatos: 'Trucks', Matricula: '' });
useEffect(() => {
setTrucksData(datos);
setTrucksColumnas(columnas);
}, []);
This should solve your problem.

Related

ReactMarkdown is not displaying in React app

I'm trying to make a component that displays some simple markdown, but just can't seem to get the markdown to display on the page. It's not even creating a component for it in the HTML. This component is displaying properly and the 'Test' is showing up but not the markdown. I tried reinstalling my node_modules and that didn't work, any tips?
import React, {useState, useEffect} from 'react'
import axios from 'axios';
import {API_DEV_URL, API_PROD_URL} from '../env';
import Markdown from 'react-markdown';
const ROOT = (process.env.REACT_APP_ENV === 'production')? API_PROD_URL : API_DEV_URL;
function Hello() {
const [test, setTest] = useState("");
console.log(process.env.REACT_APP_ENV);
useEffect(() => {
axios.get(ROOT+'/test')
.then(res => setTest(res.data))
.catch(err => console.log(err));
}, []);
const input = '# This is a header\n\nAnd this is a paragraph'
return (
<header>
<h1>Test</h1>
<Markdown source={input}/>
</header>
);
}
export default Hello;
The markdown that needs to be parsed should be provided as a children. react-markdown does have make use of source prop in there API.
Change from
<Markdown source={input}/>
to
<Markdown>{input}</Markdown>

React useEffect Hook not Triggering on First Render with [] Dependencies

I'm getting data via an Axios GET request from a local API and trying to save the data in a Context Object.
The GET request works properly when I run it outside the Context Provider function. But when I put it within a UseEffect function with no dependencies - ie. useEffect( () => /* do something*/, [] )the useEffect hook never fires.
Code here:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [])
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I never see 'this does not' in the console (double and triple checked). I'm trying to initialise the context to an empty value at first, make the GET request on first render, and then update the context value.
I'd really appreciate any help on what I'm doing wrong.
EDIT - Where Context Provider is being rendered
import React from 'react';
import AppNavbar from "./Components/AppNavbar";
import ShoppingList from "./Components/ShoppingList";
import ItemModal from "./Components/ItemModal";
//IMPORTED HERE (I've checked the import directory is correct)
import ItemsContextProvider from "./ItemsContext";
import { Container } from "reactstrap"
import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';
function App() {
return (
<div className="App">
<ItemsContextProvider> //RENDERED HERE
<AppNavbar />
<Container>
<ItemModal />
<ShoppingList /> //CONSUMED HERE
</Container>
</ItemsContextProvider>
</div>
);
}
export default App;
I have it being consumed in another file that has the following snippet:
const {items, dispatch} = useContext(ItemsContext);
console.log(items, dispatch);
I see console logs showing the empty array I initialised outside the useEffect function in the Context Provider and also a reference to the dispatch function.
I had the same problem for quite a while and stumbled upon this thred which did not offer a solution. In my case the data coming from my context did not update after logging in.
I solved it by triggering a rerender after route change by passing in the url as a dependency of the effect. Note that this will always trigger your effect when moving to another page which might or might not be appropriate for your usecase.
In next.js we get access to the pathname by using useRouter. Depending on the framework you use you can adjust your solution. It would look something like this:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
import { useRouter } from "next/router"; // Import the router
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
const router = useRouter(); // using the router
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [router.pathname]) // trigger useEffect on page change
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I hope this helps anyone in the future!
<ItemsContextProvider /> is not being rendered.
Make sure is being consumed and rendered by another jsx parent element.

Mocking dayjs with Jest

I'm new to test driven development, I'm trying to implement my simple testing but jest always finds my dayjs as not a function. Also i'm not using typescrcipt so my problem would be similar to this.
I can't seem to find how to setup my jest environment to recognise it as not default export.
Here's my simple test with failed mocking?:
import React from "react";
import { shallow } from "enzyme";
import MainDashboardApp from "../MainDashboard";
jest.mock("dayjs", () => {
return () => jest.requireActual("dayjs")("2020-01-01T00:00:00.000Z");
});
describe("Inititate main dashboard", () => {
it("renders without crashing", () => {
┊ shallow(<MainDashboardApp />);
});
});
Error:
FAIL src/app/main/apps/dashboards/main/__tests__/dashboard.test.js
● Test suite failed to run
TypeError: dayjs is not a function
9 | import { tableData } from "../helpers";
10 |
> 11 | const defaultStart = dayjs().startOf("month").format("YYYY-MM-DD");
| ^
12 | const defaultEnd = dayjs().endOf("month").format("YYYY-MM-DD");
13 |
14 | function IncidentList({
at Object.<anonymous> (src/app/main/apps/operations/incidents/IncidentList.js:11:22)
at Object.<anonymous> (src/app/main/apps/index.js:1:1)
at Object.<anonymous> (src/app/main/apps/dashboards/main/MainDashboard.js:22:1)
at Object.<anonymous> (src/app/main/apps/dashboards/main/__tests__/dashboard.test.js:3:1)
Failed component (which actually renders in chrome):
import React, { useEffect, useCallback, useState } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import * as incidents from "app/store/ducks/incident.duck";
import MaterialTable, { MTableToolbar } from "material-table";
import { useHistory } from "react-router-dom";
import * as dayjs from "dayjs";
import { DatePicker } from "#material-ui/pickers";
import { tableData } from "../helpers";
const defaultStart = dayjs().startOf("month").format("YYYY-MM-DD");
const defaultEnd = dayjs().endOf("month").format("YYYY-MM-DD");
function IncidentList({
incidentsList,
requestIncidents,
selectedLoc,
startDate,
endDate,
}) {
const history = useHistory();
const [selectedEndDate, handleEndDateChange] = useState(defaultEnd);
const [selectedStartDate, handleStartDateChange] = useState(defaultStart);
// Deps are ok because history constantly updates, and this handleclick does not need
// to be updated as well
const handleClick = useCallback((event, rowData) => {
history.push({
pathname: `/operation/incident-details/${rowData.id}`,
state: { detail: "testing" },
});
}, []);
useEffect(() => {
if (startDate && endDate) {
handleEndDateChange(endDate);
handleStartDateChange(startDate);
}
}, [startDate, endDate]);
MockDate works great for this, no intense mocking code required.
https://www.npmjs.com/package/mockdate
import MockDate from 'mockdate'
import dayjs from 'dayjs'
MockDate.set('2020-01-01')
console.log(dayjs.format())
// >>> 2019-12-31T18:00:00-06:00
Remember to clear the mock after your test with:
MockDate.reset();
Also for your situation you probably want to use DayJS UTC to avoid unexpected date math results
https://day.js.org/docs/en/plugin/utc

Redux - Hooks: Using React Hooks with Redux Hooks, create infinite loop or nothing at all

I know this question has been answered a bunch of times already. I just cannot find the answer that solves my problem, leading me to believe, that I am either stupid or my problem has not been had because it is even more stupid than me.
So aside from that, this is my problem:
I am trying to create a functional component that takes some information from a redux state in order to render the correct language labels into a login form.
Let's start with the code:
import React, { useState, useEffect } from "react";
import { Paper, TextField } from "#material-ui/core";
import { changeLanguage } from "../redux/actions";
import { useDispatch, useSelector } from "react-redux";
const Login = () => {
const dispatch = useDispatch();
const [language, setLanguage] = useState("de");
const [strings, setStrings] = useState({});
useEffect(() => {
console.log("I have been called", language, strings);
setLanguage(["de", "en"].includes(navigator.language.split("-")[0]) ? navigator.language.split("-")[0] : "de");
}, [language]);
dispatch(changeLanguage(language));
const str = useSelector(state => state.base.strings.login);
console.log(str);
setStrings(str); // <- THIS ONE IS CAUSING THE ERROR
return (
<div className={"App"}>
<Paper>
<TextField label={strings.username}/><br/>
<TextField label={strings.password} type={"password"}/>
</Paper>
</div>
);
};
export default Login;
This is what I used to get the app working at all. I realize that setting the strings on every render will cause an infinite loop. That much is clear. However when using this code:
import React, { useState, useEffect } from "react";
import { Paper, TextField } from "#material-ui/core";
import { changeLanguage } from "../redux/actions";
import { useDispatch, useSelector } from "react-redux";
const Login = () => {
const dispatch = useDispatch();
const [language, setLanguage] = useState("de");
const [strings, setStrings] = useState({});
useEffect(() => {
console.log("I have been called", language, strings);
setLanguage(["de", "en"].includes(navigator.language.split("-")[0]) ? navigator.language.split("-")[0] : "de");
dispatch(changeLanguage(language));
const str = useSelector(state => state.base.strings.login);
console.log(str);
setStrings(str);
}, [language]);
return (
<div className={"App"}>
<Paper>
<TextField label={strings.username}/><br/>
<TextField label={strings.password} type={"password"}/>
</Paper>
</div>
);
};
export default Login;
I get this error:
/src/App/pages/login.js
Line 17:15: React Hook "useSelector" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
And yes, I do understand what it is telling me but I do believe that useEffect is a React Hook or am I missing something here?
I am simply looking for a way to get this to work. It cannot be that hard because I can make it work with class components no problem.
If the question is stupid, please elaborate on why instead of just voting it down. This would be a lot more helpful in developing and understanding for the matter.
I have consulted the docs for two hours and tried a bunch of stuff. Nothing has worked.
Thank you for taking the time!
useEffect(() => {
console.log("I have been called", language, strings);
setLanguage(["de", "en"].includes(navigator.language.split("-")[0]) ? navigator.language.split("-")[0] : "de");
dispatch(changeLanguage(language));
const str = useSelector(state => state.base.strings.login);
console.log(str);
setStrings(str);
}, [language]);
You are using one hook inside another.That is not allowed.Only place you can place hooks is inside Functional Component and outside any method.For more info https://reactjs.org/docs/hooks-rules.html

Are `redux-loop` and `connected-react-router` compatible?

I have made a simple react app with the sample code from the following blogpost, which I leave only as a citation. I am otherwise new to the javascript ecosystem and am trying to fit together several unfamiliar tools (in order to learn them).
https://medium.com/#notrab/getting-started-with-create-react-app-redux-react-router-redux-thunk-d6a19259f71f
Relevantly, my store.js looks like this:
import { createStore, applyMiddleware, compose, } from 'redux';
import { connectRouter, routerMiddleware, } from 'connected-react-router';
import thunk from 'redux-thunk';
import { install, } from 'redux-loop';
import createHistory from 'history/createBrowserHistory';
import rootReducer from './modules';
export const history = createHistory();
const initialState = {};
const middleWare = [thunk, routerMiddleware(history),];
const composedEnhancers = compose(
applyMiddleware(...middleWare),
install()
);
const store = createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
);
export default store;
This seems to work fine, and the Link/Route triggers on my page work fine. Note that install from redux-loop is being called as part of an enhancer (?) and this is fine. I do not have any loop calls in my reducers, I just inserted the install command as an enhancer with the hope that I will be able to add some.
Here is my main reducer code:
import { combineReducers, } from 'redux';
import counter from './counter';
import todo from './todo';
export default combineReducers({
counter,
todo,
});
Again, this works great. However, if I insert loop anywhere in my reducers, it dies. According to the docs, this is because we need to use the combineReducers from redux-loop. Fine. If I replace the import at the top to import { combineReducers, } from 'redux-loop'; (not altering my reducers at all, there are no nonstandard returns yet) then I get some completely nonsensical errors in library code:
ConnectedRouter.js:58 Uncaught TypeError: Cannot read property 'pathname' of undefined
at ConnectedRouter.js:58
at Object.dispatch (redux.js:221)
at dispatch (install.js:66)
at middleware.js:25
at index.js:11
at Object.onLocationChanged (ConnectedRouter.js:154)
at handleLocationChange (ConnectedRouter.js:85)
at new ConnectedRouter (ConnectedRouter.js:94)
at constructClassInstance (react-dom.development.js:11769)
at updateClassComponent (react-dom.development.js:13491)
at beginWork (react-dom.development.js:14090)
at performUnitOfWork (react-dom.development.js:16416)
at workLoop (react-dom.development.js:16454)
at renderRoot (react-dom.development.js:16533)
at performWorkOnRoot (react-dom.development.js:17387)
at performWork (react-dom.development.js:17295)
at performSyncWork (react-dom.development.js:17267)
at requestWork (react-dom.development.js:17155)
at scheduleWork (react-dom.development.js:16949)
at scheduleRootUpdate (react-dom.development.js:17637)
at updateContainerAtExpirationTime (react-dom.development.js:17664)
at updateContainer (react-dom.development.js:17691)
at ReactRoot../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render (react-dom.de...
It goes on for many pages, but the issue seems to be in ConnectedRouter; I assume this is because combineReducers in redux-loop changes the response type of the main reducer into something that's not compatible with the connectRouter(history)(rootReducer) in the createStore call.
Is this the right issue? Is this fixable? Can these two libraries be used together?
There's an open issue that would address this, but until that is done it requires a hack. I called combineReducers with something like this (I am using immutable js. but if you're not it's simple to convert to that)
import { connectRouter } from 'connected-react-router/immutable';
import { Map } from 'immutable';
//....
const routerReducer = connectRouter(history)(() => fromJS({}));
return combineReducers(
{
foo: fooReducer,
blah: blahReducer,
router: (state, action) => {
const routerStateWrapper = Map({router: state});
const result = routerReducer(routerStateWrapper, action);
return result.get('router');
}
}
)
It's already supported in v5+.
Release note: https://github.com/supasate/connected-react-router/releases/tag/v5.0.0
PR: https://github.com/supasate/connected-react-router/pull/150

Categories

Resources