Redux combineReducers with multiple files - making simple mistake - ecmascript-6

I'm trying to set up a pattern for combining reducers from multiple files as per #gaearon's answer (https://github.com/reduxjs/redux/issues/609#issuecomment-133903294), but am making a simple mistake, and am unable to figure out what it is. Having some brain block on this one... :\
Getting the following error:
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
My Code:
containers/score/reducers.js
export const goals = (state = 0, action) =>
action.type === types.UPDATE_GOALS ? action.payload : state
export const points = (state = 0, action) =>
action.type === types.UPDATE_POINTS ? action.payload : state
containers/time/reducers.js
export const minutes = (state = 0, action) =>
action.type === types.UPDATE_MINUTES ? action.payload : state
export const seconds = (state = 0, action) =>
action.type === types.UPDATE_SECONDS ? action.payload : state
containers/index.js
import * as score from './score'
import * as time from './time'
export default Object.assign({}, score, time)
store/configureStore.js
import { createStore, combineReducers } from 'redux'
import reducers from '../containers'
const configureStore = initialState =>
createStore(combineReducers(reducers), initialState)
export default configureStore
components/provider.js
import configureStore from '../store/configureStore'
const initialState = { minutes: 55 }
const store = configureStore(initialState)
On large code bases, it won't be enough to just directly import the reducers to the configureStore file. You have these enormous state trees that require hundreds of reducer files, and many reducers import other reducers from other files. Basically I'm asking how to manage a deeply nested state tree of reducers, and combine them one after another using import and export until they reach the root combineReducers function.

If the object passed inside combineReducer is empty or invalid, you'll see that error.
I always structure my reducers this way,
I don't spin off separate export for every action, consolidate them into one export and manage with switch cases.
Each reducer's initialState is defined within the reducer for easy code management.
I've rewritten your code a bit:
containers/score/reducers.js
const initialState = {
goals: 0,
points: 0
};
const scoreReducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_GOALS:
return { ...state, goals: action.payload };
case UPDATE_POINTS:
return { ...state, points: action.payload };
default:
return state;
}
};
export default scoreReducer;
containers/time/reducers.js
const initialState = {
minutes: 0,
seconds: 0
};
const timeReducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_MINUTES:
return { ...state, minutes: action.payload };
case UPDATE_SECONDS:
return { ...state, seconds: action.payload };
default:
return state;
}
};
export default timeReducer;
containers/index.js
import score from './score';
import time from './time;
import { combineReducers } from "redux";
const rootReducer = combineReducers({
score,
time
});
export default rootReducer;
And I don't complicate more after this - I just pass the provider and store to my main wrapper component after defining the combined reducers:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import rootReducer from './container';
import history from './history';
import App from './App';
const store = createStore(
rootReducer
);
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
I really think structuring this way would solve your problem, please try.

It seems like score and time folders don't contain index.js files.
Try to add them or change your containers/index.js file:
import * as score from './score/reducers.js'
import * as time from './time/reducers.js'

Related

TypeError: init is not a function at mountReducer

I am trying to use ContextAPI so i started off with making the file :
Just before including these files everything worked and now the app has just crashed and isnt working at all.
StateProvider.js
// setting up the data layer in order to have record of the basket and then use it in and keep track of user
// need to track basket
import React, { createContext, useContext, useReducer } from 'react';
// This is the data layer
const StateContext = createContext();
export { StateContext };
// build a provider
const StateProvider = ({ reducer, intialState, children }) => (
<StateContext.Provider value={useReducer(reducer, intialState, children)}>
{children}
</StateContext.Provider>
);
export { StateProvider };
// THIS IS HOW WE USE IT IN THE COMPONENT
const useStateValue = () => useContext(StateContext);
export { useStateValue };
// Then created a file reducer.js :
reducer.js;
export const intialState = {
basket: [],
};
//
function reducer(state, action) {
switch (action.type) {
case 'ADD_TO_BASKET':
// Logic for adding
break;
case 'REMOVE_FROM_BASKET':
// lOGIC for removing item from basket
break;
default:
return state;
}
}
export default reducer;
Here is an image of the error log.

Use redux to track recent viewed product, is this ok?

