TypeError: searchField.toLowerCase is not a function when using hooks an redux - javascript

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()

Related

Cannot update a component while rendering a different component To locate the bad setState() call inside `DeltaY`, follow the stack trace

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;

useSelector not updating values, and not triggering useEffect but value can be read using store (redux)

I've been using Redux for quite some time now but for some reason my code just doesn't work:
import { store } from "../pages/_app";
....
const dispatch = useDispatch();
const [state_sample, setSs]: any = useState(0); // - just use for triggering useEffect
const audioCurrentTimeState: any = useSelector((state: RootState) => {
state.AudioCurrentTimeState;
});
useEffect(() => { // - I am trying to trigger this useEffect but it doesn't work
console.log(audioCurrentTimeState); // - doesn't work (?)
console.log(store.getState().AudioCurrentTimeState); // - works
}, [audioCurrentTimeState, state_sample]);
return (
<div className={style.waveform_component_root}>
<p
onClick={() => {
console.log("clicked!");
setSs(Math.random()); // - just use for triggering useEffect
dispatch({
type: "AudioCurrentTimeReducer/SET",
value: Math.random(),
});
}}
>
click me
</p>
</div>)
Here is the reducer of audioCurrentTimeState :
export const AudioCurrentTimeReducer = (value = 0, action: any) => {
switch (action.type) {
case "AudioCurrentTimeReducer/SET":
return action.value;
default:
return value;
}
};
Here is rootState :
import { combineReducers } from "redux";
import {
AudioCurrentTimeReducer,
} from "./reducers/reducers";
const rootReducers = combineReducers({
AudioCurrentTimeState: AudioCurrentTimeReducer,
});
export type RootState = ReturnType<typeof rootReducers>;
export default rootReducers;
And I setup by redux like this :
import type { AppProps } from "next/app";
import { createStore } from "redux";
import { Provider } from "react-redux";
import rootReducers from "../scripts/redux/rootReducer";
export const store = createStore(rootReducers);
export default function App({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
I've never encountered this issue before, and I can't seem to find any references if there are any changes. But as you can see from above I can only get the value of AudioCurrentTimeState if I call it by store.getState().AudioCurrentTimeState not by useSelector and I need to trigger the useEffect for this.
You forgot to return the value in the useSelector
correct:
const audioCurrentTimeState: any = useSelector((state: RootState) => {
RETURN state.AudioCurrentTimeState;
});
// OR short
const audioCurrentTimeState: any = useSelector((state: RootState) => state.AudioCurrentTimeState);
was:
const audioCurrentTimeState: any = useSelector((state: RootState) => {
state.AudioCurrentTimeState;
});

React - Object(...) is not a function or its return value is not iterable

I have a small React hook that is wrapping a class component and I am trying to pass a state from this hook to the class using useEffect but I keep getting the followwing error;
TypeError: Object(...) is not a function or its return value is not iterable on the following line;
const [{}, dispatch] = useStateValue();
I use the same line in another hook elsewhere in my app so I am not sure why this one is throwing this particular error
This is the complete component code.
If anyone has any ideas why this may not be working I would be most appreciative to hear them.
Thank you.
Component
import React, { useEffect } from 'react';
import App from 'next/app';
import firebase from 'firebase';
import { useStateValue, StateProvider } from '../state';
import { firebaseConfig } from '../constants';
import { initialState, reducer } from '../reducer';
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
function AppWrapper(Component) {
return function WrappedComponent(props) {
const [{}, dispatch] = useStateValue();
useEffect(() => {
console.log('Moiunted');
firebase.auth().onAuthStateChanged((user) => {
dispatch({
type: 'userLogin',
currentUser: user
})
});
}), [];
return <Component {...props} />
}
}
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<StateProvider
initialState={initialState}
reducer={reducer}
>
<Component {...pageProps} />
</StateProvider>
);
}
}
export default AppWrapper(MyApp)
State function
import React, { createContext, useContext, useReducer } from 'react';
export const StateContext = createContext();
export const StateProvider = ({reducer, initialState, children}) => (
<StateContext.Provider
value={useReducer(reducer, initialState)}
>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);

