Cannot mount dispatch to props with connect() - reactjs

I am having a react-redux action creation problem. My props are an empty object when I log props at the life cycle method componentDidMount()
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchSurveys } from '../../actions/index';
export class SurveyList extends Component {
componentDidMount() {
console.log(this.props);
this.props.fetchSurveys();
}
renderSurveys() {
return (
this.props.surveys.length &&
this.props.surveys.map(survey => {
return (
<div className="card blue-grey darken-1" key={survey._id}>
<div className="card-content">
<span className="card-title">{survey.title}</span>
<p>{survey.body}</p>
<p className="right">
Sent On: {new Date(survey.dateSent).toLocaleDateString()}
</p>
</div>
<div className="card-action">
<a>Yes: {survey.yes}</a>
<a>No: {survey.no}</a>
</div>
</div>
);
})
);
}
render() {
return <div>{this.renderSurveys()}</div>;
}
}
function mapStateToProps({ surveys }) {
return { surveys };
}
export default connect(mapStateToProps, { fetchSurveys })(SurveyList);
Now according to the react-redux docs by default dispatch is included on the props so we do not need to explicitly call a mapDispatchToProps in the connect method in order to hit our action creators. fetchSurveys() is an action creator and I expect it to return a list of surveys which I then render.
However this.props = {}; so of course I cannot call .map on undefined at renderSurveys() as I do not get the surveys property on props either.
I am really troubled by why my props are empty. Can anybody shed some light onto this problem, I would be very grateful. I have tried using bindActionCreators and having my own mapDispatchToProps method, this doesn't work either.
Here are my actions.
import axios from 'axios';
import { FETCH_USER, FETCH_SURVEYS } from './types';
export const fetchUser = () => async dispatch => {
const res = await axios.get('/api/current_user');
dispatch({ type: FETCH_USER, payload: res.data });
};
export const handleToken = token => async dispatch => {
const res = await axios.post('/api/stripe', token);
dispatch({ type: FETCH_USER, payload: res.data });
};
export const submitSurvey = (values, history) => async dispatch => {
const res = await axios.post('/api/surveys', values);
history.push('/surveys');
dispatch({ type: FETCH_USER, payload: res.data });
};
export const fetchSurveys = () => async dispatch => {
console.log('called');
const res = await axios.get('/api/surveys');
dispatch({ type: FETCH_SURVEYS, payload: res.data });
};
My surveys reducer -
import { FETCH_SURVEYS } from '../actions/types';
export default function(state = [], action) {
switch (action.type) {
case FETCH_SURVEYS:
return action.payload;
default:
return state;
}
}
Props in console -
Combined reducer -
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import { reducer as reduxForm } from 'redux-form';
import surveysReducer from './surveysReducer';
export default combineReducers({
auth: authReducer,
form: reduxForm,
surveys: surveysReducer
});

Just saw your code in the link. In Dashboard.js
Shouldn't
import { SurveyList } from './surveys/SurveyList';
be
import SurveyList from './surveys/SurveyList';
Since the connected component is exported as default?

Have you used your reducer in combineReducers? you should have an index.js file in reducers folder (for example) which has something like this:
import {myreducer} from './myreducer';
const rootReducer = combineReducers(
{
myreducer: myreducer
}
);
export default rootReducer;
Do you have complete codebase somewhere? It looks like issue is not in provided code..

Seems that you forgot to add your reducer to main reducers file.
So, it depends on your code it should be:
// reducers/index.js
import { combineReducers } from 'redux';
import surveys from './surveysReducer';
const rootReducer = combineReducers({
surveys,
});
export default rootReducer;
Hope it will helps.

Related

Redux doesn't fetch data from API request