I am a newbie in ReactJS. To show the recent viewed product, many pages say that we will use cookie. But I am using redux to do it. My idea is: each time visitor open the product page, we will dispatch information of the product (suchs as ID) to store of redux. Then, we only need to get data from store. I don't see any one saying about this way so I am a bit worried about my solution. In case it's better to use cookies, could you please advise a link/ website to learn about it. I search many page but there's no details so it's so hard. Thank you so much.
Here my code:
Store;
const Viewed = [];
const Viewed_reducer = (init = Viewed, action) => {
switch (action.type) {
case "ADD_TO_VIEWED":
if (init.indexOf(action.id) === -1) {
init.push(action.id)
}
return init;
default:
return init;
}
}
const All_reducer = redux.combineReducers({
Viewed: Viewed_reducer
});
const Store = redux.createStore(All_reducer);
export default Store;
Product page, this will dispatch the product ID to Store each time visitor open:
import React, {useEffect} from 'react';
import urlSlug from "url-slug";
import {connect} from "react-redux";
import ProductsInfo from '../../../Data/ProductInfo';
import RecentViewed from './RecentViewed/RecentViewed';
const ProductDetail = (props) => {
const nameSlug = props.match.params.slug;
const {dispatch} = props;
const Product = ProductsInfo.find((product) => {
return urlSlug(product.name) === nameSlug
});
useEffect(() => {
dispatch({type: "ADD_TO_VIEWED", id: Product.id})
},[dispatch, Product])
return (
<section className="product-detail">
<RecentViewed key={`recent${Product.id}`}/>
</section>
);
};
const mapStateToProps = (state) => {
return {
Store: state
}
}
export default connect(mapStateToProps)(ProductDetail)
Component to render the data from Store:
import React from "react";
import ProductsInfo from "../../../../Data/ProductInfo";
import {connect} from "react-redux";
const RecentViewed = (props) => {
const {Viewed} = props.Store;
let viewedProducts = [];
Viewed.forEach((id) => {
ProductsInfo.forEach((product) => {
if (product.id === id){
viewedProducts.push(product)
}
})
})
return (
<div className="container-fluid also-container recent-viewed-container">
<div className="row">
<div className="col">
<div className="title">Sản phẩm vừa xem</div>
<div className="wrap outside">
{viewedProducts.map((product) => {
return (
// inside here we will show information of product such as name, image, etc
)
})}
</div>
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
Store: state
}
}
export default connect(mapStateToProps)(RecentViewed)
If you're only going to remember the last viewed product for the length of the user's browsing session, then the redux store should be fine. Just remember that it only exists in-memory, so if the user closes their tab or hard-refreshes the page, the store isn't persisted and you'll lose any information there.
Setting it in a cookie will make the information survive a refresh or close/reopen, since cookies are persisted between sessions (assuming you're not using a session cookie).

react-redux-firebase firestore returns function and not object

I'm trying to use firestore with react-redux-firebase on a React App, and when I try to access state.firestore it returns a function and no the object. I'll attach the code for initialization.
This is the app file.
import React from "react";
import ReactDOM from "react-dom";
import "./index.styles.ts";
import * as serviceWorker from "./serviceWorker";
import Theme from "./components/Theme";
import configureStore from "./store/configureStore";
import { Provider } from "react-redux";
import Routes from "routes";
import firebase from 'firebase/app'
import 'firebase/auth';
import 'firebase/firestore';
// import 'firebase/functions' // <- needed if using httpsCallable
import {
ReactReduxFirebaseProvider,
} from 'react-redux-firebase'
import { createFirestoreInstance } from 'redux-firestore';
const firebaseConfig = {
};
// react-redux-firebase config
const rrfConfig = {
userProfile: 'users',
useFirestoreForProfile: true // Firestore for Profile instead of Realtime DB
// enableClaims: true // Get custom claims along with the profile
}
// Initialize firebase instance
firebase.initializeApp(firebaseConfig);
firebase.firestore();
const store = configureStore();
const rrfProps = {
firebase,
config: rrfConfig,
dispatch: store.dispatch,
createFirestoreInstance
};
ReactDOM.render(
<Theme>
<Provider store={store}>
<ReactReduxFirebaseProvider {...rrfProps}>
<Routes />
</ReactReduxFirebaseProvider>
</Provider>
</Theme>,
document.getElementById("root")
);
serviceWorker.unregister();
According to the docs, this is basically what's needed to be able to access firestore.
This is the configureStore file
import { createBrowserHistory } from "history";
import { createStore, applyMiddleware, Store } from "redux";
import { routerMiddleware } from "connected-react-router";
import thunk from "redux-thunk";
import { History } from "history";
import { combineReducers, compose } from "redux";
import { connectRouter } from "connected-react-router";
import * as reducers from "./reducers";
import { firebaseReducer } from "react-redux-firebase";
import { reduxFirestore } from "redux-firestore";
export const history = createBrowserHistory();
const createRootReducer = (history: History<any>) =>
combineReducers({
router: connectRouter(history),
firebase: firebaseReducer,
firestore: reduxFirestore,
...reducers,
});
export default function configureStore(): Store {
const store = createStore(
createRootReducer(history), // root reducer with router state
compose(
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
thunk
)
)
);
return store;
}
Update: As confirmed by #luis, the fix was actually importing firestoreReducer instead of reduxFirestore in the configureStore.ts.
You are setting fiterstore as reduxFirestore, which is imported from redux-firestore library which is indeed a function.
I am not sure if you are using it correctly. Following is usage code from library's npm page:
import { createStore, combineReducers, compose } from 'redux';
import { reduxFirestore, firestoreReducer } from 'redux-firestore';
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/firestore';
const firebaseConfig = {}; // from Firebase Console
const rfConfig = {}; // optional redux-firestore Config Options
// Initialize firebase instance
firebase.initializeApp(firebaseConfig);
// Initialize Cloud Firestore through Firebase
firebase.firestore();
// Add reduxFirestore store enhancer to store creator
const createStoreWithFirebase = compose(
reduxFirestore(firebase, rfConfig), // firebase instance as first argument, rfConfig as optional second
)(createStore);
// Add Firebase to reducers
const rootReducer = combineReducers({
firestore: firestoreReducer,
});
// Create store with reducers and initial state
const initialState = {};
const store = createStoreWithFirebase(rootReducer, initialState);
Notice how createStoreWithFirebase is called instead of createStore and firestoreReducer is passed along with an initial state.

