I'm getting a Typescript error that an object I'm using from Redux is possibly undefined, even though I don't say it's type can be undefined or set it to undefined anywhere.
/redux/globalSettings/actions.ts
import { ADD_GLOBAL_SETTINGS } from '../../config/actions';
import { AddGlobalSettingsAction } from './types';
import GlobalSettings from '../../typings/contentful/GlobalSettings';
export const addGlobalSettings = (payload: GlobalSettings): AddGlobalSettingsAction => ({
type: ADD_GLOBAL_SETTINGS,
payload,
});
/redux/globalSettings/reducers.ts
import { ADD_GLOBAL_SETTINGS } from '../../config/actions';
import { GlobalSettingsAction, GlobalSettingsState } from './types';
export default (
state: GlobalSettingsState,
action: GlobalSettingsAction,
): GlobalSettingsState => {
switch (action.type) {
case ADD_GLOBAL_SETTINGS:
return { ...action.payload };
default:
return state;
}
}
/redux/globalSettings/types.ts
import { ADD_GLOBAL_SETTINGS } from '../../config/actions';
import GlobalSettings from '../../typings/contentful/GlobalSettings';
export type GlobalSettingsState = GlobalSettings;
export interface AddGlobalSettingsAction {
payload: GlobalSettings;
type: typeof ADD_GLOBAL_SETTINGS;
}
export type GlobalSettingsAction = AddGlobalSettingsAction;
/redux/reducer.ts
import { combineReducers } from 'redux';
import globalSettings from './globalSettings/reducers';
const rootReducer = combineReducers({
globalSettings,
});
export type StoreState = ReturnType<typeof rootReducer>;
export default rootReducer;
/redux/index.ts
import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
import rootReducer, { StoreState } from './reducer';
export const initialiseStore = (
initialState: StoreState,
) => createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware()),
);
I use this in my NextJS project by using next-redux-wrapper NPM package (a React HOC) on the export of my _app.js page like so:
export default withRedux(initialiseStore)(Page);
I get an error in /redux/reducer.ts of: Type 'undefined' is not assignable to type 'GlobalSettings'.
If I use the globalSettings redux state on one of my pages, accessing globalSettings.fields.navigationLinks creates another Typescript error that globalSettings might be undefined.
Driving me crazy, what have I done wrong here?
The error
I get an error in /redux/reducer.ts of: Type 'undefined' is not assignable to type 'GlobalSettings'
relates to how you defined reducer
It should be
const initalState: GlobalSettingsState = {/* valid inital state */};
export default (
state: GlobalSettingsState | undefined = initalState,
action: GlobalSettingsAction,
): GlobalSettingsState => {
switch (action.type) {
case ADD_GLOBAL_SETTINGS:
return { ...action.payload };
default:
return state;
}
}
Reducer can be called with state set to undefined (to initialize state). So state argument should has undefined as possible value.
I had an issue similar to this when I added extraReducers to a pre-existing reducers dict (using redux-toolkit). All of my reducers previously fine had TypeScript errors around either
WritableDraft<WritableDraft<MySlice>>
and/or
Type 'undefined' is not assignable to type 'WritableDraft<MySlice>'
It turns out one of my reducers was returning undefined instead of state (redux-toolkit uses immer so I guess it may work despite return undefined).
Returning state fixed it.
Related
I am trying to write a React Redux component using Typescript following the documentation in Redux Getting Started and Proper Typing of react-redux Connected Components and am getting a type error on my connect function.
My app uses Redux to maintain a table of questions.
state.tsx
import {AnyAction, combineReducers} from "redux";
const ADD_QUESTION = "ADD_QUESTION";
export interface Question {
id: number,
text: string
}
export interface State {
questions: QuestionsTable
}
interface QuestionsTable {
nextId: number,
questions: Question[]
}
const questions = (state: State = {questions: {nextId: 0, questions: []}}, action: AnyAction): State => {
const questions = state.questions;
switch (action.type) {
case ADD_QUESTION:
return {
...state,
questions: {
...questions,
nextId: questions.nextId + 1,
questions: questions.questions.concat([{id: questions.nextId, text: action.text}])
}
};
default:
return questions
}
};
export const reducers = combineReducers({
questions
});
A Questions component displays them. I use connect to create QuestionsContainer from that.
questions.tsx
import React from "react"
import {Question, State} from "../state";
import {connect} from "react-redux";
export type QuestionsProps = {
questions: Question[]
}
const Questions: React.FC<QuestionsProps> = ({questions}) => {
return (
<div>
<ul>
{questions.map(question => (
<li>{question.text}</li>
))}
</ul>
</div>
)
};
const mapStateToProps = (state: QuestionsTable): QuestionsProps => {
return {questions: state.questions.questions};
};
export const QuestionsContainer = connect<QuestionsProps>(mapStateToProps)(Questions);
My top-level app displays this container component.
App.tsx
import React from "react";
import "./App.css";
import {reducers} from "./state";
import {createStore} from "redux"
import {Provider} from "react-redux"
import {QuestionsContainer} from "./components/questions";
const store = createStore(reducers);
const App: React.FC = () => {
return (
<Provider store={store}>
<div className="App">
<QuestionsContainer/>
</div>
</Provider>
);
};
export default App;
I get a type error in my call to connect.
Error:(61, 59) TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type '(state: State) => QuestionsProps' is not assignable to parameter of type 'MapStateToPropsParam<QuestionsProps, {}, {}>'.
Type '(state: State) => QuestionsProps' is not assignable to type 'MapStateToPropsFactory<QuestionsProps, {}, {}>'.
Types of parameters 'state' and 'initialState' are incompatible.
Property 'questions' is missing in type '{}' but required in type 'State'.
If I suppress this error with #ts-ignore and log the value of questions passed to my Questions component I see this.
{"nextId":0,"questions":[]}
I can't figure out why the nextId field is there even though mapStateToProps dereferences state.questions.questions.
What is the correct way to set this up?
Okay
I will try it from phone
Sorry for formatting
import {AnyAction, combineReducers} from "redux";
const ADD_QUESTION = "ADD_QUESTION";
export interface Question {
id: number,
text: string
}
interface State {
nextId: number,
items: Question[]
}
const initialState: State = {nextId: 0, items: []}
const questions = (state = initialState, action: AnyAction): State => {
switch (action.type) {
case ADD_QUESTION:
return {
...state,
items: action.items,
nextId: action.nextId
};
default:
return state
}
};
export const reducers = combineReducers({
questions
});
That's how I see your reducer
Then in component in mapStateToProps
questions: state.questions.items
I am trying to set up Redux in React for the first time and I can't seem to pass my initial state from the store to the component. My store file is setting state to the return value of the reducer. Here is what happens when I log this.props to the console
Component
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { exampleAction } from '../../actions';
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
console.log(this.props)
return (
<div>
<p>this is {this.props.examplePropOne}</p>
</div>
);
}
}
const mapStateToProps = state => ({
examplePropOne: state.examplePropOne,
examplePropTwo: state.examplePropTwo
});
const mapDispatchToProps = dispatch => {
return bindActionCreators({ exampleAction }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
Reducer
import { EXAMPLE_ACTION } from './../actions/types'
const initialState = {
examplePropOne : 'Example Property One',
examplePropTwo : 'Example Property Two'
}
export default function (state = initialState, action) {
switch(action.type) {
case EXAMPLE_ACTION:
return {
...state,
examplePropOne: action.payload
}
default:
return state
}
}
Action
import { EXAMPLE_ACTION } from './types'
export const exampleAction = text => ({
type: EXAMPLE_ACTION,
payload: text,
})
[Edit]
Here is what happens when I log the state within mapStateToProps
import React from 'react';
import { createStore, combineReducers } from 'redux';
import reducers from '../reducers';
export const store = createStore(
combineReducers({
state: reducers
}),
);
With how combineReducers() was used with state passed in as a key, your mapStateToProps() would need to look like this instead to access examplePropOne and examplePropTwo:
const mapStateToProps = state => ({
examplePropOne: state.state.examplePropOne,
examplePropTwo: state.state.examplePropTwo
});
Given that combineReducers():
The state produced by combineReducers() namespaces the states of each
reducer under their keys as passed to combineReducers()
The issue is that:
export const store = createStore(
combineReducers({
state: reducers
}),
);
The key state passed to combineReducers() created a namespace/property of state. With the argument named state for the mapStateToProps(), requires that properties are accessed as state.state. This can probably be resolved by instead giving the key passed to combineReducers() a more descriptive name representing what is being used to manage in the store. For example, if it's related to authentication, it could be called some like auth. It would look like:
export const store = createStore(
combineReducers({
auth: reducers
}),
);
// ...
const mapStateToProps = state => ({
examplePropOne: state.auth.examplePropOne,
examplePropTwo: state.auth.examplePropTwo
});
Hopefully that helps!
As part of my ongoing project to learn React (I'm natively an ASP.NET guy) I've hit this issue. I have a suite of React apps in which I want to use some common UI elements, so I've attempted to break these out into a separate npm package. For the shared components themselves this has worked fine.
However, some of these components depend on redux actions to operate, so I've tried to bundle these actions and a reducer function into the external package. Here's a simplified version of my actions\index.js:
export const SNACKBAR_MESSAGE = "SNACKBAR_MESSAGE";
export const SNACKBAR_HIDE = "SNACKBAR_HIDE";
export function showSnackBarMessage(message) {
console.log('hit 1');
return (dispatch, getState) => {
console.log('hit 2');
dispatch(hideSnackBar());
dispatch({
type: SNACKBAR_MESSAGE,
message: message
});
}
}
export const hideSnackBar = () => {
type: SNACKBAR_HIDE
};
And this is reducer\index.js:
import {
SNACKBAR_MESSAGE,
SNACKBAR_HIDE
} from "../actions";
const initialState = {
snackBarMessage: null,
snackBarVisible: false
};
export default function UiReducer(state = initialState, action) {
switch(action.type) {
case SNACKBAR_MESSAGE:
return Object.assign({}, state, {
snackBarMessage: action.message,
snackBarVisible: true
});
case SNACKBAR_HIDE:
return Object.assign({}, state, {
snackBarMessages: '',
snackBarVisible: false
});
default:
return state;
}
}
This is the same code that worked fine when part of the original project. These are exported by my package's entry point file like this:
// Reducer
export { default as uiReducer } from './reducer';
// Actions
export { showSnackBarMessage as uiShowPrompt } from './actions';
export { hideSnackBar as uiHidePrompt } from './actions';
Then in my consuming project, my default reducer looks like this:
import { routerReducer } from 'react-router-redux';
import { combineReducers } from 'redux';
import { uiReducer } from 'my-custom-ui-package';
// Import local reducers
const reducer = combineReducers(
{
// Some local reducers
ui: uiReducer
}
);
export default reducer;
The problem is when I try to dispatch one of these actions imported from my external package. I include the action, e.g. import { uiShowPrompt } from "my-custom-ui-package"; and dispatch it like dispatch(uiShowPrompt("Show me snackbar")); then I see the two console messages (hit 1 and hit 2) displayed, but then the following error:
Uncaught TypeError: Cannot read property 'type' of undefined
at store.js:12
at dispatch (applyMiddleware.js:35)
at my-custom-ui-package.js:1
at index.js:8
at middleware.js:22
at store.js:15
at dispatch (applyMiddleware.js:35)
at auth.js:28
at index.js:8
at middleware.js:22
The store itself looks like this:
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk';
import { browserHistory } from "react-router";
import {
syncHistoryWithStore,
routerReducer,
routerMiddleware
} from "react-router-redux";
import reducer from "./reducer";
const loggerMiddleware = store => next => action => {
console.log("Action type:", action.type);
console.log("Action payload:", action.payload);
console.log("State before:", store.getState());
next(action);
console.log("State after:", store.getState());
};
const initialState = {};
const createStoreWithMiddleware = compose(
applyMiddleware(
loggerMiddleware,
routerMiddleware(browserHistory),
thunk)
)(createStore);
const store = createStoreWithMiddleware(reducer, initialState);
export default store;
I'm afraid I don't understand this error. I don't see what I'm doing differently other than essentially moving identical code from my local project to an npm package. Since neither the actions nor reducer actually depend on redux, my npm package doesn't itself have a dependency on react-redux. Is that a problem? If there's anything else I could share to help you help me just let me know. Like I say, I'm still fairly new to all this so clearly there's something I'm not getting right!
The problem might be in declaration of hideSnackBar function
export const hideSnackBar = () => {
type: SNACKBAR_HIDE
};
Here the function is trying to return an Object Literal from Arrow Function. This will always return undefined. As the parser doesn't interpret the two braces as an object literal, but as a block statement. Thus the error, Cannot read property 'type' of undefined as store is expecting an action with property type.
Replace code like this and see if it works.
export const hideSnackBar = () => ({
type: SNACKBAR_HIDE
});
The parentheses forces it to parse as Object Literal. Hope this helps
I had exported it like
export default userReducer();
and not like this:
export default userReducer;
Just get rid of that ()
Found out that it was case of wrong order in receiving the arguments when using redux-thunk.
// wrong argument order
const anAction = () => (getState, dispatch) => {...}
// correct one
const anAction = () => (dispatch, getState) => {...}
I am trying to initialise a basic store from a root reducer with initial state.
My root reducer
import Entity from "../api/Entity";
import { UPDATE_GROUPING } from "../constants/action-types";
import IAction from "../interfaces/IAction";
import IStoreState from "../interfaces/IStoreState";
const initialState:IStoreState = {
callsInProgress: false,
groupingOptions: ["Strategy", "User", "Date"],
groupingType: "Strategy",
numberOfCalls: 2,
items: [new Entity()],
};
const rootReducer = (state = initialState, action: IAction<object>) => {
switch (action.type) {
case UPDATE_GROUPING:
return { ...state, groupingType: action.payload};
default:
return state;
}
};
export default rootReducer;
When I create the store with the rootreducer as below
import { createStore } from 'redux';
import rootreducer from '../reducers/rootreducer';
const store = createStore(rootreducer);
export default store;
It works. The React components get initialised with the correct state for groupingType, groupingOptions etc.
However if I try and use a combineReducers() approach - even with just this single root reducer (should be identical) then when my components load, they do not have any initial state passed.
ie
import { createStore } from 'redux';
import reducers from '../reducers';
const store = createStore(reducers);
export default store;
My index.ts in the reducers folder which returns a combineReducers() call (the one which doesnt work)
import {combineReducers} from 'redux';
import rootreducer from './rootreducer';
// main reducers
export default combineReducers({
rootreducer
});
And lastly my component which hooks into redux and should import the state from the redux store
import updateGroupingType from "./actions/uiactions";
import './App.css';
import * as React from 'react';
import { connect } from 'react-redux';
import IStoreState from './interfaces/IStoreState';
interface IGroupingProps {
groupingOptions? : string[],
groupingType? : string,
updateGroupingAction? : any
}
class GroupingSelector extends React.Component<IGroupingProps, {}> {
constructor(props: IGroupingProps) {
super(props);
this.onGroupingChange = this.onGroupingChange.bind(this);
}
public render() {
if (this.props.groupingOptions == null)
{
return null;
}
return (
<div className="Grouping-selector">
<div className="Horizontal-panel-right Grouping-search-combo">
<select onChange={this.onGroupingChange}>
{this.props.groupingOptions.map((name, index)=>
<option key={index}>{name}</option>
)}
</select>
</div>
<div className="Content Horizontal-panel-right">
Group by
</div>
</div>);
}
private onGroupingChange(e: any) {
const { value } = e.target;
this.props.updateGroupingAction(value);
}
}
const mapStateToProps:any = (state: IStoreState) => {
return {
groupingOptions: state.groupingOptions,
groupingType: state.groupingType,
};
}
const mapDispatchToProps = (dispatch:any) => {
return {
updateGroupingAction: (groupingType:string) => dispatch(updateGroupingType(groupingType))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(GroupingSelector);
Why is my usage of combineReducers not working in the same way as when I use the single rootreducer?
From the doc
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.
The resulting reducer calls every child reducer, and gathers their results into a single state object.
The state produced by combineReducers() namespaces the states of each reducer under their keys as passed to combineReducers()
When you are using rootReducer as a key inside of your combineReducers, it will create a state which shape will be
{ "rootReducer": YOUR_PREVIOUS_STATE}
You should use combineReducers only if you have different reducers for each key
Your root reducer should be key value pairs like,
export default combineReducers({
home:homeReducer
});
So that in your component, mapStateToProps() you will be able to access these values as,
const mapStateToProps = (state: any) => {
return {
users: state.home
};
};
Can you help me with exception Unexpected key "userName" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: "default". Unexpected keys will be ignored.
I discovered this Link but it doesn't help me. I don't undestand something, maybe this part from documentation: plain object with the same shape as the keys passed to it
Can you exlain me my mistake on my example?
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import App from './containers/App.jsx';
import * as reducers from './reducers'
import types from './constants/actions';
const reducer = combineReducers(reducers);
const destination = document.querySelector("#container");
var store = createStore(reducer, {
userName : ''
});
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
destination
);
console.log(1)
store.subscribe( ()=> {
console.log("-------------------------")
let s = store.getState()
console.log("STATE = " + s)
for (var k in s) {
if (s.hasOwnProperty(k)) {
console.log("k = " + k + "; value = " + s[k]);
}
}
})
store.dispatch({
type: types.LOAD_USER_NAME,
userName : "Oppps1"
})
my reducer:
import types from './../constants/actions'
export default function userNameReducer (state = {userName : 'N/A'}, action) {
console.log('inside reducer');
switch (action.type) {
case types.LOAD_USER_NAME:
console.log("!!!!!!!!!!!!!!!")
console.log("action.userName = " + action.userName)
for (var k in state) {
if (state.hasOwnProperty(k)) {
console.log("k = " + k + "; value = " + state[k]);
}
}
return action.userName;
default:
return state
}
}
result in console after execution:
TLDR: stop using combineReducers and pass your reducer to createStore directly. Use import reducer from './foo' instead of import * from './foo'.
Example with default import/export, no combineReducers:
// foo.js
function reducer(state, action) { return state; }
export default reducer;
----
// index.js
import myReducer from './foo';
Example with combineReducers
// foo.js
export default (state, action) => { ... }
----
// bar.js
export default (state, action) => { ... }
----
// index.js
import foo from './foo';
import bar from './bar';
const store = createStore(combineReducers({
foo,
bar,
});
The second argument of createStore (preloaded state) must have the same object structure as your combined reducers. combineReducers takes an object, and applies each reducer that is provided in the object to the corresponding state property. Now you are exporting your reducer using export default, which is transpiled to something like module.exports.default = yourReducer. When you import the reducer, you get module.exports, which is equal to {default: yourReducer}. Your preloaded state doesn't have a default property thus redux complains. If you use import reducer from './blabla' you get module.exports.default instead which is equal to your reducer.
Here's more info on how ES6 module system works from MDN.
Reading combineReducers docs from redux may also help.
Or just:
import yesReducer from './yesReducer';
const store = createStore(combineReducers({
yesReducer,
noReducer: (state = {}) => state,
});
import * as reducers from './reducers'
is a good technique to avoid having your core code depend on the various functionalities implementations.
In order to make it work, you have to specify the name of the reducer to be the same as your store key userName. This can be achieved this way:
In the index.js that sits in the reducers folder, do
export { userNameReducer as userName } from "./UserNameReducer"
export { otherReducer as other } from "./OtherReducer"
An other way would be to directly rename the exported reducer the same as the store key.