I'm new to React/Redux. I'm making an app using an API but the code doesn't work. When I run the code it says "this.props.recipes.map is not a function" and doesn't render anything.
If I change payload to: "payload: response.data.recipes" then the error changes to "Given action "FETCH_RECIPE", reducer "recipes" returned undefined." but no errors on screen (only in console). I thought writing "(state = [], action)" would solve the problem but it seems not. What's the problem and how do I fix this error?
Action Creator
import recipe from '../apis/recipe';
export const fetchRecipe = () => async dispatch => {
const response = await recipe.get('');
dispatch({ type: 'FETCH_RECIPE', payload: response.data })
};
Reducer
import { combineReducers } from 'redux';
const recipeReducer = (state = [], action) => {
switch(action.type) {
case 'FETCH_RECIPE':
return action.payload;
default:
return state;
}
};
export default combineReducers({
recipes: recipeReducer
});
import React from 'react';
import { connect } from 'react-redux';
import { fetchRecipe } from '../actions';
class Recipe extends React.Component {
componentDidMount() {
this.props.fetchRecipe();
console.log("This doesn't work", this.props.recipes)
}
renderList() {
return this.props.recipes.map(recipe => {
return (
<div>
<p>{recipe.publisher}</p>
</div>
)
})
}
render() {
console.log("First loaded: empty, second time: data fetched", this.props.recipes)
return (
<div>
{this.renderList()}
</div>
);
}
}
const mapStateToProps = (state) => {
return { recipes: state.recipes }
};
export default connect(mapStateToProps,{
fetchRecipe
})(Recipe);
API Request
import axios from 'axios';
import { key } from './config';
export default axios.create({
baseURL: `https://cors-anywhere.herokuapp.com/https://www.food2fork.com/api/search?key=${key}&q=pizza`
});

How to fix 'Actions must be plain objects. Use custom middleware for async actions.'

I am making a React-redux component to embed in Laravel blade file. Where in the react side,
I am using redux with the thunk, When I try to get data without thunk from the Laravel route, it getting properly.
But When I use an axios request in the action creator to get data asynchronously. It gives the:
'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.'
This is the entry component of the react side.
Entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reducer from '../store/reducers/reducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
// console.log(getState());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('like_post'));
This is the App.js main component
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from 'prop-types';
import axios from 'axios';
import {getCurrentPostLikes} from '../store/actions/actions';
class App extends Component {
constructor(props) {
super(props)
var domain = window.location.hostname;
var url = window.location.pathname;
var urlsplit = url.split("/");
var post_slug = urlsplit[urlsplit.length - 1];
this.state={
post_slug: post_slug,
post_likes:''
}
}
kFormatter(num) {
return num > 999 ? (num / 1000).toFixed(1) + 'k' : num
}
componentDidMount() {
this.props.getCurrentPostLikes();
// axios.get(`/api/get_post_likes/${this.state.post_slug}`)
// .then(response => {
// console.log(response.data.likes);
// this.setState({
// post_likes: response.data.likes
// })
// })
}
render() {
return (
<div className="App">
<img src="/images/love.svg" alt="post like" width="50px" height="50px"/>
<p>{this.kFormatter(this.state.post_likes)}</p>
<p><span>{this.props.likes}</span></p>
</div>
);
}
}
export default connect(null, {getCurrentPostLikes})(App);
// export default connect( mapStateToProps, mapDispachToProps )(App);
This is the actions.js file /store/actions/actions.js
// types.js is also defined properly as
// export const GET_POST_LIKES = 'GET_POST_LIKES';
import axios from 'axios';
import {GET_POST_LIKES} from './types';
// Get current post likes
export const getCurrentPostLikes = () => dispatch => {
return dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
}
Tried this action creator also, but still same error
export const getCurrentPostLikes = () => {
return dispatch => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}
}
This is the reducers.js file under /store/reducers/reducer.js
import { GET_POST_LIKES } from '../actions/types';
const initialState = {
likes: null
};
const reducer = (state=initialState, action) => {
const newState = {...state};
switch(action.type){
case 'GET_POST_LIKES':
return {
...state,
post: action.payload
}
case 'LIKE_UP':
newState.likes += action.value
break;
}
return newState;
};
export default reducer;
Now, This should return a field value of posts table, with post id = 2.
Your problem is at Entry.js, this line:
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
You are setting the thunk middleware as the second parameter. The second parameter is for the initial state.
The thunk middleware should be part of the composed enhancers in the third parameter of the createStore function.
The parameters should be applied as such:
createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
Full example:
import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import rootReducer from './core/reducers'
export const history = createHistory()
const initialState = {}
const enhancers = []
const middleware = [thunk, routerMiddleware(history)]
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
export default createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
This is the 'signature' of an action creator curried function using redux-thunk:
export const getCurrentPostLikes = () => async dispatch => {
const response = await axios.get(`/api/get_post_likes/2`);
dispatch({ type: GET_POST_LIKES, payload: response.data.likes });
};
This is how you want your action creator to look:
export const getCurrentPosts = () => {
return async function(dispatch, getState) {
const response = await axios.get("/api/get_post_likes");
dispatch({ type: "GET_POST_LIKES", payload: response });
};
};
Leave the Promises out of it and go with ES7 async/await syntax and also leave out the setTimeout, unless you can provide a really good justification for wanting to time out an asynchronous request that is already taking a non-zero amount of time to complete, maybe I don't understand, but you do want that data don't you? It's necessary for your reducers to do their jobs and eventually update state in your application.
What I have above is not perfect in that it's boilerplate, like you don't really need getState in there, yes it's part of middleware, but if you are not going to use something, no need to define it. Anyway I am trying to get you to a point where your action creator works again and you are sparing your eyes and your mind.
It seems your action has an extra dispatch =>
export const getCurrentPostLikes = () => dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}

