Redux has proven a little tricky for me to wrap my head around, and I was wondering if someone could help point me in the right direction of what piece I am not grasping to get my desired results. Just a forewarning: I am using ES6 syntax.
Okay, so I have setup somewhat of a sandbox to test out how redux works, and this is the current file setup I am working with.
-actions
--index.js
-reducers
--index.js
--reducer_user.js
-containers
--ReduxTest.js
In my container, ReduxTest.js, I have the following code.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions/index';
class ReduxTest extends Component {
render() {
return (
<div>
{console.log(this.props.fetchUser())}
{console.log(this.props.user)}
</div>
)
}
}
export default connect( null, { fetchUser } ) (ReduxTest);
When I render ReduxTest.js to the screen, the first console.log statement shows up as,
Object { type: "FETCH_USER", payload: "This is just a test."}
The second one however, shows up as "undefined".
Here is what my actions index.js looks like,
export const FETCH_USER = 'FETCH_USER';
export function fetchUser() {
const testing = "This is just a test.";
return {
type: FETCH_USER,
payload: testing
}
}
Here is my reducer_user.js file
import { FETCH_USER } from '../actions/index';
export default function(state = null, action) {
switch(action.type) {
case FETCH_USER:
return action.payload;
}
return state;
}
and finally, here is my index.js in the reducer folder
import { combineReducers } from 'redux';
import UserReducer from './reducer_user';
const rootReducer = combineReducers({
user: UserReducer
});
export default rootReducer;
I am using a video tutorial from Udemy, so that is where I am getting some of my syntax and what not. I was under the impression that I would be able to access "this.props.user" from the index.js reducer, but I am doing something wrong, or missing a step. Any help would be appreciated.
Just so I am clear, all my intention is, is to successfully have the ReduxTest container console log JUST the string that is in the payload. if you can help with that, I think I can carry it on from there. Thanks =)
You're only passing the action creator to your component. If you want to access your props.user than you have to provide it. You can achieve this by the first argument of the connect function.
const mapStateToProps = state => ({
user: state.user,
});
export default connect(mapStateToProps, { fetchUser })(ReduxTest);
The first argument of connect must be a callable function. The only argument of this function is the current state. The function must return an object, containing all properties you want to access inside your component.
Please notice that the state of your user reducer is set to null initially. Redux fires multiple, internal actions. If you log your current state in your render method, it can happen, that your state gets logged before you are calling your own actions. This can be confusing.
You can change the initial state of your reducer this way:
import { FETCH_USER } from '../actions/index';
export default function(state = 'User not fetched yet', action) {
switch(action.type) {
case FETCH_USER:
return action.payload;
}
return state;
}
Related
I have a react main component that dispatches redux action on componentDidMount, the action will fetch API data.
The problem is: when I start my application my componentDidMount and main component are executed twice. So, it makes 2 API calls for each time application loads. API has a limit for the total number of calls I make, I don't want to reach my limit.
I have already tried fixing the issue by removing constructor, using componentWillMount problem is not solved.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../redux/actions/fetchActions';
import TableHeader from './tableHeader';
class Main extends Component {
componentDidMount() {
console.log("mounted");
// this.props.dispatch(actions.fetchall("market_cap"));
}
render() {
console.log("rendered");
// console.log(this.props.cdata);
// console.log(this.props.cdata.data.data_available);
return <div className="">
<TableHeader {...this.props} />
</div>
}
}
export default Main;
///actions
import axios from 'axios';
export function fetchall(sort) {
return function (dispatch) {
axios.get(`https://cors-anywhere.herokuapp.com/https:-----------`)
.then(function (response) {
dispatch({
type: 'FETCH_DATA',
payload: response.data
})
})
.catch(function (error) {
console.log(error);
})
}
}
//reducer
let initialState = {
coins: [],
data_available: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case 'FETCH_DATA':
return {
...state,
coins: action.payload,
data_available: true
}
default: return state;
}
}
//rootreducer
import { combineReducers } from 'redux';
import DataReducer from './dataReducer';
export default combineReducers({
data: DataReducer
});
////index
import {createStore, applyMiddleware} from 'redux';
import MapStateToProps from './components/mapStateToProps';
import rootReducer from './redux/reducers/rootReducer';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
//const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, applyMiddleware(...middleware));
ReactDOM.render(<Provider store={store}><MapStateToProps/></Provider>, document.getElementById("root"));
console image is posted for reference "rendered" is logged inside main component
"runned1" is logged inside main-subcomponent
"mounted" logged inside componentDidMount
"
I believe you can work around this by providing some additional logic in your componentDidmount. You should also make use of your component state.
Write something like this:
constructor(props){
super(props)
this.state = {
mounted: false
}
}
componentDidMount(){
if(!this.state.mounted){
this.props.dispatchmyAction()
this.setState({
mounted: true
})
}
}
This essentially says, if your component has already mounted once, then you will not make your action creator request.
If you watch your console.log carefully you can notice that your HMR Hot Module Reloading -plugin, re-mounts your component and this is the main reason behind this occurrence.
What this plugin does, is that it watches for your bundles code changes and on every time you save re-renders your component. There has been a lot of discussion as well that this plugin does not work all cases as expected.
Here is some material you might consider to go trough if you wish to use HMR.
Writing about HMR -
https://codeburst.io/react-hot-loader-considered-harmful-321fe3b6ca74
User guide for HMR -
https://medium.com/#rajaraodv/webpacks-hmr-react-hot-loader-the-missing-manual-232336dc0d96
The problem is solved when I removed webpack from the project. But can anyone answer how can I solve this while still using the webpack.
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.
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'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.
I am trying to get familiar with the flow of the react-boilerplate.
Till now I love how neat clean and easy to understand are things, I although feel that I miss a piece of the puzzle. Would be nice if someone with more experience could help me with that.
The problem I am facing at the moment goes as follows.
I am triggering an action within componentWillMount() of a specific component.
The action is being created in actions.js, its a simple get request made with axios.
The data are being processed in a promise middleware library redux-promise.
The promise is now being passed into the reducer of the specific component, where the whole state and the data that I need are being returned.
Trying to catch this state at the component is where I fail. I am trying to mapStateToProps but cannot find the data that I need there instead a Map {} is being received.
How do I Map this object with my props ?
I am sure I miss something important.
Here is my repo.
https://github.com/paschalidi/blog-react-redux
And here is my code so you can have a brief look.
index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchPosts } from './actions'
import selectPostsIndex from './selectors'
export class PostsIndex extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div>
<h3>Posts</h3>
<ul className="list-group">
A list would render here.
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state.posts)
//return { posts: state } //****I dont get why the redux state is not being given here.
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPosts }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
actions.js
import axios from 'axios'
import { FETCH_POSTS } from './constants';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=dsklhfksdhfjkdshfkjdshkj';
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
store.js
import promise from 'redux-promise';
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
promise
];
reducer.js
import { fromJS } from 'immutable';
import {
FETCH_POSTS
} from './constants';
const initialState = fromJS({ all:[], post: null });
function postsIndexReducer(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return { ...state, all: action.payload.data };
default:
return state;
}
}
export default postsIndexReducer;
Also the action is being registered in reducers.js
import PostsReducer from 'containers/PostsIndex/reducer'
export default function createReducer(asyncReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
posts: PostsReducer,
form: reduxFormReducer,
...asyncReducers,
});
}
Note I didn't test your code, but it looks like your reducer puts the fetched data in the field all of your global states posts field, but your mapStateToProps doesn't pick that up. Note that mapStateToProps should slice the part of the global state that the given component is interested in.
After a successful fetch the state you receive in mapStateToProps should look something like this:
{
posts: {
all: // whatever fetch returned
post: null
}
}
So your mapStateToProps could look something like this (note that this method receives the global state as an argument, not just for the specific reducer):
function mapStateToProps(state) {
// in component this.props.posts is { all: /* fetch result */, post: null }
return { posts: state.posts }
}
Also try to debug these methods, it becomes clearer once you see the flow of the data!
This GitHub issue covers this exact problem: https://github.com/reactjs/react-redux/issues/60.
I had to manually extract the values from the Map in mapStateToProps function:
const mapStateToProps = (state) => {
return {
posts: state.get('posts'),
};
}
Thanks to this StackOverflow post.