react redux action not calling reducer

I cant for the life of my get one of my actions to call the reducer.
I've written multiple other actions and reducers in this app, which works perfectly.
It is the beginning of a filter function. I have a text input field, where I constantly keep track of the input field state, and dispatch the field value to the redux action. I have a console.log inside the action, which logs every keypress properly.
What I cant seem to understand, is why the reducer isn't called at each keypress. I've tried multiple console.log's inside the reducer, however none of them gets logged with keypresses.
The first console.log inside the reducer is called when I refresh the page.
if I try to log action.type instead, I get:
##redux/PROBE_UNKNOWN_ACTION1.0.i.0.0.9
If I try the same in any of the other reducers I've written in the same app, I get the appropriate type logged out.
Some code:
Filter Component:
import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import { filterAnecdotes } from '../reducers/filterReducer';
const Filter = () => {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value)
}
useDispatch(filterAnecdotes(value));
const style = {
marginBottom: 10
}
return (
<div style={style}>
filter <input onChange={handleChange} />
</div>
)
}
export default Filter
Reducer and action:
Here, I haven't figured out how to get the state of all anecdotes, and what to actually return. For now, I'm just trying to have it get called properly.
const filterReducer = (state = null, action) => {
// These logs only get logged on refresh.
// The action.type should be 'SEARCH', but is not.
console.log("From filterReducer")
console.log(action.type)
switch(action.type) {
case 'SEARCH':
// This is not called at all.
console.log(action.type, action.data)
return action.data;
default:
return state
}
}
export const filterAnecdotes = (filter) => {
console.log(filter);
return {
type: 'SEARCH',
data: filter
}
}
export default filterReducer;
Example of a redux file that actually works:
const reducer = (state = [], action) => {
console.log(state, action)
switch(action.type){
case 'NEW_ENTRY_NOTIFICATION':
console.log(action.type)
return [...state, action.data]
case 'NEW_VOTE_NOTIFICATION':
return [...state, action.data]
case 'HIDE_NOTIFICATION':
return []
default:
return state
}
}
export const createNewEntryNotification = (notification) => {
return {
type: 'NEW_ENTRY_NOTIFICATION',
data: notification
}
}
export const createNewVoteNotification = (notification) => {
return {
type: 'NEW_VOTE_NOTIFICATION',
data: notification
}
}
export const hideNotification = () => {
return {
type: 'HIDE_NOTIFICATION'
}
}
export default reducer
App component (should be irrelevant)
import React from 'react';
import NewEntry from './components/AnecdoteForm'
import AnecdoteList from './components/AnecdoteList'
import Notification from './components/Notification'
import Filter from './components/Filter';
const App = () => {
return (
<div>
<h2>Anecdotes</h2>
<Filter />
<Notification />
<AnecdoteList />
<NewEntry />
</div>
)
}
store (should be irrelevant)
import anecdoteReducer from './anecdoteReducer';
import notificationReducer from './notificationReducer';
import filterReducer from './filterReducer';
import { combineReducers } from 'redux'
const reducer = combineReducers({
anecdotes: anecdoteReducer,
notifications: notificationReducer,
filters: filterReducer,
});
export default reducer
index.js (should also be irrelevant)
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './App'
import reducer from './reducers/store'
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
export default App
I'll be happy to provide more information if needed.
This is for a project from fullstackopen.
Try to instantiate useDispatch like this after const [value, setValue] = useState("");
const dispatch = useDispatch();
And then use the intance to dispatch the action
dispatch(filterAnecdotes(value));
the use of useDispatch is misunderstood.
Link for reference: https://react-redux.js.org/api/hooks#usedispatch
You should create a dispatch from useDispatch:
const dispatch = useDispatch();
dispatch(filterAnecdotes(value));

"TypeError: dispatch is not a function" when using useReducer/useContext and React-Testing-Library

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.

Categories