reducer function is being called twice - javascript

I am practicing React hooks and encountered an issue with useReducer and dispatch functions. The reducer function that I created is being called twice when I'm clicking on either buttons on the SECOND time onwards. My console log output is printed once when clicking either buttons for the first time, and after that on each button push it's printed twice.
Here are my files:
Utils.js
import React, {createContext, useReducer, useContext} from 'react';
const initialState = {
text: 0
}
const StoreContext = createContext(initialState);
const reducer = (state, action) => {
console.log('hello');
switch(action.type) {
case 'add-counter': return {
...state,
text: state.text + 1
}
default:
throw new Error();
}
}
export const StoreProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreContext.Provider value={{state, dispatch}}>
{children}
</StoreContext.Provider>
)
}
export const useStore = (store) => {
const {state, dispatch} = useContext(StoreContext);
return {state, dispatch};
}
UserList.js
import React, {useCallback} from 'react';
import { Row, Col, Button } from 'antd';
import TextDisplayComponent from './TextDisplay';
import {useStore} from '../util';
function ListComponent() {
const {dispatch} = useStore();
const increment = useCallback(() => dispatch({type: 'add-counter'}), [dispatch])
return (
<Row>
<Col span={12} style={{textAlign: 'center'}}>
<Button type="primary" onClick={increment}>Press Me</Button>
</Col>
<Col span={12} style={{textAlign: 'center'}}>
<TextDisplayComponent />
</Col>
</Row>
)
}
export default ListComponent;
TextDisplay.js
import React, {useCallback} from 'react';
import {Button} from 'antd'
import {useStore} from '../util'
function TextDisplayComponent() {
const {state, dispatch} = useStore();
const increment = useCallback(() => dispatch({type: 'add-counter'}), [dispatch])
return (
<div>
{state.text}
<Button onClick={increment}>Add</Button>
</div>
)
}
export default TextDisplayComponent
App.js
import React from 'react';
import UserListComponent from './components/UserList';
import 'antd/dist/antd.css';
import {StoreProvider} from './util';
function App() {
React.createContext()
return (
<StoreProvider>
<div className="App">
<UserListComponent />
</div>
</StoreProvider>
);
}
export default App;
Can anyone please help? Thanks.
Complete test project can be found at https://github.com/Saro-DA/my-app.git

That's intentional. You're wrapping your app in <React.StrictMode>, which will cause that to happen in development mode.
Please check this:
Another thing that React Strict Mode does is run certain
callbacks/methods twice (in DEV mode ONLY). You read that right! The
following callbacks/methods will be run twice in Strict Mode (in DEV
mode ONLY)

Related

React showing a blank screen without any error messages (context api used)

I'm building a react Amazon clone, so in order to give the user the ability to add a product into his basket (as the first version of all the future checkout system), I've used Context Api to manage this.
When I finished writing Context Api code and adding it to the index.js file so I could access the data I got a blank screen with absolutely no error messages. I don't know where is exactly the problem.
StateProvider.js
import React , { createContext, useContext, useReducer} from 'react'
export const StateContext = createContext();
export const StateProvider = ({reducer , initialValue, children}) => {
<StateContext.Provider value={useReducer(reducer, initialValue)}>
{children}
</StateContext.Provider>
};
export const useStateValue = () => useContext(StateContext)
reducer.js
export const initialState = {
basket: [],
};
const reducer = (state, action) => {
switch(action.type) {
case "ADD_TO_BASKET":
return{
...state,
basket: [...state.basket, ...action.item]
};
default:
return state;
};
};
export default reducer;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { StateProvider } from './Special_components/StateProvider';
import reducer, { initialState } from './Special_components/reducer';
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<StateProvider reducer={reducer} initialState={initialState} >
<App />
</StateProvider>
</React.StrictMode>
)
Looks like you're providing props to the wrong component.
Try this in index.js:
...
<StateProvider reducer={reducer} initialValue={initialState}>
<App />
</StateProvider>
...
Also note how I renamed the prop to initialValue because that's what your StateProvider is expecting.
You forgot to add return in your StateProvider function
export const StateProvider = ({reducer , initialValue, children}) => {
return (
<StateContext.Provider value={useReducer(reducer, initialValue)}>
{children}
</StateContext.Provider>
)}
export const useStateValue = () => useContext(StateContext)
You must change arrow ES6 {} => () because {} need return
export const StateProvider = ({reducer , initialValue, children}) => (
<StateContext.Provider value={useReducer(reducer, initialValue)}>
{children}
</StateContext.Provider>
);

React Redux --> UseSelector --> Not Working

