I can not understand how to deal with new typings of Redux.
Right now i am trying to complete this tutorial https://maxfarseer.gitbooks.io/redux-course-ru/content/index.html
At this moment I am on "Creating Store" phase with this simple code below:
store/configureStore.ts
import {createStore, Reducer} from 'redux';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState);
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer=require('../reducers');
store.replaceReducer(nextRootReducer);
})
}
return store;
}
When I try to compile it messages me about this error:
ERROR in ./src/store/configureStore.ts
(10,34): error TS2345: Argument of type '{}' is not assignable to parameter of type '<A extends Action>(state: any, action: A) => any'.
Type '{}' provides no match for the signature '<A extends Action>(state: any, action: A): any'
This is my index file of reducers
import {combineReducers} from "redux/index";
import page from './page';
import user from './user';
const rootReducer = combineReducers({
page,
user
});
export default rootReducer;
//page.ts
const initialState = {
year: 2016,
photos: []
};
function page(state = initialState,
action = {type: '', payload: null}) {
return state
}
export default page;
//user.ts
const initialState = {
name: 'Аноним'
};
export default function user(state = initialState,
action = {type: '', payload: null}) {
return state
}
Related
I'm new in Redux and I've got some issues learning it.
If I call a selector before a dispatch it becomes in an infinite loop, but if I call the selector after the dispatch action it works fine. I've followed documentations steps and look through stack-overflow and couldn't find some issue similar.
I let the code below:
selector:
import { createSelector } from '#reduxjs/toolkit';
import { RootState } from '#/store';
export const selectCount = (state: RootState) => state.learningLanguages;
export const countSelector = createSelector(selectCount, state => state);
action:
import {createAction} from "#reduxjs/toolkit"
import {LearningLanguagesType} from "#/globals";
export const storeLearningLanguages = createAction<LearningLanguagesType[]>('languages/store')
reducer
import {
createReducer, createSlice, PayloadAction,
} from '#reduxjs/toolkit';
import { LearningLanguagesType } from "#/globals";
import {storeLearningLanguages} from "#/store";
const initialState: LearningLanguagesType[] = [{
id: 'en',
name: 'english',
level: '0'
}];
export const learningLanguagesReducer = createReducer(initialState, builder => {
builder
.addCase(storeLearningLanguages, (state, action) => {
return action.payload;
});
});
hooks
import {
TypedUseSelectorHook,
useDispatch,
useSelector,
} from 'react-redux';
import type {
AppDispatch,
RootState,
} from '.';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
store
import {
Action,
configureStore,
ThunkAction,
} from '#reduxjs/toolkit';
import {learningLanguagesReducer} from './app/languages/reducer'
export const index = configureStore({
reducer: {
learningLanguages : learningLanguagesReducer
},
});
export type AppDispatch = typeof index.dispatch;
export type RootState = ReturnType<typeof index.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
execution
import {
useAppDispatch,
useAppSelector,
} from '#/store/hooks'
import {LearningLanguagesType} from "#/globals";
const Home = () => {
const dispatch = useAppDispatch();
const testLang: LearningLanguagesType[] = [
{
id: 'es',
name: 'Spain',
level: '0'
},
{
id: 'en',
name: 'English',
level: '0'
}
];
console.log('before',useAppSelector(countSelector))
console.log('dispatch', (dispatch(storeLearningLanguages(testLang))))
console.log('after',useAppSelector(countSelector))
}
This way works perfectly, and it doesn't matter how many times I call the selector after the dispatch:
//console.log('before',useAppSelector(countSelector))
console.log('dispatch', (dispatch(storeLearningLanguages(testLang))))
console.log('after',useAppSelector(countSelector))
Thanks anyway.
export const rootReducer = combineReducers({
login: loginReducer,
});
This works fine but as soon as I try to combine another reducer,
export const rootReducer = combineReducers({
login: loginReducer,
logout: logoutReducer
});
I start getting an error on rootReducer that
'rootReducer' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
How could I modify this?
This is how my logoutReducer looks like:
import {store} from '../index'
export const logoutReducer = (state = store.getState(), { type, payload } :any) => {
switch (type) {
case "logout":
return {...state, token: payload};
default:
return state;
}
};
Have you tried this with assigning type to your underlying reducers?
For example:
import {Action, Reducer} from 'redux';
interface LoginState {
isLoggedIn: boolean;
token: string;
}
interface LogoutState {
token:string;
}
export const logOutReducer: Reducer<LogoutState> = (state: LogoutState | undefined, incomingAction: Action): LogoutState=> {
switch (incomingAction.type) {
case "logout":
return {...state, token: payload};
default:
return state;
}
}
//... export const logInReducer: Reducer<LoginState>...
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 using the React Hooks implementation of React-Redux. Below is the flow of my code. For some reason any values that I pass in my dispatch(fuction(value)) is not detected in my reducer. I can't figure it out.
src/components/categories/selectCategory.tsx
import React from 'react';
import {useDispatch} from 'react-redux';
import {setCategory} from '../../store/actions';
const selectCategory = (name: string) => {
dispatch(setCategory(name)); // ex: name = 'algebra'
};
store/actions/index.ts
export const setCategory = (name) => ({
type: 'SET_CATEGORY',
name: name
});
store/reducers/index.ts
import {combineReducers} from 'redux';
import category from './category';
const app = combineReducers({
category
});
export default app;
store/reducers/category.ts
const initialState = {
name: 'calculus'
};
const category = (state = initialState, action) => {
switch (action.type) {
case 'SET_CATEGORY':
return {name: state.name}; // outputs 'calculus'
default:
return state;
}
};
export default category;
I'm sure there is some small detail I am missing.
My issue was fixed by returning the action.name property instead of state.name.
store/reducers/category.ts
const initialState = {
name: 'calculus'
};
const category = (state = initialState, action) => {
switch (action.type) {
case 'SET_CATEGORY':
return {name: action.name};
default:
return state;
}
};
export default category;
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.