Redux thunk wont update with response from async function

I have been following a guide to setup redux-thunk so I can fetch a users geolocation and then dispatch and update state. However, every time I attempt to dispatch the action with response data, it just sets the data to null.
When I attempt to simulate an API call with a timeout and set some random values, it works without a problem.
geoLocationActions.js
export function geoLocationActions() {
return dispatch => {
const geolocation = navigator.geolocation;
geolocation.getCurrentPosition((position) => {
console.log(position.coords);
dispatch({
type: 'FETCH_USER_LOCATION_SUCCESS',
payload: position
});
});
}
};
MapContainer.js
import React from "react";
import { geoLocationActions } from '../../../actions/geoLocationActions';
import { connect } from 'react-redux';
class MapContainer extends React.Component {
componentWillMount() {
this.props.geoLocationActions();
}
render() {
return (
<div>
<p>Fetching location...</p>
</div>
);
}
}
// update current geolocation state
const mapDispatchToProps = (dispatch) => {
return {
geoLocationActions: () => dispatch(geoLocationActions())
};
}
const mapStateToProps = (state) => {
return {
state: state
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MapContainer);
reducers.js
case 'FETCH_USER_LOCATION_SUCCESS':
return {
...state,
userLocation: action.payload
}
store.js
import { createStore, combineReducers, applyMiddleware, compose } from
'redux';
import reducer from '../reducers/reducers';
import reduxThunk from "redux-thunk";
const rootReducer = combineReducers({
state: reducer
});
export const store = createStore(
rootReducer,
compose(
applyMiddleware(reduxThunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
);
It turns an HTML5 Geoposition object. You need to convert it to a regular object that can be serialized with JSON.stringify.
You can use this method:
const geopositionToObject = geoposition => ({
timestamp: geoposition.timestamp,
coords: {
accuracy: geoposition.coords.accuracy,
latitude: geoposition.coords.latitude,
longitude: geoposition.coords.longitude
}
})
Update your geoLocationActions.js like this:
export function geoLocationActions() {
return dispatch => {
const geolocation = navigator.geolocation;
geolocation.getCurrentPosition((position) => {
const positionObj = geopositionToObject(position)
console.log(positionObj);
dispatch({
type: 'FETCH_USER_LOCATION_SUCCESS',
payload: positionObj
});
});
}
};
You can have a look at my repo to see the same code.

React redux saga - getting some odd behaviour

I am in process of learning how to use reactjs, redux, react-redux and redux-saga. My attempt at this in my public github repo, found here:
https://github.com/latheesan-k/react-redux-saga/tree/5cede54a4154740406c132c94684aae1d07538b0
My store.js:
import { compose, createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import reducer from "./reducers";
import mySaga from "./sagas";
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// TODO: Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
})
: compose;
const storeEnhancer = composeEnhancers(applyMiddleware(sagaMiddleware));
export default createStore(reducer, storeEnhancer);
sagaMiddleware.run(mySaga);
my actions.js
export const HELLO_WORLD_REQUEST = "HELLO_WORLD_REQUEST";
export const HELLO_WORLD_RESPONSE = "HELLO_WORLD_RESPONSE";
export const HELLO_WORLD_ERROR = "HELLO_WORLD_ERROR";
export const helloWorldRequest = () => ({ type: HELLO_WORLD_REQUEST });
export const helloWorldResponse = text => ({ type: HELLO_WORLD_RESPONSE, text });
export const helloWorldError = error => ({ type: HELLO_WORLD_ERROR, error });
and my sagas.js
import { put, takeLatest } from "redux-saga/effects";
import { HELLO_WORLD_REQUEST, helloWorldResponse, helloWorldError } from "./actions";
function* runHelloWorldRequest(action) {
try {
// TODO: real api call here
yield put(helloWorldResponse("Hello from react-redux-saga :)"));
} catch (e) {
yield put(helloWorldError(e));
}
}
export default function* mySaga() {
yield takeLatest(HELLO_WORLD_REQUEST, runHelloWorldRequest);
}
and my helloWorldReducer.js
import { HELLO_WORLD_RESPONSE } from "../actions";
export default (state = "", { type, text }) => {
switch (type) {
case HELLO_WORLD_RESPONSE:
return text;
default:
return state;
}
};
and this is how put it all together on my App component:
class App extends Component {
componentDidMount() {
this.props.helloWorldRequest();
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>{this.props.responseText}</p>
</header>
</div>
);
}
}
const mapStateToProps = state => ({ responseText: state.helloWorldReducer });
const mapDispatchToProps = dispatch => bindActionCreators({ helloWorldRequest }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
This works fine, but here's the odd behaviour I am trying to understand. In order to get the response value out of the state and map it into props, I had to do this:
const mapStateToProps = state => ({ responseText:
state.helloWorldReducer });
Based on what I saw in the react devtools:
Notice after the request is processed and a response is generated; the resulting state object contains a field called helloWorldReducer.
Where did this come from?
I assumed this field name should have been called text.
P.S. Sorry about the long post; still learning react-redux-saga, so I didn't know which part of my code was relevant to the question at hand.
the resulting state object contains a field called helloWorldReducer.
Where did this come from?
It comes from your root reducer which is actually the reducer created by using the combineReducers() method.
This is your reducers/index.js file which export the root reducer for creating redux store:
import { combineReducers } from "redux";
import helloWorldReducer from "./helloWorldReducer";
export default combineReducers({
helloWorldReducer // here
});

Get data from Redux thunk

I've just started implementing Redux in a React application, and it's the first time i try to, so please bear with me.
My problem is that i can't access the data in my component this this.props.questions
I have a simple action which is supposed to async fetch some data
export function fetchQuestions(url) {
const request = axios.get('url');
return (dispatch) => {
request.then(({data}) => {
dispatch({ type: 'FETCH_QUESTIONS', payload: data });
console.log(data);
});
};
}
Which is picked up my reducer questions_reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
console.log('Getting here');
return state.concat([action.payload.data]);
console.log('But not here');
}
return state;
}
My index reducer looks like this:
import { combineReducers } from 'redux';
import fetchQuestions from './question_reducer';
const rootReducer = combineReducers({
questions: fetchQuestions
});
export default rootReducer;
I pass it to my store where i apply the thunk middleware and finally into <Provider store={store}> which wraps my app, but the prop just returns undefined in my React component
configureStore:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
I don't know if the console.log is to be trusted but it logs from my questions_reducer before the data is returned from the dispatch in my action
EDIT (Component)
class QuestionsRoute extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.fetch('someUrl);
setTimeout(function(){ console.log(this.props.questions) },
1500);
}
render() {
{console.log(this.props.questions)}
return (
<div>
<1>Hello</1>
{this.props.questions !== undefined ?
<p>We like props</p>: <p>or not</p>
}
</div>
);
}
};
const mapStateToProps = (state) => {
return {
questions: state.questions,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetch: () => dispatch(fetchQuestions())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(QuestionsRoute);
In your reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
return state.concat([action.payload.data]);
}
return state;
}
You should probably instead have return state.concat([action.payload]);
Since from dispatch({ type: 'FETCH_QUESTIONS', payload: data }); we see that payload is data, it doesn't contain it.
Update: I'd recommend setting up redux-devtools / redux-devtools-extension / react-native-debugger so you can visually see your actions and store state live - makes things like this a lot easier to debug!

Resources