I am trying to write a redux integration test. My test successfully passes, however, I get the message:
console.error node_modules/redux/lib/utils/warning.js:14
Unexpected key "word" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead:
"jotto", "router". Unexpected keys will be ignored.
It seems to me that my createStore and root reducer look fine. Is there something I need to change that is messing up this preloaded state? You can find the scripts below. Thanks!
jottoRedux.test.js:
import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
import {routerMiddleware} from 'connected-react-router';
import rootReducer from 'reducers/rootReducer';
import {initialState} from './jottoReducer';
import {createBrowserHistory} from 'history';
export const history = createBrowserHistory();
const middleware = applyMiddleware(routerMiddleware(history), thunkMiddleware);
export const storeFactory = () =>
createStore(rootReducer(createBrowserHistory()), {...initialState}, middleware);
export const setWord = (word) => ({
type: 'SET_WORD',
word,
});
describe('testing SET_WORD action', () => {
let store;
beforeEach(() => {
store = storeFactory();
});
test('state is updated correctly for an unsuccessful guess', () => {
store.dispatch(setWord('foo'));
const expectedState = {
...initialState,
word: 'foo',
};
const newState = store.getState().jotto;
expect(newState).toEqual(expectedState);
});
});
jottoReducer.js:
export const initialState = {
word: null,
};
const jotto = (state = initialState, action) => {
switch (action.type) {
case 'SET_WORD':
return {
...state,
word: action.word,
};
default:
return state;
}
};
export default jotto;
rootReducer:
import {combineReducers} from 'redux';
import {connectRouter} from 'connected-react-router';
import jotto from './jottoReducer';
export default (historyObject) => combineReducers({
jotto,
router: connectRouter(historyObject),
});
Try this:
export const storeFactory = () =>
createStore(rootReducer(createBrowserHistory()), { jotto: initialState }, middleware);
Related
I don't understand why the following error appears:
reducers\userReducer
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: 'user',
initialState:{
currentUser: {},
isAuth: false
},
reducers: {
setUser(state, action) {
state.currentUser = action.payload;
state.isAuth = true
}
}
})
export const {setUser} = userSlice.actions
export default userSlice.reducer
It seems to me that the problem is in combineReducers, but I may be wrong
index.js
import {configureStore, combineReducers} from '#reduxjs/toolkit'
import {applyMiddleware} from 'redux'
import {composeWithDevTools } from 'redux-devtools-extension'
import thunk from "redux-thunk";
import {userSlice} from './userReducer';
const rootReducer = combineReducers({
user:userSlice.reducer
})
export const setupStore = () => {
return configureStore ({
reducer: rootReducer,
middleware: composeWithDevTools(applyMiddleware(thunk))
});
}
The innitial state shows in the DevTools but nothing else, the actions taken after the code has rendered do not show up.
in pages/_app.tsx I have this
import getStore from '../store/store'
export default function MyApp({ Component, pageProps }: AppProps) {
const store = getStore(pageProps.initialState);
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
If it wasn't for the above setup (where I needed to pass props before initialising the state) the #Firmino Changani would be right, but I can't run getStore at the store because I wouldn't get the initial state
Here's the store
import { configureStore, ThunkAction, Action, combineReducers } from "#reduxjs/toolkit";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import UISlice from '#todoSlice/todoSlice'
const rootReducer = combineReducers({
todo: UISlice,
});
export default function getStore(incomingPreloadState?: AppState) {
const store = configureStore({
reducer: rootReducer,
preloadedState: incomingPreloadState,
});
return store;
}
export type AppState = ReturnType<typeof rootReducer>;
export type AppDispatch = ReturnType<typeof getStore>["dispatch"];
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
AppState,
unknown,
Action<string>
>;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;
Here's the page itself
import type { NextPage } from 'next'
import getStore, { useAppDispatch, useAppSelector, AppState } from '#store/store'
import { intidialTodos } from '#todoSlice/todoSlice'
export async function getServerSideProps() {
const store = getStore();
await store.dispatch(intidialTodos());
return {
props: {
initialState: store.getState(),
},
};
}
const Home: NextPage = () => {
const dispatch = useAppDispatch();
const categories = useAppSelector( ( state: AppState ) => state.todo.categories );
const addTodo = () => dispatch(addTodos({name: "The one", id: 506}))
return (
<><button onClick={addTodo}>Add!</button>
.....
)}
I think we can't expect the intidialTodos triggered from getServerSideProps to in the actions pannel of dev tools, But when I click the add button, I should see the action in the dev tools and I should see the new added item in the state, right?
The app works, the new item gets added and everything but nothing after ##INIT happens in the Redux dev tools
I tried this but did not work:
import { composeWithDevTools } from 'redux-devtools-extension';
import UISlice from '#todoSlice/todoSlice'
import {createAsyncThunk} from '#todoSlice/todoSlice';
const rootReducer = combineReducers({
todo: UISlice,
});
export default function getStore(incomingPreloadState?: AppState) {
const composeEnhancers = composeWithDevTools({ actionCreators: [createAsyncThunk], trace: true, traceLimit: 25 });
const store = configureStore({
reducer: rootReducer,
preloadedState: incomingPreloadState,
devTools: false,
enhancers: [composeEnhancers({ realtime: true, port: 8000 })],
});
return store;
}
You have devTools set to false. I have the following setup on a Next app:
import { createLogger } from "redux-logger";
const logger = createLogger();
export function makeStore() {
return configureStore({
devTools: true,
middleware: [logger],
reducer: {/* My reducers */},
});
}
const store = makeStore();
Please use next-redux-wrapper instead of reinventing that yourself. That package already does all you are trying to do here.
I'm trying pass the data from reducer to component and receive as props.
But the data return UNDEFÄ°NED, so I have tried console the data on reducer and action, but it's okey. There isn't any problem with the data coming from the API, but it always return to component undefined. Where is my fault?
Action
export default ProfileTab;
import axios from 'axios';
import { BASE, API_KEY } from '../config/env';
export const FETCHED_MOVIES = 'FETCHED_MOVIES';
export function fetchMovies() {
return (dispatch) => {
axios
.get(`${BASE}s=pokemon&apikey=${API_KEY}`)
.then((result) => result.data)
.then((data) =>
dispatch({
type: FETCHED_MOVIES,
payload: data.Search,
}),
);
};
}
Reducer
import { FETCHED_MOVIES } from '../actions/movies';
const initialState = {
fetching: false,
fetched: false,
movies: [],
error: {},
};
export default (state = initialState, action) => {
switch (action.type) {
case 'FETCHED_MOVIES':
return {
...state,
movies: action.payload,
};
default:
return state;
}
};
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { fetchMovies } from '../../actions/movies';
class Case extends Component {
static propTypes = {
movies: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchMovies();
}
onChangeHandler = (e) => {
this.setState({
input: e.target.value,
});
};
render() {
console.log(this.props.movies);
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
<h1>mert</h1>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
movies: state.movies,
};
};
const mapDispatchToProps = {
fetchMovies,
};
export default connect(mapStateToProps, mapDispatchToProps)(Case);
Do this in the connect statement:
export default connect(mapStateToProps,{fetchMovies})(Case);
And remove the mapDispatchToProps function from your code.
Dispatching props as an object is quite incorrect. Try this, and it should work.
That's because your mapDispatchToProps function should return an object and take dispatch as parameter. Each field in your returned object should contain a function that dispatches your action.
So try something like this:
const mapDispatchToProps = dispatch => {
return {
fetchMovies: () => dispatch(fetchMovies())
}
}
Although there's already an accepted answer, I'm not sure how correct it is, as it's completely valid to pass mapDispatchToProps the way you did with the latest react (16.13.1) and react-redux (7.2.1) versions (I'm not sure about earlier versions).
Now, assuming your question contains the whole code, there are two important things missing:
Creating the store:
import { createStore } from "redux";
const store = createStore(reducer);
and passing it to the Provider component:
<Provider store={store}>
If you go ahead and do as above, you'll see that this.props.fetchMovies emits the following error:
Actions must be plain objects. Use custom middleware for async actions.
To fix it, do as it says and add a middleware, e.g. thunk:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
What follows is the full code. Note that I "split" fetchMovies into two functions: sync and async, for illustrating the difference usage between the two. I also modified your code (made is shorter, mostly) for this answer's readability. You can also see a live demo here:
File app.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMoviesSync, fetchMoviesAsyncMock } from "./api";
class App extends Component {
componentDidMount() {
this.props.fetchMoviesSync();
this.props.fetchMoviesAsyncMock();
}
render() {
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
{this.props.movies.join("\n")}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({ movies: state.movies });
const mapDispatchToProps = {
fetchMoviesSync,
fetchMoviesAsyncMock
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
File api.js
export const FETCHED_MOVIES = "FETCHED_MOVIES";
export const fetchMoviesSync = () => ({
type: FETCHED_MOVIES,
payload: ["movie1", "movie2", "movie3", "movie4"]
});
export const fetchMoviesAsyncMock = () => (dispatch) => {
dispatch({
type: FETCHED_MOVIES,
payload: ["movie5", "movie6", "movie7", "movie8"]
});
};
File reducer.js
const initialState = {
movies: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case "FETCHED_MOVIES":
return {
...state,
movies: state.movies.concat(action.payload)
};
default:
return state;
}
};
File index.js
import React from "react";
import ReactDOM from "react-dom";
import Case from "./app";
import reducer from "./reducer";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
let store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<Case />
</Provider>,
document.getElementById("container")
);
File index.html
<body>
<div id="container"></div>
</body>
Hey I've looked at a bunch of help files now and cant seem to get the issue solved. Most suggestions are using a different setup than I have. The main issue that the others dont have is the LOGOUT feature. Can you suggest another way to handle the LOGOUT?
Here is my index.js for "combine reducers":
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import { LOGOUT_USER } from '../constants/index';
/* App Reducer Files */
import app from './app/reducer';
import accountData from './account/reducer';
import employeeData from './employee/reducer';
import locationData from './location/reducer';
import googleData from './google/reducer';
import requestData from './request/reducer';
import menuItemData from './menuItem/reducer';
import orderData from './order/reducer';
/* Public Reducer Files */
import valorData from './valor/reducer';
const appReducer = history => combineReducers({
router: connectRouter(history),
app,
accountData,
employeeData,
locationData,
googleData,
requestData,
menuItemData,
orderData,
// Public
valorData,
});
const rootReducer = history => (state, action) => {
if (action.type === LOGOUT_USER) {
state = undefined;
}
return appReducer(history, state, action);
};
export default rootReducer;
And my store.js:
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { routerMiddleware } from 'connected-react-router';
import rootReducer from '../services';
import history from '../history';
const debugware = [];
if (process.env.NODE_ENV !== 'production') {
debugware.push(createLogger({
collapsed: true,
}));
}
export default function configureStore(initialState) {
const store = createStore(
rootReducer(history),
initialState,
compose(
applyMiddleware(
routerMiddleware(history),
thunkMiddleware,
...debugware,
),
),
);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../services', () => {
const nextRootReducer = require('../services/index').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
This was working before I upgraded to a new router version. Again the main issue is the LOGOUT. If I just export appReducer it works just fine but doesnt logout.
WOW. The moment I post it I try one more thing...
const rootReducer = history => (state, action) => {
if (action.type === LOGOUT_USER) {
state = undefined;
}
return appReducer(history)(state, action);
};
Why are you returning an undefined state? Instead, return
if (action.type === LOGOUT_USER) {
return{ ...state, user: [] }
}
It basically returns and empty array as user.
So, I'm having an issue with an action returning the above mentioned error (See attached image), instead of updating redux state as expected. What am I overlooking here?
actionCreators.js
export function userToken(token) {
console.log('userToken has been fired');
return (dispatch) => {
dispatch({
type: 'Graphcool_Token',
payload: token
});
}
}
App.js
....
// Root Query
const allPostsCommentsQuery = graphql(All_Posts_Comments_Query, {
options: {
cachePolicy: 'offline-critical',
fetchPolicy: 'cache-first',
},
});
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
}
export default compose(
allPostsCommentsQuery,
connect(mapDispatchToProps)
)(Main);
Reducer
var tokenDetails = function(state, action) {
if (state === undefined) {
state = [];
}
switch (action.type) {
case 'Graphcool_Token':
const newState = [action.payload];
return newState;
default:
return state;
}
}
export default tokenDetails;
LoginUser.js
signinUser: function(emailID, passwordID) {
const email = emailID;
const password = passwordID;
this.props.client.mutate({
mutation: signinUser_Mutation,
variables: {
email,
password,
},
options: {
cachePolicy: 'offline-critical',
fetchPolicy: 'cache-first',
},
})
.then(this.updateStateLoginDetails)
.catch(this.handleSubmitError);
},
updateStateLoginDetails: function({data}) {
this.props.userToken(data.signinUser.token);
},
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, autoRehydrate} from 'redux-persist';
import { syncHistoryWithStore } from 'react-router-redux';
import { browserHistory } from 'react-router'
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
import client from './apolloClient';
import localForage from 'localforage';
const middlewares = [thunk, client.middleware()];
const enhancers = compose(
applyMiddleware(...middlewares),
(typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined' || process.env.NODE_ENV !== 'production') ? window.__REDUX_DEVTOOLS_EXTENSION__() : (f) => f,
autoRehydrate(),
);
const store = createStore(
rootReducer,
{}, // initial state
enhancers
);
// begin periodically persisting the store
persistStore(store, {storage: localForage});
export const history = syncHistoryWithStore(
browserHistory,
store
);
if(module.hot) {
module.hot.accept('./reducers/', () => {
const nextRootReducer = require('./reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}
export default store;
The first argument you should pass to connect is mapStateToProps, which is a function that receives the state and component props.. You should add null there if you don't need it:
connect(null, mapDispatchToProps)(Main)
BTW, generally speaking, you don't need bindActionCreators.. usually returning an object is enough, like:
const mapDispatchToProps = {
someActionName,
someOtherAction,
}