Redux newbie here.
I have an reducer ProductListReducer that takes asynchronous action (its action creator fetch data from API), whose initial state is InitialProductListStates. I have another synchronous reducer CartReducer, whose initial state is InitialCartListStates. I want to have them work on one single store store. How should I do this?
My attempt: I want to first combine the two reducers into one reducer with combineReducers, and then combine their initial states as InitialState = {ProductListReducer: InitialProductListStates, CartReducer:InitialCartListStates}. Next, I want to use createStore():
store = createStore(reducer, InitialState , applyMiddleware(thunk));
However, I am stuck at the first test-of-concept: After combining ProductListReducer with itself and setting InitialState={ProductListReducer: InitialProductListStates} , I re-run the app and get this error:
Uncaught Error: Minified Redux error #12; visit https://redux.js.org/Errors?code=12 for the full message or use the non-minified dev environment for full errors.
at redux.js:441:13
at Array.forEach (<anonymous>)
at redux.js:434:25
at _l.ProductListReducer (redux.js:499:5)
at main.ts:4:17
at index.tsx:13:27
at index.tsx:13:27
My codes (that doesn't work):
import reducer from './reducers/main';
import { ProductListReducer, InitialProductListStates } from './reducers/reducers'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const InitialStates = {
ProductListReducer: InitialProductListStates
};
export const store = createStore(reducer, InitialStates, applyMiddleware(thunk));
My code (that did work):
import reducer from './reducers/main';
import { ProductListReducer, InitialProductListStates } from './reducers/reducers'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const InitialStates = {
ProductListReducer: InitialProductListStates
};
export const store = createStore(ProductListReducer, InitialProductListStates, applyMiddleware(thunk));
I am building my first React app and just added redux on it as it started getting too complex to pass states to my different components.
I have been following the official redux documentation as well as online tutorials and although everything seems to work fine, the app crashes whenever I reload the page and I receive the following error message "Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>".
Here is what my index.js looks like:
import React from 'react'
import ReactDOM from 'react-dom'
import './app.css'
import App from './App'
import { store } from './app/store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Here is my store.js:
import { configureStore } from '#reduxjs/toolkit';
import userReducer from "features/user";
import userInterfaceReducer from 'features/userInterface';
export const store = configureStore({
reducer: {
user: userReducer,
userInterface: userInterfaceReducer,
}
})
And my two slice reducers:
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: "user",
initialState: { value: {userType: "", fName: "", lName: ""}},
reducers: {
user: (state, action) => {
state.value = action.payload;
}
}
})
export default userSlice.reducer;
and
import { createSlice } from "#reduxjs/toolkit";
export const userInterfaceSlice = createSlice({
name: "open",
initialState: { value: false}, // this state toggles a drawer present on several pages
reducers: {
toggleOpen: (state, action) => {
state.value = action.payload;
},
}
})
export const { toggleOpen } = userInterfaceSlice.actions;
export default userInterfaceSlice.reducer;
I read through dozens of posts, tried several of the solutions given, killed my app and restarted it several times but the problem persists.
The code in itself seems to work fine, I can access and update the states as I want to and they stay consistent through the different pages. But if I refresh any of the pages where I use useSelector, the app crashes. The redux devtools doesn't show any error until I refresh and then says "No store found. Make sure to follow the instructions."
Am I missing something?
Thanks in advance!
EDIT: it took me a while but I got it to work. Hopefully, this might help someone one day, so here is how I solved my issue:
Following tutorials, my redux Provider was wrapped around my App component in my index.js file. I moved it to the actual App component. Problem solved!
wrapping the provider directly in my app at app.js really helped
I have a root reducer which imports action type constants from the another file. When the reducer is called for the first time because of the createstore, the values of these constants are undefined.
I tried converting those constants into functions and it gave me error- "Object can not be function". When I printed its type, it initially gave me undefined but later calls prints the type- function.
My directory structure is such
helpers/
-index.js
-store.js
-constants.js
reducers/
-index.js
-rootReducer.js
Every index.js file is of type-
export * from './rootReducer'
This is my store file (store.js)-
import { createStore, applyMiddleware, compose } from 'redux'
import { rootReducer } from '../reducers'
import ReduxThunk from 'redux-thunk'
const initialState = {}
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
export const store = createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(ReduxThunk))
)
This is my constants file (constants.js)-
export const constants = {
BENCHMARK_DATA_RECEIVED: 'BENCHMARK_DATA_RECEIVED',
BENCHMARK_DATA_FAILED: 'BENCHMARK_DATA_FAILED',
}
This is my root reducer (rootReducer.js)-
import { constants } from '../helpers'
export const rootReducer = (state = [], action) => {
console.log(constants)
// Initially print undefined
// After that prints correct value
switch (action.type) {
case 'BENCHMARK_DATA_RECEIVED':
return { ...state, benchmark_data: action.payload }
default:
return state
}
}
I am no sure about what is causing this problem but it only happens when the reducer is used first time ( most probably while creating store ). I have googled a lot but has not come across any such questions. Maybe there is some problem with my setup. Also printing those constants anywhere (like in action creators) works fine.
To sum up the discussion in the comments, you can import directly from constants.js in your reducer and meanwhile investigate the file structure you have.
So I'm trying to learn React with Redux and so far I think I've been able to work out most of the code needed to make it work but I'm having an issue with getting my state passed down to my component. I am using Visual Studio 2017's ASP.NET Core project template that has react and redux boilerplate codes and they used this:
export default connect(
state => state.weatherForecasts,
dispatch => bindActionCreators(actionCreators, dispatch)
)(FetchData);
I tried doing the same thing with my own component like so:
export default connect(
state => state.lecture,
dispatch => bindActionCreators(actionCreators, dispatch)
)(LectureTable);
but when trying to access the contents of my props, the properties I want to get are tagged as undefined. I checked through Redux devtools that my initial state exists but my component is unable to see the props I'm trying to pass to it. The weird thing is I just imitated the boilerplate code but it isn't working yet the boilerplate code works just fine (ie I can go to the component and log out its initial state).
Since I'm following the format used by Visual Studio,my actioncreators, reducers, and constants are in one file shown below:
const GET_LECTURES = "GET_LECTURES";
const initialState = {
lectures: [],
selectedLecture: {},
isLoading: false,
test: 0
};
export const actionCreators = {
requestLectures: isLoading => async (dispatch) =>
{
if (!isLoading) {
// Don't issue a duplicate request (we already have or are loading the requested data)
return;
}
dispatch({ type: GET_LECTURES });
const url = `api/lecture/`;
const response = await fetch(url);
const lectures = await response.json();
dispatch({ type: RECEIVE_LECTURES, payload: lectures });
}
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case GET_LECTURES:
return { ...state, isLoading: true };
default:
return state;
}
};
I'm sorry if its all messy. I'm really just starting to begin to understand redux..
Edit
My component code:
import React, { Component } from 'react';
import {Button, Table, Label, Menu, Icon} from 'semantic-ui-react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {actionCreators} from './../../store/Lecture';
export class LectureTable extends Component {
componentWillMount(){
// this.props.requestLectures(this.props.isLoading);
console.log(this.props.test);
}
render() {
return (
<Table size='large'>
{/*removed to make it cleaner..currently only has static data too lol*/}
</Table>
)
}
}
export default connect(
state => state.lecture,
dispatch => bindActionCreators(actionCreators, dispatch)
)(LectureTable);
where my store is configured:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Lecture from './Lecture';
import * as Counter from './Counter';
import * as WeatherForecasts from './WeatherForecasts';
export default function configureStore(history, initialState) {
const reducers = {
lecture: Lecture.reducer,
counter: Counter.reducer,
weatherForecasts: WeatherForecasts.reducer
};
const middleware = [
thunk,
routerMiddleware(history)
];
// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
enhancers.push(window.devToolsExtension());
}
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createBrowserHistory } from 'history';
import configureStore from './store/configureStore';
import App from './pages/App';
import registerServiceWorker from './registerServiceWorker';
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const history = createBrowserHistory({ basename: baseUrl });
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = window.initialReduxState;
const store = configureStore(history, initialState);
const rootElement = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
rootElement);
registerServiceWorker();
The first argument to connect() should be a function that returns an object - with the props you want added as keys, and their value being the value from state. e.g.
state => ({ lecture: state.lecture })
I found the solution. First of all I'm a noob both to stackoverflow and to react so I apoligize for all my inconsistencies (if thats the right term?).
What I found out:
I am using react router
I was doing the connect method to a subcomponent of the component being rendered by the router
I placed the connect method to the parent component and it worked
Some notes:
state => state.lecture still works
I will take all of your advices to heart and change my code accordingly
The only reason I was adamant with solving the problem using the code I had was because I couldn't accept the fact that boilerplate code wouldn't work unless I had done something specifically different from what the boilerplate did. I just didn't take into account that the router played a huge role with it.
I repeat...I'm a react noob so I'm sorry for wasting your time T_T
Edit again:
I was able to connect a different child component with the Redux store. I'm trying to look at why I still can't do it for that specific component that caused me to ask this question. I'll update my answer once I find the reason.
I think in their example weatherForecasts is an object. In your example lectures seems to be an array so I suggest to rewrite your mapStateToProps function like this if you only need to get the lectures prop
state => ({ lectures: state.lectures})
if you need the whole state you can have state => state so you can access the props this.props.test and this.props.lectures
Keep in mind that mapStateToProps should return an object, not an array. By the way, in your reducer the field name is lectures (plural) not lecture so state => state.lecture will be undefined
Rick, your connect argument should be something like:
export default connect( state => {
return {
test: state.lecture // Or any value
}
})(LectureTable);
You're trying to console log the test prop, so you should include it in your connect call.
I think by doing the following steps, you can solve the issue:
First you need to call two functions when you want to connect your component to application state, one is mapDispatchToProps and another one is mapStateToProps, for your code to be clean, its better to define these functions separately and then pass them by name to connect, but if you want to use your own way you should do these changes: (assuming your reducer name is lecture from your combineReducers, and assuming you are calling requestLectures with this syntax: this.props.lectureActions.requestLectures() and importing lectureActions from the file you have written lecture related actions) :
export default connect(
state => state.lecture.lectures,
dispatch => {lectureActions: bindActionCreators(lectureActions, dispatch)}
)(LectureTable);
from above code, you do not need to export an object that contains the actions like actionCreators, you should export the requestLectures function out of it independently
add below case to your reducer so that when getting the lectures succeeds the state of the application gets updated with the lectures:
case RECEIVE_LECTURES:
return { ...state, isLoading: false, lectures: payload.lectures };
default:
return state;
}
You have two problems here.
You are defining mapStateToProps function as the first argument to connect wrong. As many of answers explain this now you should use it like, this:
export default connect(
state => ( { lecture: state.lecture } ),
dispatch => bindActionCreators(actionCreators, dispatch)
)(LectureTable);
Now, you have a lecture prop as your state. You can reach it with this.props.lecture. But in your componentWillMount method, you are trying to log it like this.props.test. It should be this.props.lecture.test.
By the way, try to use componentDidMount instead of componentWillMount since it will be deprecated in the future releases.
I'm new to React/Redux and not sure if I'm not doing something wrong.
I'm having a component which makes an AJAX call on componentDidMount to fetch data from the server to render.
The problem is that Redux is dispatching two #INIT actions and often the second one is dispatched after I already received the response from the server. It comes with an empty (initial) state which is passed to the component props and, as result, I receive a blank screen.
Please see this log produced by the reducer:
I already found that having two ##INIT actions is an expected behavior, the first one is needed to test the reducers and the second one is an actual init (check the discussion here).
The question is how can I solve this issue in a proper way. Is it a race condition or am I doing something wrong? Thanks!
Update
What is interesting is that it definitely relates to the performance of my laptop. The server is also running on my local environment. To allow me to proceed with development while I'm waiting for the answer I temporarily put setTimeout with 100ms delay into componentDidMount. Now I commented it and can't repro the issue.
Update Adding pieces of my code
Store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
const middleware = window.devToolsExtension
? compose(
applyMiddleware(thunk),
window.devToolsExtension()
)
: applyMiddleware(thunk);
const store = createStore(reducers, middleware);
export default store;
Reducer (nothing special, just used it to log the action because browser Redux extension shows only one ##INIT action)
import * as types from '../actions/types';
const initialState = {
listings: []
};
export default function(state = initialState, action) {
console.log(action, state);
switch (action.type) {
case types.LISTINGS_FOUND:
return { listings: action.payload };
default: return state;
}
};
Component
import React from 'react';
import { connect } from 'react-redux';
import { search as searchListings } from '../../actions/listing-actions'
import View from './View'
class Container extends React.Component {
componentDidMount() {
if (this.props.listings.length === 0) {
this.props.searchListings();
}
}
render() {
console.log('rendering list', this.props.listings);
return (
<View listings={this.props.listings}/>
);
}
}
Container.propTypes = {
listings: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
searchListings: React.PropTypes.func.isRequired,
};
const mapStateToProps = function(store) {
return {
listings: store.listingSearch.listings
};
};
export default connect(mapStateToProps, { searchListings })(Container);
As I said I can't repro this issue now. I'll try to make some synthetic example to repro this later when I have more time.
You should load on componentDidMount() as recommended in the docu. You can also see in this example from the creator of Redux.