I have a react component. I want to set the state within this component that will be passed down to child components. I am getting a reference error to this and I am not sure why.
export const WidgetToolbar: React.FC<{}> = () => {
this.state = {
targetBox:null,
}
const isOpen = useBehavior(mainStore.isWidgetToolbarOpen);
const themeClass = useBehavior(mainStore.themeClass);
const userDashboards = useBehavior(dashboardStore.userDashboards);
const [filter, setFilter] = useState("");
const [sortOrder, setSortOrder] = useState<SortOrder>("asc");
const userWidgets = useMemo(() => {
let _userWidgets = values(userDashboards.widgets).filter((w) => w.widget.isVisible);
if (sortOrder === "asc") {
_userWidgets.sort((a, b) => a.widget.title.localeCompare(b.widget.title));
} else {
_userWidgets.sort((a, b) => b.widget.title.localeCompare(a.widget.title));
}
if (!isBlank(filter)) {
_userWidgets = _userWidgets.filter((row) => {
return row.widget.title.toLowerCase().includes(filter.toLocaleLowerCase());
});
}
return _userWidgets;
}, [userDashboards, sortOrder, filter]);
...
This is the error I am getting:
TypeError: Cannot set property 'state' of undefined
at WidgetToolbar (WidgetToolbar.tsx?ba4c:25)
at ProxyFacade (react-hot-loader.development.js?439b:757)
There's no this or this.state in a functional component. Use the useState hook, similar to what you're doing a few lines below.
export const WidgetToolbar: React.FC<{}> = () => {
const [targetBox, setTargetBox] = useState<null | whateverTheTypeIs>(null);
//...
}
Functional React Components can't have state. You'd have to use a class-based component in order to have state.
https://guide.freecodecamp.org/react-native/functional-vs-class-components/
You used the hook to "use state" in this function: const [filter, setFilter] = useState("");
You could do the same for targetBox, instead of trying to set a property on a non-existent 'this'
Related
I am trying to create a reusable country select functionality by utilizing React Hooks. The idea behind the scenes is to create a function which would handle the country change within a custom select box. With this in mind, I have attempted the following code, which results in an endless loop. Is there any alternative to this code, or any ideas on how to implement it?
const { useState } = require("react");
const useCountrySelect = (defaultCountires, defaultCountry) => {
const [countries, setCountries] = useState([]);
const filteredCountries = defaultCountires.filter(
(country) => country !== defaultCountry
);
setCountries(filteredCountries);
return [countries, setCountries];
};
export default useCountrySelect;
Hook usage in a component:
const [country] = useCountrySelect(["lv", "ee", "lt"], "lt");
console.log(country);
Now, Why is your code resulting in an endless loop?
it is because when u call useCountrySelect hook it will call setCountries(filteredCountries). now state is changed. react tries re-render the component. but in the rerendering process, setCountries(filteredCountries) will be called again. this will continue like a loop.
try this,
import {useState} from 'react'
const useCountrySelect = (defaultCountires, defaultCountry) => {
const [countries, setCountries] = useState(() => {
return defaultCountires.filter(
(country) => country !== defaultCountry
);
});
return [countries, setCountries];
};
export default useCountrySelect;
setCountries(filteredCountries); is unconditionally enqueueing state updates and triggering the render looping. Just set the state initially.
const { useState } = require("react");
const useCountrySelect = (defaultCountires = [], defaultCountry) => {
const [countries, setCountries] = useState(defaultCountires.filter(
(country) => country !== defaultCountry
));
return [countries, setCountries];
};
export default useCountrySelect;
Usage:
const [country] = useCountrySelect(["lv", "ee", "lt"], "lt");
console.log(country);
Output:
["lv", "ee"]
Don't understand why your state variable is an array. It seems you want the custom hook to return the selected country as well as a function to update it.
If my understanding is correct, here is a possible solution :
const { useState } = require("react");
function useCountrySelect(countries, defaultCountry) {
const [country, setCountry] = useState(defaultCountry);
// reset the country when input countries change
useEffect(() => setCountry(defaultCountry), [countries.join()]);
function setCountryWrapper(nextCountry) {
// prevent updates with invalid country
if (!countries.includes(nextCountry) {
return;
}
setCountry(country);
}
return [country, setCountryWrapper, countries];
};
export default useCountrySelect;
Usage
const [country, setCountry] = useCountrySelect(["lv", "ee", "lt"], "lt");
console.log(country);
const { useState, useEffect } = require("react");
function filter(defaultCountires, defaultCountry) {
const filteredCountries = defaultCountires.filter(
(country) => country !== defaultCountry
);
return filteredCountries;
}
const useCountrySelect = (defaultCountires, defaultCountry) => {
const [countries, setCountries] = useState(
filter(defaultCountires, defaultCountry) // this will set the initial state
);
// side-effects like this should go inside `useEffect`
useEffect(() => {
setCountries(filter(defaultCountires, defaultCountry));
}, [defaultCountires, defaultCountry]); // based on prop change, this effect will rerun again
// don't set any state directly in the root component/hook body! Else you'll be stuck in an endless loop
return [countries, setCountries];
};
export default useCountrySelect;
I'm new to Typescript and can't figure out how to type this situation.
I'm writing a custom hook and trying to create a callback ref.
My problem is that this function sets the ref's current, and returns nothing, but since I use it as a ref, typescript yells at me Property 'current' is missing in type '(node: any) => void' but required in type 'RefObject<HTMLDivElement>'.
Thank you in advance.
This is the code:
import React, {useCallback, useRef} from 'react'
const useCustom = (): [RefObject<HTMLDivElement>] => {
const ref = useRef<HTMLDivElement | null>(null)
const setRef = useCallback(node => {
....
ref.current = node
}, [])
return [setRef]
}
const SomeComp = () => {
const [ref] = useCustom()
return <div ref={ref}>Text</div>
}
The problem is you said the return value of useCustom would be RefObject<HTMLDivElement>, but returned (node: HTMLDivElement) => void.
Your custom hook should return 2 values: one for setting the ref value, the other for the ref itself. So it will look like useState hook:
const useCustom = (): [
RefObject<HTMLDivElement>,
(node: HTMLDivElement) => void
] => {
const ref = useRef<HTMLDivElement | null>(null);
const setRef = useCallback((node) => {
ref.current = node;
}, []);
return [ref, setRef];
};
I propose a different solution.
This is assuming the author is trying to use a "callback ref" to execute a side effect, when the ref changes. When using two return values the ref might still accidentally be set (by using the ref) without executing the callback (setRef), which I'm guessing is not the author's intention.
Typing things like this seems to work as expected (using author's example):
import React, {useCallback, useRef} from 'react'
const useCustom = (): [React.RefCallback<HTMLElement>] => {
const ref = useRef<HTMLElement | null>(null)
const setRef: React.RefCallback<HTMLElement> = useCallback(node => {
....
ref.current = node
}, [])
return [setRef]
}
const SomeComp = () => {
const [ref] = useCustom()
return <div ref={ref}>Text</div>
}
Note: I also changed HTMLDivElement to HTMLElement to make the hook more universal.
I export a JS object called Products to this file, just to replace a real API call initially while I am building/testing. I want to set the function's state to the object, but mapped. I have the component looking like this:
function App() {
const [rooms, setRooms] = useState([]);
const [days, setDays] = useState([]);
const roomsMapped = products.data.map(room => ({
id: room.id,
title: room.title
}))
useEffect(() => {
setRooms(roomsMapped);
})
return ( etc )
This returns the following error: Error: Maximum update depth exceeded.
I feel like I'm missing something really obvious here, but am pretty new to React and Hooks. How can I set this data before the component renders?
Just declare it as initial value of rooms
const Component = () =>{
const [rooms, setRooms] = useState(products.data.map(room => ({
id: room.id,
title: room.title
})))
}
You can also use lazy initial state to avoid reprocessing the initial value on each render
const Component = () =>{
const [rooms, setRooms] = useState(() => products.data.map(room => ({
id: room.id,
title: room.title
})))
}
Change useEffect to this
useEffect(() => {
setRooms(roomsMapped);
},[])
With Lazy initialisation with function as a parameter of useState
import React, { useState } from "react";
function App() {
const [rooms, setRooms] = useState(() => {
// May be a long computation initialization
const data = products.data || [];
return data.map(({ id, title }) => ({ id, title }));
});
return (
// JSX stuffs
)
}
You can use default props for this.set initial value with empty list .
You are getting 'Error: Maximum update depth exceeded', because your useEffect function doesn't have dependency array. Best way to fix this is to pass empty array as the second argument to useEffect like this:
useEffect(() => {
setRooms(roomsMapped);
},[]) <= pass empty array here
this will prevent component to re render, it you want your component to re render on props change you can pass the props in the array like this:
useEffect(() => {
setRooms(roomsMapped);
},[props.props1,props.props2])
here you can pass as many props as you want...
I have a mathematical algorithm that I want to keep separated from React. React would be a view to the state within that algorithm, and should not define the way of how the logic is flowing within the algorithm. Also, since it is separated, it's much easier to unit test the algorithm. I have implemented it using class components (simplified):
class ShortestPathRenderer extends React.Component {
ShortestPath shortestPath;
public constructor(props) {
this.shortestPath = new ShortestPath(props.spAlgorithm);
this.state = { version: this.shortestPath.getVersion() };
}
render() {
... // Render waypoints from shortestPath
}
onComponentDidUpdate(prevProps) {
if (prevProps.spAlgorithm !== this.props.spAlgorithm) {
this.shortestPath.updateAlgorithm(this.props.spAlgorithm);
}
}
onComponentWillUnmount() {
this.shortestPath = undefined;
}
onAddWayPoint(x) {
this.shortestPath.addWayPoint(x);
// Check if we need to rerender
this.setState({ version: this.shortestPath.getVersion() });
}
}
How would I go about this using React hooks? I was thinking about using the useReducer method. However, the shortestPath variable would then be a free variable outside the reducer and the reducer is no longer pure, which I find dirty. So in this case the whole state of the algorithm must be copied with every update on the internal state of the algorithm and a new instance must be returned, which is not efficient (and forces the logic of the algorithm to be the React-way):
class ShortestPath {
...
addWayPoint(x) {
// Do something
return ShortestPath.clone(this);
}
}
const shortestPathReducer = (state, action) => {
switch (action.type) {
case ADD_WAYPOINT:
return action.state.shortestPath.addWayPoint(action.x);
}
}
const shortestPathRenderer = (props) => {
const [shortestPath, dispatch] = useReducer(shortestPathReducer, new ShortestPath(props.spAlgorithm));
return ...
}
You can switch class-based in example in functional analog just using useState hook
function ShortestPathRenderer({ spAlgorithm }) {
const [shortestPath] = useRef(new ShortestPath(spAlgorithm)); // use ref to store ShortestPath instance
const [version, setVersion] = useState(shortestPath.current.getVersion()); // state
const onAddWayPoint = x => {
shortestPath.current.addWayPoint(x);
setVersion(shortestPath.current.getVersion());
}
useEffect(() => {
shortestPath.current.updateAlgorithm(spAlgorithm);
}, [spAlgorithm]);
// ...
}
I'd go with something like this:
const ShortestPathRenderer = (props) => {
const shortestPath = useMemo(() => new ShortestPath(props.spAlgorithm), []);
const [version, setVersion] = useState(shortestPath.getVersion());
useEffect(() => {
shortestPath.updateAlgorithm(spAlgorithm);
}, [spAlgorithm]);
const onAddWayPoint = (x) => {
shortestPath.addWayPoint(x);
// Check if we need to rerender
setVersion(shortestPath.getVersion());
}
return (
... // Render waypoints from shortestPath
)
}
you can even decouple logic further and create useShortestPath hook:
reusable stateful logic:
const useShortestPath = (spAlgorithm) => {
const shortestPath = useMemo(() => new ShortestPath(spAlgorithm), []);
const [version, setVersion] = useState(shortestPath.getVersion());
useEffect(() => {
shortestPath.updateAlgorithm(spAlgorithm);
}, [spAlgorithm]);
const onAddWayPoint = (x) => {
shortestPath.addWayPoint(x);
// Check if we need to rerender
setVersion(shortestPath.getVersion());
}
return [onAddWayPoint, version]
}
presentational part:
const ShortestPathRenderer = ({spAlgorithm }) => {
const [onAddWayPoint, version] = useShortestPath(spAlgorithm);
return (
... // Render waypoints from shortestPath
)
}
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!