I have the following problem,
The object passed as the value prop to the Context provider (at line 20) changes every render. To fix this consider wrapping it in a useMemo hook.
I don't know hot to use useMemo in this case. So how do I fix it?
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Context from './Context';
function Provider({ children }) {
const [data, setData] = useState([]);
useEffect(() => {
const getDataAPI = async () => {
const response = await fetch('https://swapi-trybe.herokuapp.com/api/planets/');
const dataAPI = await response.json();
const alteredData = dataAPI.results.map(({ residents, ...params }) => params);
const result = [...alteredData];
setData(result.sort((a, b) => a.name.localeCompare(b.name)));
};
getDataAPI();
}, []);
return (
<Context.Provider value={ { data } }>
{ data[0] && children }
</Context.Provider>
);
}
Provider.propTypes = {
children: PropTypes.node.isRequired,
};
export default Provider;
Look at what you are passing:
value={ { data } }
It is an object {data : data}. This is defined everytime.
You better pass value={data} and change your useContext hooks accordingly in children
I seem to have an infinite loop in my code but I can't seem to see where, I usually see this if I am setting the state of something that is a dependency of useEffect but here the two variables (values / items) are completely seperate.
App.js
import React from 'react';
import './style.css';
import MyComponent from './MyComponent.js';
export default function App() {
return (
<div>
<MyComponent />
</div>
);
}
MyComponent.js
import React, { useEffect, useState } from 'react';
const MyComponent = ({ values = [] }) => {
console.log('MyComponent Reloaded');
const [items, setItems] = useState();
useEffect(() => {
const loadItems = () => {
//setItems([]); //why does this cause infinite render loop?
};
loadItems();
}, [values]);
return <></>;
};
export default MyComponent;
Why does this cause a render loop?
I have an example build Here (Uncomment the commented line to begin render loop)
You need to introduce the default values of the props the right way:
import React, { useEffect, useState } from 'react';
const MyComponent = ({ values }) => {
console.log('MyComponent Reloaded');
const [items, setItems] = useState();
useEffect(() => {
console.log(values)
const loadItems = () => {
setItems([]);
};
loadItems();
}, [values]);
return <></>;
};
MyComponent.defaultProps = {
values: []
}
export default MyComponent;
There are two possible bugs in the code:
According to your code, values variable is created every time, when checked with the previous values variable it is not same. So it causes infinite loop.
To fix this use default props.
const MyComponent = ({ values }) => {
...
MyComponent.defaultProps = {
values: []
}
As in your question,this line causes you the infinite loop
//setItems([]);
To overcome this
Add items dependency along with values, so that both values are watched before re-rendering.
useEffect(() => {
console.log(values)
const loadItems = () => {
setItems([]);
};
loadItems();
}, [values,items]);
I have this component in my react js application:
import React, { useState } from "react";
import Select, { components, DropdownIndicatorProps } from "react-select";
import { ColourOption, colourOptions } from "./docs/data";
const Component = () => {
const [state, setState] = useState();
console.log(state);
const DropdownIndicator = (props) => {
const { menuIsOpen } = props.selectProps;
setState(menuIsOpen);
return (
<components.DropdownIndicator {...props}>12</components.DropdownIndicator>
);
};
return (
<Select
closeMenuOnSelect={false}
components={{ DropdownIndicator }}
defaultValue={[colourOptions[4], colourOptions[5]]}
isMulti
options={colourOptions}
/>
);
};
export default Component;
In the DropDownIndicator component i set the state:
const {
menuIsOpen
} = props.selectProps;
setState(menuIsOpen);
Setting the state in that place i get the next warning: Warning: Cannot update a component (Component) while rendering a different component (DropdownIndicator). To locate the bad setState() call inside DropdownIndicator . Question: How can i fix this warning and to make it disappear? demo: https://codesandbox.io/s/codesandboxer-example-forked-97sx0?file=/example.tsx:0-724
You should call setState inside useEffect
const DropdownIndicator = (props) => {
const { menuIsOpen } = props.selectProps;
useEffect(() => {
setState(menuIsOpen);
});
return (
<components.DropdownIndicator {...props}>12</components.DropdownIndicator>
);
};
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. Read more about useEffect
Incase if your setState is depend of menuIsOpen. Pass to useEffect as dependency.
const DropdownIndicator = (props) => {
const { menuIsOpen } = props.selectProps;
useEffect(() => {
setState(menuIsOpen);
},[menuIsOpen]);
return (
<components.DropdownIndicator {...props}>12</components.DropdownIndicator>
);
};
Complete solution on CodeSandbox
Just mark the useState with default value as false
const [state, setState] = useState(false);
looks like when you are doing setState(menuIsOpen); for the first time, its value is undefined and DropdownIndicator is not yet finished with first rendering,
[Hypothesis] There must be some code with React.Component as per the stack trace, based on undefined/null check. But when given initialState, the error is not occurring.
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?
Currently Im using functional component with react hooks. But I'm unable to test the useState hook completely. Consider a scenario like, in useEffect hook I'm doing an API call and setting value in the useState. For jest/enzyme I have mocked data to test but I'm unable to set initial state value for useState in jest.
const [state, setState] = useState([]);
I want to set initial state as array of object in jest. I could not find any setState function as similar like class component.
You can mock React.useState to return a different initial state in your tests:
// Cache original functionality
const realUseState = React.useState
// Stub the initial state
const stubInitialState = ['stub data']
// Mock useState before rendering your component
jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => realUseState(stubInitialState))
Reference: https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga
First, you cannot use destructuring in your component. For example, you cannot use:
import React, { useState } from 'react';
const [myState, setMyState] = useState();
Instead, you have to use:
import React from 'react'
const [myState, setMyState] = React.useState();
Then in your test.js file:
test('useState mock', () => {
const myInitialState = 'My Initial State'
React.useState = jest.fn().mockReturnValue([myInitialState, {}])
const wrapper = shallow(<MyComponent />)
// initial state is set and you can now test your component
}
If you use useState hook multiple times in your component:
// in MyComponent.js
import React from 'react'
const [myFirstState, setMyFirstState] = React.useState();
const [mySecondState, setMySecondState] = React.useState();
// in MyComponent.test.js
test('useState mock', () => {
const initialStateForFirstUseStateCall = 'My First Initial State'
const initialStateForSecondUseStateCall = 'My Second Initial State'
React.useState = jest.fn()
.mockReturnValueOnce([initialStateForFirstUseStateCall, {}])
.mockReturnValueOnce([initialStateForSecondUseStateCall, {}])
const wrapper = shallow(<MyComponent />)
// initial states are set and you can now test your component
}
// actually testing of many `useEffect` calls sequentially as shown
// above makes your test fragile. I would recommend to use
// `useReducer` instead.
If I recall correctly, you should try to avoid mocking out the built-in hooks like useState and useEffect. If it is difficult to trigger the state change using enzyme's invoke(), then that may be an indicator that your component would benefit from being broken up.
SOLUTION WITH DE-STRUCTURING
You don't need to use React.useState - you can still destructure in your component.
But you need to write your tests in accordance to the order in which your useState calls are made. For example, if you want to mock two useState calls, make sure they're the first two useState calls in your component.
In your component:
import React, { useState } from 'react';
const [firstOne, setFirstOne] = useState('');
const [secondOne, setSecondOne] = useState('');
In your test:
import React from 'react';
jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => [firstInitialState, () => null])
.mockImplementationOnce(() => [secondInitialState, () => null])
.mockImplementation((x) => [x, () => null]); // ensures that the rest are unaffected
Below function will return state
const setHookState = (newState) =>
jest.fn().mockImplementation(() => [
newState,
() => {},
]);
Add below to use react
const reactMock = require('react');
In your code, you must use React.useState() to this work, else it won't work
const [arrayValues, setArrayValues] = React.useState();`
const [isFetching, setFetching] = React.useState();
Then in your test add following, mock state values
reactMock.useState = setHookState({
arrayValues: [],
isFetching: false,
});
Inspiration: Goto
//Component
const MyComponent = ({ someColl, someId }) => {
const [myState, setMyState] = useState(null);
useEffect(() => {loop every time group is set
if (groupId) {
const runEffect = async () => {
const data = someColl.find(s => s.id = someId);
setMyState(data);
};
runEffect();
}
}, [someId, someColl]);
return (<div>{myState.name}</div>);
};
// Test
// Mock
const mockSetState = jest.fn();
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: initial => [initial, mockSetState]
}));
const coll = [{id: 1, name:'Test'}, {id: 2, name:'Test2'}];
it('renders correctly with groupId', () => {
const wrapper = shallow(
<MyComponent comeId={1} someColl={coll} />
);
setTimeout(() => {
expect(wrapper).toMatchSnapshot();
expect(mockSetState).toHaveBeenCalledWith({ id: 1, name: 'Test' });
}, 100);
});
I have spent a lot of time but found good solution for testing multiple useState in my app.
export const setHookTestState = (newState: any) => {
const setStateMockFn = () => {};
return Object.keys(newState).reduce((acc, val) => {
acc = acc?.mockImplementationOnce(() => [newState[val], setStateMockFn]);
return acc;
}, jest.fn());
};
where newState is object with state fields in my component;
for example:
React.useState = setHookTestState({
dataFilter: { startDate: '', endDate: '', today: true },
usersStatisticData: [],
});
I used for multiple useState() Jest mocks the following setup in the component file
const [isLoading, setLoading] = React.useState(false);
const [isError, setError] = React.useState(false);
Please note the useState mock will just work with React.useState() derivation.
..and in the test.js
describe('User interactions at error state changes', () => {
const setStateMock = jest.fn();
beforeEach(() => {
const useStateMock = (useState) => [useState, setStateMock];
React.useState.mockImplementation(useStateMock)
jest.spyOn(React, 'useState')
.mockImplementationOnce(() => [false, () => null]) // this is first useState in the component
.mockImplementationOnce(() => [true, () => null]) // this is second useState in the component
});
it('Verify on list the state error is visible', async () => {
render(<TodoList />);
....
NOT CHANGING TO React.useState
This approach worked for me:
//import useState with alias just to know is a mock
import React, { useState as useStateMock } from 'react'
//preseve react as it actually is but useState
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}))
describe('SearchBar', () => {
const realUseState: any = useStateMock //create a ref copy (just for TS so it prevents errors)
const setState = jest.fn() //this is optional, you can place jest.fn directly
beforeEach(() => {
realUseState.mockImplementation((init) => [init, setState]) //important, let u change the value of useState hook
})
it('it should execute setGuestPickerFocused with true given that dates are entered', async () => {
jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => ['', () => null]) //place the values in the order of your useStates
.mockImplementationOnce(() => ['20220821', () => null]) //...
.mockImplementationOnce(() => ['20220827', () => null]) //...
jest.spyOn(uiState, 'setGuestPickerFocused').mockReturnValue('')
getRenderedComponent()
expect(uiState.setGuestPickerFocused).toHaveBeenCalledWith(true)
})
})
My component
const MyComp: React.FC<MyCompProps> = ({
a,
b,
c,
}) => {
const [searchQuery, setSearchQuery] = useState('') // my first value
const [startDate, setStartDate] = useState('') // my second value
const [endDate, setEndDate] = useState('') // my third value
useEffect(() => {
console.log(searchQuery, startDate, endDate) // just to verifiy
}, [])
Hope this helps!