Unit Testing to see if JSON file is null

I am currently trying to unit test a container that pulls in a static JSON file of phone numbers and passes it to the component to display, however I am not sure how I should go about testing it. The code for the container is as follows:
import React from 'react';
import data from *JSON file location*
import CountryInfo from *component for the country information* ;
class CountryInfoContainer extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
numbersJson: null
};
}
async componentWillMount() {
const numbersJson = data;
this.setState({ numbersJson });
}
render() {
return (
<CountryInfo json={this.state.numbersJson} showText={this.props.showText} />
);
}
}
export default CountryInfoContainer;
I currently have my unit test to look like this
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { mount, configure } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import CountryInfoContainer from './CountryInfoContainer';
configure({ adapter: new Adapter() });
describe('Successful flows', () => {
test('checks if json has null entries', () => {
const wrapper = mount(<MemoryRouter><CountryInfoContainer /></MemoryRouter >);
const data = wrapper.find(numbersJson);
// eslint-disable-next-line no-console
console.log(data.debug);
});
});
Obviously, it doesn't work now because I am not sure how to use the variable numbersJson in the container in the test file or how to check if it is null.
The variable numbersJson is not defined in the scope of your test. If I understand correctly, you are testing that when you first mount the component, that it's state contains a null value for the numbersJson key.
First of all, you need to mount your component directly without MemoryRouter:
const wrapper = mount(<CountryInfoContainer />);
Then you can write an expect() for the state:
expect(wrapper.state().numbersJson).toBeNull();

React Redux Turbo Module Build Failed

I've been following this tutorial (https://www.turbo360.co/tutorial/redux-walkthrough) and I keep trying to run webpack but the build keeps failing. Any one know why it keeps crashing?
import { createStore, appMiddleware, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import { todoReducer } from './reducers'
let store = null
export default {
createStore: () => {
const reducers = combineReducers({
todo: todoReducer
})
store = createStore(
reducers
appMiddleware(thunk)
)
return store
},
currentStore: () => {
return store
}
}
Does anyone know the solution?
You need to import applyMiddleware, not appMiddleware. It's a typo.
applyMiddleware