I am trying to make a simple exercise for react-redux to understand the process but somehow I am stuck .. any help would be really appreciate.
The interesting part is when I do subscribe and try to log store into the console, it works and shows me updated value but I am not able to select it using useSelector
Also with the help of Dev tool's i could see the state being changed from INIT to ADD_USER..
Below are my components files and reducers.
App.js
import React from "react";
import { Provider } from "react-redux";
import store from "./stores/store";
import { HomePage } from "./components/containers/HomePage";
function App() {
return (
<Provider store={ store }>
<HomePage/>
</Provider>
);
}
export default App;
HomePage.js. --> Here state.isLogin is not selected.. but the subscribe comment works
import React from "react";
import { Sidebar } from "./Sidebar";
import { LoginPage } from "./LoginPage";
import { useSelector } from "react-redux";
export const HomePage = () => {
const userLogin = useSelector(state => state.isLogin);
// const storeState = store.subscribe (() => console.log(store.getState()));
return (
<div>
<LoginPage />
<Sidebar />
</div>
);
};
LoginPage.js
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import * as action from "../../action/index";
export const LoginPage = (setLogin) => {
const dispatch = useDispatch();
const [name, setName] = useState("");
const createUser = (e) => {
e.preventDefault();
const addUser = {
name: name,
isLogin: true
};
dispatch(action.addUsers(addUser));
};
return (
<div className="card border-0 shadow">
<div className="card-header">Login Here!</div>
<div className="card-body">
<form onSubmit={(e) => createUser(e)}>
<div className="form-group">
<input
type="text"
className="form-control"
placeholder="Enter Your Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<button className="btn btn-primary" type="submit">
Create Contact
</button>
</form>
</div>
</div>
);
};
reducers - Index.js and user.js
import userReducer from './users'
import { combineReducers} from "redux";
const allReducers = combineReducers({
addUser : userReducer,
});
export default allReducers;
User.js
import * as types from '../actionTypes/index'
const intialState = {
user: [],
messages : [],
isLogin : false
};
const users = (state = intialState, action) => {
switch (action.type) {
case types.ADD_USER:
return {
...state,
user: [action.payload.name, ...state.user],
isLogin: action.payload.isLogin
};
default:
return state
}
}
export default users;
Store.js
import { createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import allReducers from '../reducers'
const store = createStore(allReducers,composeWithDevTools());
export default store;
Any idea's/Input on what went wrong ? or what is the issue?
Thank you
Try using below code in HomePage
const userLogin = useSelector(state => state.addUser.isLogin);

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

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

"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.

Material UI React Test cases failing - JEST, ENZYME

I have a connected component and have integrated MaterialUI in my component. For some reason, the tests keep on failing and I am not able to find some article online to resolve this.
Please advice.
Below is my code.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import SourceCurrencyInput from './components/SourceCurrencyInput';
import TargetCurrencyInput from './components/TargetCurrencyInput';
import {fetchCurrencyConverterRates} from './actions/currencyConverterActions';
import CurrencyConverterValue from './components/CurrencyConverterValue';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import {withStyles} from '#material-ui/core/styles';
import './App.css';
import {
changeSourceCurrencyValue,
changeTargetCurrencyValue
} from './actions/actions';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
};
class App extends Component {
componentDidMount() {
this.props.fetchCurrencyConverterRates(
this.props.srcCurrencyType,
this.props.tgtCurrencyType
);
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.srcCurrencyType !== this.props.srcCurrencyType
|| prevProps.tgtCurrencyType !== this.props.tgtCurrencyType) {
this.props.fetchCurrencyConverterRates(
this.props.srcCurrencyType,
this.props.tgtCurrencyType);
}
}
clearAll = () => {
this.props.sourceValue('');
this.props.targetValue('');
};
render() {
const {srcCurrencyType, tgtCurrencyType, srcCurrencyValue, tgtCurrencyValue, currencyConversionRate, classes} = this.props;
return (
<div className="App">
<AppBar position="static">
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow}>
Currency Converter by Arun Manohar
</Typography>
</Toolbar>
</AppBar>
<header className="App-header">
<SourceCurrencyInput/>
<TargetCurrencyInput/>
<Button variant="contained" color="primary" className={classes.button}
onClick={() => this.clearAll()}>Clear</Button>
</header>
<CurrencyConverterValue srcCurrencyType={srcCurrencyType}
tgtCurrencyType={tgtCurrencyType}
srcCurrencyValue={srcCurrencyValue}
tgtCurrencyValue={tgtCurrencyValue}
currencyConversionRate={currencyConversionRate}
/>
<footer><a href={'https://currencyconverterapi.com'} target={'_blank'}>API from currencyconverterapi.com</a></footer>
</div>
);
}
}
const mapStateToProps = state => {
return {
srcCurrencyType: state.currencyConverterReducer.sourceCurrencyType,
tgtCurrencyType: state.currencyConverterReducer.targetCurrencyType,
srcCurrencyValue: state.currencyConverterReducer.sourceCurrencyValue,
tgtCurrencyValue: state.currencyConverterReducer.targetCurrencyValue,
currencyConversionRate: state.getConvertedRates.data[0]
};
};
const mapDispatchToProps = (dispatch) => ({
fetchCurrencyConverterRates: (src, tgt) => dispatch(fetchCurrencyConverterRates(src, tgt)),
sourceValue: (val) => dispatch(changeSourceCurrencyValue(val)),
targetValue: (val) => dispatch(changeTargetCurrencyValue(val)),
});
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(App));
Below is my test case.
import React from 'react';
import {Provider} from 'react-redux';
import configureStore from 'redux-mock-store';
import App from './App';
import {createMount} from '#material-ui/core/test-utils';
const mockStore = configureStore();
const initialState = {sourceCurrencyType: 'USD'};
const store = mockStore(initialState);
describe('<App />', () => {
let mount;
beforeEach(() => {
mount = createMount();
});
it('should work', () => {
const wrapper = mount(<Provider store={store}><App/></Provider>);
console.log(wrapper.debug());
});
});
This is the error I get.
TypeError: Cannot read property 'sourceCurrencyType' of undefined
I just need a way to access this component in my tests. Please help me out.
Your initial state must keep the same structure with the reducer object, such:
const initialState = {
currencyConverterReducer: {
sourceCurrencyType: 'USD',
// rest of attributes of currencyConverterReducer
}
};

Categories