I did use combineReducers() and the initialState is defined in each reducer separately.
The issue is:
-> the state seem to be always undefined, even within the reducer, I tried to console.log({...state}) and I get an empty object.
When I replace state with initialState in each return{} then I get the initialState data.
I tried in the clientsReducer function the following:
1- state = {...initialState} => gives an empty object
2- remove the initialState from the args and do the assignment inside the function. => gives undefined
any idea why?
const initialState = {
clientsArray: [],
shouldFetchClients: false,
isFetchingClients: false,
fetchClientsError: null,
};
export default function clientsReducer(state = initialState, action) {
switch (action.type) {
case FETCH_CLIENTS_SUCCESS:
return {
...state,
clientsArray: action.payload,
isFetchingClients: false,
};
case FETCH_CLIENTS_FAILURE:
return {
...state,
fetchClientsError: action.payload,
isFetchingClients: false,
};
case FETCH_CLIENTS:
return {
...state,
isFetchingClients: true,
shouldFetchClients: true,
clientsArray: null,
};
default:
return state;
}
}
This is a reducer's code, and I am using combineReducers function to define the RootReducer :
import { combineReducers } from 'redux';
//REDUCER FILES IMPORTS HERE
export default combineReducers({
clients: clientsReducer,
admins: adminsReducer
})
then, in createStore I have:
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { persistReducer, persistStore } from 'redux-persist';
import createSagaMiddleware from 'redux-saga';
import storage from 'redux-persist/lib/storage';
import createFilter from 'redux-persist-transform-filter';
// CONNECTED-REACT-ROUTER
import { createBrowserHistory } from 'history';
import { routerMiddleware, connectRouter } from 'connected-react-router';
import rootReducer from './rootReducer';
export const history = createBrowserHistory();
const sagaMiddleware = createSagaMiddleware();
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(
connectRouter(history)(persistedReducer),
composeEnhancer(
applyMiddleware(
routerMiddleware(history),
sagaMiddleware,
thunk,
createLogService(true),
),
),
);
Related
I'm building a game using react-redux and TypeScript. I've got TS and React down, but redux... less so.
When the player clicks the start game button, the main menu is hidden via manipulation of redux state and the "gameState" is generated. "gameState" contains all the relevant information for the game world and entities therein, and is several thousand lines of serializable JSON. This part is definitely working, the problem comes when I try to dispatch to update it. I can see from the Redux browser extension that the payload being sent to the reducer function updateGameState is correct, but after the dispatch has been completed it's as if it never happened.
My question is simple: what am I doing wrong?
The code for the previously-mentioned dispatch is:
let nGS = gameStateGenerator.create(scene)
dispatch(updateGameState(nGS))
The layout of this part of the redux logic is as shown below. The four children of multiverse, universes, species, connections, and players, should all be populated, but are not.
I'm using combined reducers, as follows. I've not used ES6 notation for the reducer object properties as part of my attempts to rule out causes (which hopefully speaks to my level of desperation).
store.ts (top level)
import { configureStore, ThunkAction, Action } from '#reduxjs/toolkit';
import gameState from '../features/gameState/gameState';
export const store = configureStore({
reducer: {
gameState: gameState
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
gameState.ts (first and so far only child of root)
import { combineReducers, createSlice, PayloadAction } from '#reduxjs/toolkit';
import { GameState } from '../../interfaces/GameState';
import flags, { initialState as flagsInitialState} from '../flags/flags';
import multiverse, { initialState as multiverseInitialState } from '../multiverse/multiverse';
export const initialState: GameState = {
multiverse: multiverseInitialState,
flags: flagsInitialState
};
export const gameState = createSlice({
name: 'gameState',
initialState,
reducers: {
updateGameState: (state: GameState, action: PayloadAction<GameState>) => {
return Object.assign({}, state, action.payload)
}
}
});
export const { updateGameState } = gameState.actions;
export default combineReducers({
flags: flags,
multiverse: multiverse
})
flags.ts
import { createSlice } from '#reduxjs/toolkit';
import { FlagsState } from '../../interfaces/Flags';
export const initialState: FlagsState = {
ui: {
showMainMenu: true,
showWelcome: true,
gameStarted: false,
gameLoading: false,
gameLoaded: false
}
};
export const flags = createSlice({
name: 'flags',
initialState,
reducers: {
startGame: (state: Required<FlagsState>) => {
return Object.assign({}, state, {
ui: {
...state.ui,
showMainMenu: false,
gameStarted: true,
gameLoading: true
}
})
},
gameLoaded: (state: Required<FlagsState>) => {
return Object.assign({}, state, {
ui: {
...state.ui,
gameLoaded: true
}
})
}
}
});
export const { startGame, gameLoaded } = flags.actions;
export const getState = (state: FlagsState) => state;
export default flags.reducer;
And finally multiverse.ts
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { Multiverse } from '../../interfaces/Multiverse';
import { Universe } from './../../interfaces/Universes';
export const initialState: Multiverse = {
universes: [],
species: [],
connections: [],
players: []
};
export const multiverse = createSlice({
name: 'multiverse',
initialState,
reducers: {
setUniverses: (state: Required<Multiverse>, action: PayloadAction<Universe[]>) => {
return Object.assign({}, state, { universes: action.payload })
}
}
});
export const { setUniverses } = multiverse.actions;
export const getState = (state: Multiverse) => state;
export default multiverse.reducer;
I'm think the trouble comes from your reducer, you use Object assign and return.
Redux-Toolkit uses Immer to change the state with no mutating like this :
(state, action) => state.value = action.payload
See the doc, https://redux-toolkit.js.org/usage/immer-reducers#immutable-updates-with-immer
So for you, you can do something like :
state.univers = {...state.univers, ... action.payload}
with no return.
I am trying to get redux working in my react-native app. Basically, I have a signIn action defined in my authActions.js file:
const signInAction = () => {
return {
type: 'signIn',
};
};
export { signInAction };
Then I have an authReducer defined as this in authReducer.js:
const initialState = {
isAuthenticated: false,
}
const authReducer = (state = initialState, action) => {
switch(action.type) {
case "signIn":
return Object.assign({}, state, {
isAuthenticated: true,
})
default: return state;
}
};
export default authReducer;
I combine that reducer in my rootReducer.js file
import { combineReducers } from 'redux';
import auth from 'app/src/redux/reducers/authReducer.js';
const rootReducer = combineReducers({
auth,
});
export default rootReducer;
and then created a store in reduxIndex.js:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from 'app/src/redux/reducers/rootReducer.js';
let store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
export default store;
I wrapped my app in a <Provider> component, and that seems to be working fine (I can read from the state and see the value of isAuthenticated. However, when I try to dispatch an action using mapDispatchToProps in one of my views the function is undefined:
// More imports
// ...
import { connect } from 'react-redux';
import { signInAction } from 'app/src/redux/actions/authActions.js';
const mapStateToProps = (state) => {
return {};
}
const mapDispatchToProps = (dispatch) => {
return {
onSignIn: () => { dispatch(signInAction) },
};
}
class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
}
}
onSignInPress() {
// ******* this is where the error occurrs ****
this.props.onSignIn();
}
render() {
const {navigation} = this.props;
return (
<View style={SignInStyles.container}>
<ScrollView>
<View>
<Button
large
title="SIGN IN"
backgroundColor={colors.primary}
onPress={this.onSignInPress}
/>
</View>
</ScrollView>
</View>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
I cant really see where I am going wrong, but im sure its a simple mistake somewhere. The specific error I get is :
"undefined is not an object. Evaluating this.props.onSignIn"
The onSignInPress callback isn't bound to any particular object, so when it gets called this is undefined.
The easy way to fix it is to use arrow syntax to make it always be bound. In your class definition:
onSignInPress = () => {
this.props.onSignIn();
}
Google found me this Medium article from Miron Machnicki which explains the differences and possible alternative syntaxes in pretty good detail.
No matter what I do, I can't get rid of the mistake. I have often rewritten the actions but the error remains. I also wrote thunk at the top of the createstore. It would be great if you could support me a little bit.
My action, nothing special here only a fetch call to get my players
import fetch from "cross-fetch"
export const SET_PLAYERS = "setplayers"
export const setPlayers = players => {
return{
type: "setplayers",
players
}
}
export const fetchPlayers = () => (dispatch, getState) => {
return fetch("http://localhost:4444/api/players")
.then(response => response.json())
.then(players => {
dispatch(setPlayers(players))
}).catch(err => {
console.log("Could not fetch assortments" , err)
})
}
Component, at this point in time only a dummy to invoke the action:
import React from "react"
import PropTypes from "prop-types"
import { fetchPlayers } from "./action"
import { connect } from "react-redux"
import EnhancedTable from "../components/list/List"
import getPlayers from "./reducer"
class PlayerTable extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
componentDidMount(){
this.props.fetchPlayers()
}
render() {
console.log("#######", this.props.players)
return (
<EnhancedTable />
)
}
}
PlayerTable.propTypes = {
classes: PropTypes.object.isRequired,
}
const mapStateToProps = state => ({
players: getPlayers(state)
})
export default connect(mapStateToProps, { fetchPlayers })(PlayerTable)
Reducer
import { SET_PLAYERS } from "./action"
const setPlayers = (state={}, action) => {
console.log("ACTION", action)
switch (action.type) {
case SET_PLAYERS:
return {...state, players: action.players}
default:
return state
}
}
export default setPlayers
export const getPlayers = state => ([])
CombinedReducers
import { combineReducers } from "redux"
import { reducer as formReducer } from "redux-form"
import showProgressbar from "../components/progressbar/reducer"
import showSnackBar from "../components/snackbar/reducer"
import setPlayers from "../player/reducer"
export default combineReducers({
form: formReducer,
showProgressbar,
showSnackBar,
setPlayers
})
CreateStore
import App from "./App"
import React from "react"
import rootReducer from "./reducers"
import thunk from "redux-thunk"
import { render } from "react-dom"
import { createStore, applyMiddleware, compose } from "redux"
import { Provider } from "react-redux"
import { createLogger } from "redux-logger"
const store = createStore(
rootReducer,
compose(applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
))
render(
<Provider store={store}>
<App />
</Provider>,
/* eslint-disable*/
document.getElementById("root")
/* eslint-enable */
)
You've defined mapStateToProps properly, but don't you need to do the same with mapDispatchToProps for the second argument to connect()?
const mapStateToProps = state => ({
players: getPlayers(state)
})
const mapDispatchToProps = dispatch => ({
fetchPlayers() {
dispatch(fetchPlayers())
}
})
export default connect(mapStateToProps, mapDispatchToProps)(PlayerTable)
I am trying to reset my storage when I log out but it doesn't seem to work at all.
As you can see I am using AsyncStorage for my store and I try to follow the answer from this post.
Here is my index.js from store folder
import thunk from 'redux-thunk';
import { createStore, compose, applyMiddleware } from 'redux';
import { AsyncStorage } from 'react-native';
import { persistStore, autoRehydrate } from 'redux-persist';
import rootReducer from '../reducers';
var defaultState = {};
export function configureStore(initialState = defaultState) {
var store = createStore(rootReducer, initialState, compose(
applyMiddleware(thunk),
autoRehydrate(),
));
persistStore(store, { storage: AsyncStorage });
return store;
}
and here is my index.js from reducers folder
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import { AsyncStorage } from 'react-native';
import authReducer from './authReducer';
import alertsReducer from './alertsReducer';
import jobsReducer from './jobsReducer';
import userDataReducer from './userDataReducer';
const appReducer = combineReducers({
form: formReducer,
auth: authReducer,
alerts: alertsReducer,
jobs: jobsReducer,
userData: userDataReducer
})
const rootReducer = ( state, action ) => {
if(action.type === 'UNAUTH_USER') {
Object.keys(state).forEach(key => {
AsyncStorage.removeItem(`persist:${key}`);
console.log(state)
});
}
return appReducer(state, action)
}
export default rootReducer
On Initial load of our application the reducer state is fresh.
We Can copy this initial default state and use it to assign to our reducer again on logging out, the way we can achieve this could be as follows.
Step 1: call an action on the application load that will copy reducer's initial state as the defaultState
Step 2: While logging out of the application we can simply reAssign the default state and it should work as new.
App root Component
componentDidMount() {
dispatch(ON_APP_LOAD)
}
App Reducer
const appReducer = combineReducers({
user: userStatusReducer,
analysis: analysisReducer,
incentives: incentivesReducer
});
let defaultState = null;
export default (state, action) => {
switch (action.type) {
case **ON_APP_LOAD**:
// will be assigned or called only once
defaultState = defaultState || state;
break;
case **RESET_STATE**:
// detaching the reference on reset
state = _.deepClone(defaultState);
return state;
default:
break;
}
return appReducer(state, action);
};
On Logout calling the action for resetting state
function* logoutUser(action) {
// on logout success
dispatch("RESET_STATE")
}
I assume you have the js file where all reducers are combined in one and thus you have:
const allReducers = combineReducers({
reducer: nameOfReducer
});
const rootReducer = (state, action) => {
switch (action.type) {
case CLEAR_ALL_REDUCERS_DATA:
return state = undefined;
default:
return allReducers(state, action)
}
};
export default rootReducer;
and in index file where you create a store you need to define
const store = createStore(rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
and then simply call it on logout:
dispatch(clearAllStoreData());
where clearAllStoreData is an action defined in your action file:
export const clearAllStoreData = () => {
return {
type: CLEAR_ALL_REDUCERS_DATA
}
};
I add redux to my react application, and my case is:
I have a component has tabs tow categories called: (topics, files) each one has tree list of data, I need to search inside the activated tab, I found redux-search library to make searchable resources.
I will add a snippet of my code, please make a review to my redux code and redux-search package implementation.
TopicsReducer:
export default function reducer(state = {
topics: [], // should be searchble state.
files: [], // should be searchble state.
fetching: false,
fetched: false,
error: null
}, action) {
switch (action.type) {
case "FETCH_TOPICS_PENDING": {
return {...state, fetching: true};
}
case "FETCH_TOPICS_REJECTED": {
return {...state, fetching: false, error: action.payload}
}
case "FETCH_TOPICS_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
topics: action.payload
}
}
case "FETCH_FILES_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
files: action.payload
}
}
}
return state;
}
store.js
import {applyMiddleware, compose, createStore} from "redux";
import {reduxSearch} from 'redux-search'
import reducer from './reducers';
import thunk from "redux-thunk";
import logger from 'redux-logger'
const middleware = applyMiddleware(thunk, logger);
const redux_search = reduxSearch({
resourceIndexes: {
topics: ['title', 'description'] // HERE should i tell reduxSearch i have topics and files
},
resourceSelector: (resourceName, state) => {
return state.topics[resourceName]
}
});
export default createStore(reducer, compose(middleware, redux_search));
In the component:
import React from "react";
import {Tab, TabList, TabPanel, Tabs} from "react-tabs";
import ReactDOM from "react-dom";
import {Provider} from 'react-redux';
import store from '../store';
import TreeNode from "./partials/TreeView/TreeNode";
import Search from "./partials/search";
import {connect} from 'react-redux'
import {fetchTopics} from "../actions/topicsActions";
import {createSelector} from 'reselect'
import {createSearchAction, getSearchSelectors} from 'redux-search'
const topics = state => state.topics; // NOTE: i was using getIn()like in repo but show me error in console getIn is not a function for this i don't used.
const {
text,
result
} = getSearchSelectors({
resourceName: 'topics',
resourceSelector: (resourceName, state) =>
state.topics[resourceName]
});
const selectors = createSelector(
[result, topics, text],
(topicId, topics, searchText) => ({
topicId,
topics,
searchText
})
);
const actions = {
searchTopics: createSearchAction('topics')
};
#connect(selectors, actions)
class Topics extends React.Component { ....
And inside the component, I use a function to despatching data when changing the tab.
// index: tab index.
fetchData(index = this.state.tabIndex) {
this.props.dispatch(fetchTopics(index))
}
console error :
who fix this error?
is this implementation work with me to searching inside topics and files?
dispatch is only injected to the wrapped component if mapDispatchToProps is not provided.
From the docs:
If you do not supply your own mapDispatchToProps function or object full of action creators, the default mapDispatchToProps implementation just injects dispatch into your component’s props.
If you want to include dispatch in addition to your actions, try:
const actions = (dispatch) => ({
...bindActionCreators({ searchTopics: createSearchAction('topics') }, dispatch),
dispatch
})