Having this React function, tried to run a test but it doesn't pass it:
import React, { useEffect } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { useIntl } from 'react-intl';
import {
PrimaryButton,
OutlineButton
} from 'react-komodo-design';
const Buttons = () => {
const dispatch = useDispatch();
const intl = useIntl();
const orderType = useSelector(
(state) => state.orderDetails.orderDetails.orderType,
shallowEqual
);
...
return (
<div>
<OutlineButton>
{intl.formatMessage({ id: 'buttons' })}
</OutlineButton>
{orderType.toLowerCase() !== 't9' && (
<PrimaryButton
onClick={clickReorder}
</PrimaryButton>
)}
</div>
);
};
export default Buttons;
The test file is this:
import React from 'react';
import { render } from '#testing-library/react';
import Buttons from './Buttons';
import { WrapIntlProvider, WrapStore } from '../../testsHelper';
describe('<Buttons />', function () {
it('should render <Buttons></Buttons>', () => {
const { container } = render(
<WrapStore>
<WrapIntlProvider>
<Buttons />
</WrapIntlProvider>
</WrapStore>
);
expect(container).toMatchSnapshot();
});
});
Error message: TypeError: Cannot read property 'toLowerCase' of undefined
What can be done to avoid this error?
I've tried to add values inside the test function like this:
<Buttons orderType="test" /> or <Buttons orderType={"test'} /> or send it as a variable:
describe('<Buttons />', function () {
it('should render <Buttons></Buttons>', () => {
const xx = "test"; // <--- added here
const { container } = render(
<WrapStore>
<WrapIntlProvider>
<Buttons orderType={xx} />
</WrapIntlProvider>
</WrapStore>
);
expect(container).toMatchSnapshot();
});
});
import * as redux from 'react-redux'
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ orderType:'test' })
Try to mock useSelector like this.
Related
When I try to use createContext() the console gives me this error:
App.js:6
Uncaught TypeError: Cannot destructure property 'consoleLogFunction' of '(0 , react__WEBPACK_IMPORTED_MODULE_1__.useContext)(...)' as it is null.
I've seen others asking questions about this here in Stack Overflow but I can't find a solution.
GlobalContext.js
import React from 'react'
import { createContext } from 'react'
export const AppContext = createContext();
function GlobalContext() {
const consoleLogFunction = () => {
console.log("ok")
}
return (
<AppContext.Provider value={{consoleLogFunction}}></AppContext.Provider>
)
}
export default GlobalContext
App.js
import "./index.css";
import { useContext, useEffect } from "react";
import { AppContext } from "./components/GlobalContext";
function App() {
const { consoleLogFunction } = useContext(AppContext);
useEffect(() => {
consoleLogFunction();
}, []);
return (
<AppContext>
<div>home</div>
</AppContext>
);
}
export default App;
You don't need to export 'AppContext', creating the provider and exporting that is good enough.
Try this, I've also made a couple of modifications to make it easier to use the context later:
GlobalContext.js
import React from 'react'
import { createContext, useContext } from 'react'
const AppContext = createContext();
function GlobalContext({ children }) {
const consoleLogFunction = () => {
console.log("ok")
}
return (
<AppContext.Provider value={{consoleLogFunction}}>
{ children }
</AppContext.Provider>
)
}
export default GlobalContext;
// This is a helper
export const useGlobalContext = () => useContext(AppContext);
Home.js
import { useEffect } from "react";
import { useGlobalContext } from "./components/GlobalContext";
export const Home = ({ children }) => {
const { consoleLogFunction } = useGlobalContext();
useEffect(() => {
consoleLogFunction();
}, []);
return(
<div>home</div>
)
};
App.js
import "./index.css";
import { useEffect } from "react";
import GlobalContext from "./components/GlobalContext"
import { Home } from "./components/Home";
function App() {
return(
<GlobalContext>
<Home />
</GlobalContext>
);
}
export default App;
hello man the problem is because App component is not wrapped inside GlobalContext . and in the GlobalContext component you should handle the children prop.
it will work when doing it like this example :
import { useEffect, useContext, createContext } from "react";
import "./styles.css";
export const AppContext = createContext();
function GlobalContext(props) {
const consoleLogFunction = () => {
console.log("ok");
};
return (
<AppContext.Provider value={{ consoleLogFunction }}>
{props.children}
</AppContext.Provider>
);
}
const Home = () => {
const { consoleLogFunction } = useContext(AppContext);
useEffect(() => {
consoleLogFunction();
}, []);
return <div>home</div>;
};
export default function App() {
return (
<GlobalContext>
<Home />
</GlobalContext>
);
}
Hope this help.
I have react project generated by vite, I get this error when I add eventListener to the DOM. I also use React context API. But I think there might be a problem with the StateProvider.jsx that contains the context API but I'm not sure.
The error says:
Cannot update a component (`StateProvider`) while rendering a different component (`DeltaY`). To locate the bad setState() call inside `DeltaY`, follow the stack trace as described in ...
Here is the snapshot of the error in the console:
Here is the code:
main.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { StateProvider } from './StateProvider.jsx';
import reducer, { initialState } from './reducer';
ReactDOM.createRoot(document.getElementById('root')).render(
<StateProvider initialState={initialState} reducer={reducer}>
<App />
</StateProvider>,
);
App.jsx
import './App.css';
import DeltaY from './DeltaY';
import { useStateValue } from './StateProvider';
function App() {
return (
<>
<DeltaY />
<Desc />
</>
);
}
const Desc = () => {
const [{ deltaY, scrollMode }, dispatch] = useStateValue();
return (
<>
<h1> deltaY: {deltaY} </h1>
<h1> scroll: {scrollMode} </h1>
</>
);
};
export default App;
DeltaY.jsx
import { useEffect, useState } from 'react';
import { useStateValue } from './StateProvider';
const DeltaY = () => {
// ------------------------------ context API ------------------------------- //
const [{ state }, dispatch] = useStateValue();
// ------------------------------ context API ------------------------------- //
const [scrollMode, setScrollMode] = useState(false);
const [deltaY, setDeltaY] = useState(0);
useEffect(() => {
function handleWheel(e) {
setDeltaY(e.deltaY);
setScrollMode(true);
}
window.addEventListener('wheel', handleWheel);
return () => window.removeEventListener('wheel', handleWheel);
}, []);
useEffect(() => {
setTimeout(() => {
setScrollMode(true);
}, 1000);
}, []);
// ------------------------------ dispatch ------------------------------- //
dispatch({
type: 'GET_DELTAY',
value: deltaY,
});
dispatch({
type: 'GET_SCROLLMODE',
value: scrollMode,
});
// ------------------------------ dispatch ------------------------------- //
return null;
};
export default DeltaY;
StateProvider.jsx
import React, { createContext, useContext, useReducer } from 'react';
// prepare the daya layer
export const StateContext = createContext();
// Wrap our app and provide the data layer
export const StateProvider = ({ reducer, initialState, children }) => {
return (
<>
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
</>
);
};
// PUll the information from the data layer
export const useStateValue = () => useContext(StateContext);
Any Idea how to fix it ? thankyou
Here is the solution:
instead of using it as a component, I use it as a custom Hook
Here is the code:
useDeltaY.jsx
import { useEffect, useState } from 'react';
const useDeltaY = () => {
const [scrollMode, setScrollMode] = useState(false);
const [deltaY, setDeltaY] = useState(0);
useEffect(() => {
function handleWheel(e) {
setDeltaY(e.deltaY);
setScrollMode(true);
}
window.addEventListener('wheel', handleWheel);
return () => window.removeEventListener('wheel', handleWheel);
}, []);
useEffect(() => {
setTimeout(() => {
setScrollMode(false);
}, 1000);
}, [scrollMode]);
return [deltaY, scrollMode];
};
export default useDeltaY;
App.jsx
import './App.css';
import useDeltaY from './useDeltaY';
function App() {
return (
<>
<Desc />
</>
);
}
const Desc = () => {
const [deltaY, scrollMode] = useDeltaY();
return (
<>
<h1> deltaY: {deltaY} </h1>
{scrollMode ? (
<h1> scrollMode: active </h1>
) : (
<h1> scrollMode: inActive </h1>
)}
</>
);
};
export default App;
I created a custom hook, Custom.js:
import React, {useState, useEffect} from 'react';
import Clarifai from 'clarifai';
const app = new Clarifai.App({
apiKey: 'XXXXXXXXXXXXXX'
})
const Custom = () => {
const [input, setInput] = useState('');
const [imgUrl, setImgUrl] = useState('');
function onInputChange (text) {
setInput(text);
}
useEffect(()=>{
setImgUrl(input)
}, [input])
function onSubmit () {
console.log('submitted');
console.log(imgUrl)
app.models.predict(Clarifai.COLOR_MODEL, "https://www.takemefishing.org/getmedia/bde1c54e-3a5f-4aa3-af1f-f2b99cd6f38d/best-fishing-times-facebook.jpg?width=1200&height=630&ext=.jpg").then(
function(response) {
console.log(response);
},
function(err) {
// there was an error
}
);
}
return {input, imgUrl, onInputChange, onSubmit}
}
export default Custom;
I imported this custom hook into 2 of my other components, FaceRecognition.js and InputForm.js.
FaceRecognition.js:
import React from 'react';
import Custom from '../Custom';
const FaceRecognition = () => {
const { imgUrl } = Custom();
function yes (){
return console.log(imgUrl)
}
yes()
return (
<div>
<h1 className='white'>The url is {ImgUrl} </h1>
<img width={'50%'} alt=''src={imgUrl}/>
</div>
);
}
export default FaceRecognition;
ImportForm.js:
import React, {useState} from 'react';
import './InputForm.css'
import Custom from '../Custom';
const InputForm = () => {
const { onInputChange, onSubmit } = Custom();
return (
<>
<p className='txt f3'>Enter image link address</p>
<div className='center flex w-70'>
<input type='text' className='w-80 pa1' onChange={(e)=>onInputChange(e.target.value)}/>
<button className='w-20 pa1 pointer' onClick={onSubmit}>Detect</button>
</div>
</>
);
}
export default InputForm;
The functions onSubmit and onImputChange work as expected for InputForm.js and the value of imgUrl logs on the console when function onSubmit runs, as expected. But the imgUrl state which is a string fails to show up between the h1 tags <h1 className='white'>The url is {imgUrl} boy</h1> from my FaceRecognition.js snippet above, and it also doesn't work as the src of the image <img width={'50%'} alt=''src={imgUrl}/> below the h1 tag. This is my problem.
Issue
React hooks don't magically share state. You've two separate instances of this Custom function, each with their own useState hook. I say "function" because you've also mis-named your hook. All React hooks should be named with a "use-" prefix so React can identify it and apply the Rules of Hooks against it.
Solution
If you want separate instances of your useCustom hook to share state then the state needs to be lifted to a common component to be shared. For this you should use a React Context.
Example:
import React, { createContext, useContext, useState, useEffect } from 'react';
import Clarifai from 'clarifai';
const app = new Clarifai.App({
apiKey: 'XXXXXXXXX'
});
const CustomContext = createContext({
input: '',
imgUrl: '',
onInputChange: () => {},
onSubmit: () => {}
});
const useCustom = () => useContext(CustomContext);
const CustomProvider = ({ children }) => {
const [input, setInput] = useState('');
const [imgUrl, setImgUrl] = useState('');
function onInputChange (text) {
setInput(text);
}
useEffect(()=>{
setImgUrl(input);
}, [input]);
function onSubmit () {
console.log('submitted');
console.log(imgUrl);
app.models.predict(
Clarifai.COLOR_MODEL,
"https://www.takemefishing.org/getmedia/bde1c54e-3a5f-4aa3-af1f-f2b99cd6f38d/best-fishing-times-facebook.jpg?width=1200&height=630&ext=.jpg"
).then(
function(response) {
console.log(response);
},
function(err) {
// there was an error
}
);
}
return (
<CustomContext.Provider value={{ input, imgUrl, onInputChange, onSubmit }}>
{children}
</CustomContext.Provider>
);
}
export {
CustomContext,
useCustom
};
export default CustomProvider;
Usage:
Wrap your app with your CustomProvider component.
import CustomProvider from '../path/to/CustomProvider';
...
return (
<CustomProvider>
<App />
</CustomProvider>
);
Import and use the useCustom hook in consumers.
import React from 'react';
import { useCustom } from '../path/to/CustomProvider';
const FaceRecognition = () => {
const { imgUrl } = useCustom();
useEffect(() => {
console.log(imgUrl);
});
return (
<div>
<h1 className='white'>The url is {ImgUrl}</h1>
<img width={'50%'} alt='' src={imgUrl}/>
</div>
);
}
export default FaceRecognition;
...
import React, {useState} from 'react';
import './InputForm.css'
import { useCustom } from '../path/to/CustomProvider';
const InputForm = () => {
const { onInputChange, onSubmit } = useCustom();
return (
<>
<p className='txt f3'>Enter image link address</p>
<div className='center flex w-70'>
<input
type='text'
className='w-80 pa1'
onChange={(e) => onInputChange(e.target.value)}
/>
<button
className='w-20 pa1 pointer'
onClick={onSubmit}
>
Detect
</button>
</div>
</>
);
}
export default InputForm;
try to put your return statement inside the .then of the predict
I am have been working on a little project to better understand react. I recently converted it to use hooks and I am trying to implement redux, with it. However I get the following error now.
TypeError: searchField.toLowerCase is not a function
looking at the docs, I stopped using connect from react-redux and switched to using useDispatch and useSelector. But I believe I have set up everything correctly but not sure as to why this error being raise.
This is my action.js
import { SEARCH_EVENT } from './searchfield_constants';
export const setSearchField = (payload) => ({ type: SEARCH_EVENT, payload });
This is my reducer
import { SEARCH_EVENT } from './searchfield_constants';
const initialState = {
searchField: '',
};
export const searchRobots = (state = initialState, action = {}) => {
switch (action.type) {
case SEARCH_EVENT:
return { ...state, searchField: action.payload };
default:
return state;
}
};
this is my index.js where I am using the Provider from react-redux
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { searchRobots } from './searchfield/searchfield_reducers';
import './styles/index.css';
import App from './App';
const store = createStore(searchRobots);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
finally here is my App.jsx
import { useState, useEffect, useCallback } from 'react';
import { setSearchField } from './searchfield/searchfield_actions';
import { useDispatch, useSelector } from 'react-redux';
import axios from 'axios';
import React from 'react';
import CardList from './components/CardList';
import SearchBox from './components/SearchBox';
import Scroll from './components/Scroll';
import Error from './components/Error';
import 'tachyons';
import './styles/App.css';
// const mapStateToProps = (state) => ({
// searchField: state.searchField,
// });
// const mapDispatchToProps = (dispatch) => ({
// onSearchChange: (e) => dispatch(setSearchField(e.target.value)),
// });
const App = () => {
const searchField = useSelector(state => state.searchField)
const dispatch = useDispatch();
const [robots, setRobots] = useState([]);
// const [searchField, setSearchField] = useState('');
const fetchUsers = useCallback(async () => {
try {
const result = await axios('//jsonplaceholder.typicode.com/users');
setRobots(result.data);
} catch (error) {
console.log(error);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
fetchUsers();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const filteredRobots = robots.filter((robot) => {
return robot.name.toLowerCase().includes(searchField.toLowerCase());
});
return !robots.length ? (
<h1 className='f1 tc'>Loading...</h1>
) : (
<div className='App tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={dispatch(setSearchField(e => e.target.value))} />
<Scroll>
<Error>
<CardList robots={filteredRobots} />
</Error>
</Scroll>
</div>
);
};
export default App;
what am I doing wrong?
So the solution was the following,
I created a function called on searchChange, which calls dispatch and then the setSearchField which uses the e.target.value as the payload.
const onSearchChange = (e) => {
dispatch(setSearchField(e.target.value));
};
so the final return looks like the following
return !robots.length ? (
<h1 className='f1 tc'>Loading...</h1>
) : (
<div className='App tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={onSearchChange} />
<Scroll>
<Error>
<CardList robots={filteredRobots} />
</Error>
</Scroll>
</div>
);
};
In you App.js, convert this line
const searchField = useSelector(state => state.searchField)
to
const { searchField } = useSelector(state => state.searchField)
basically de-structure out searchField from state.searchField
This is attributed to the fact how redux sets state.
In your reducer searchRobots the initial state provided by redux will be
state = {
...state,
searchField
}
and in this line return { ...state, searchField: action.payload };, you're adding
another property searchField to state.searchField object so you'll need to de-structure it out.
It looks like your searchField value is getting set to undefined or some non-string value.
I found this line to be incorrect
<SearchBox searchChange={dispatch(setSearchField(e => e.target.value))} />
It should be changed to
<SearchBox searchChange={() => dispatch(setSearchField(e => e.target.value))} />
So that on search change this function can be called. Currently you are directly calling dispatch and this may be setting your searchField to undefined
Also for safer side before using toLowerCase() convert it to string ie searchField.toString().toLowerCase()
I'm having issues testing my components that use dispatch via useReducer with React-testing-library.
I created a less complex example to try to boil down what is going on and that is still having the same dispatch is not a function problem. When I run my tests, I am getting this error:
11 | data-testid="jared-test-button"
12 | onClick={() => {
> 13 | dispatch({ type: 'SWITCH' })
| ^
14 | }}
15 | >
16 | Click Me
Also, if I do a console.log(typeof dispatch) inside RandomButton, and I click on the button the output says function.
Here is the test in question.
import React from 'react'
import RandomButton from '../RandomButton'
import { render, fireEvent } from '#testing-library/react'
describe('Button Render', () => {
it('click button', () => {
const { getByTestId, queryByText } = render(<RandomButton />)
expect(getByTestId('jared-test-button')).toBeInTheDocument()
fireEvent.click(getByTestId('jared-test-button'))
expect(queryByText('My name is frog')).toBeInTheDocument()
})
})
Here is my relevant code:
RandomButton.js
import React, { useContext } from 'react'
import MyContext from 'contexts/MyContext'
const RandomButton = () => {
const { dispatch } = useContext(MyContext)
return (
<div>
<Button
data-testid="jared-test-button"
onClick={() => {
dispatch({ type: 'SWITCH' })
}}
>
Click Me
</Button>
</div>
)
}
export default RandomButton
MyApp.js
import React, { useReducer } from 'react'
import {myreducer} from './MyFunctions'
import MyContext from 'contexts/MyContext'
import RandomButton from './RandomButton'
const initialState = {
blue: false,
}
const [{ blue },dispatch] = useReducer(myreducer, initialState)
return (
<MyContext.Provider value={{ dispatch }}>
<div>
{blue && <div>My name is frog</div>}
<RandomButton />
</div>
</MyContext.Provider>
)
export default MyApp
MyFunctions.js
export const myreducer = (state, action) => {
switch (action.type) {
case 'SWITCH':
return { ...state, blue: !state.blue }
default:
return state
}
}
MyContext.js
import React from 'react'
const MyContext = React.createContext({})
export default MyContext
It is probably something stupid that I am missing, but after reading the docs and looking at other examples online I'm not seeing the solution.
I've not tested redux hooks with react-testing-library, but I do know you'll have to provide a wrapper to the render function that provides the Provider with dispatch function.
Here's an example I use to test components connected to a redux store:
testUtils.js
import React from 'react';
import { createStore } from 'redux';
import { render } from '#testing-library/react';
import { Provider } from 'react-redux';
import reducer from '../reducers';
// https://testing-library.com/docs/example-react-redux
export const renderWithRedux = (
ui,
{ initialState, store = createStore(reducer, initialState) } = {},
options,
) => ({
...render(<Provider store={store}>{ui}</Provider>, options),
store,
});
So, based upon what you've shared I think the wrapper you'd want would look something like this:
import React from 'react';
import MyContext from 'contexts/MyContext';
// export so you can test that it was called with specific arguments
export dispatchMock = jest.fn();
export ProviderWrapper = ({ children }) => (
// place your mock dispatch function in the provider
<MyContext.Provider value={{ dispatch: dispatchMock }}>
{children}
</MyContext.Provider>
);
and in your test:
import React from 'react';
import RandomButton from '../RandomButton';
import { render, fireEvent } from '#testing-library/react';
import { ProviderWrapper, dispatchMock } from './testUtils';
describe('Button Render', () => {
it('click button', () => {
const { getByTestId, queryByText } = render(
<RandomButton />,
{ wrapper: ProviderWrapper }, // Specify your wrapper here
);
expect(getByTestId('jared-test-button')).toBeInTheDocument();
fireEvent.click(getByTestId('jared-test-button'));
// expect(queryByText('My name is frog')).toBeInTheDocument(); // won't work since this text is part of the parent component
// If you wanted to test that the dispatch was called correctly
expect(dispatchMock).toHaveBeenCalledWith({ type: 'SWITCH' });
})
})
Like I said, I've not had to specifically test redux hooks but I believe this should get you to a good place.