React Redux - container/component not updating when passing data from reducer - javascript

I'm new to React, please keep this in mind.
I'm trying to render a list of recipes fetched from food2fork API but I can't get the view to update, even if the data is fetched correctly.
Here's recipe_list.js:
import React, { Component } from "react";
import { connect } from "react-redux";
class RecipesList extends Component {
// renderRecipe(recipe) {
// console.log(recipe);
// return (
// <div>{recipe}</div>
// );
// }
render() {
console.log("Render function ", this.props.recipes)
return (
<div>
<p>Recipes</p>
<div>{this.props.recipes}</div>
</div>
);
}
}
function mapStateToProps(state){
return { recipes: state.recipes };
}
export default connect(mapStateToProps)(RecipesList);
Here's reducer_recipes.js:
import FETCH_RECIPES from "../actions/index";
export default function(state = [], action){
switch (action.type) {
case FETCH_RECIPES:
return action.payload;
}
return state;
}
Here's /reducers/index.js:
import { combineReducers } from 'redux';
import RecipesReducer from "./reducer_recipes";
console.log(RecipesReducer);
const rootReducer = combineReducers({
recipes: RecipesReducer
});
export default rootReducer;
Here's /actions/index.js:
import axios from "axios";
const API_KEY = "****************************";
export const URL = `https://food2fork.com/api/search?key=${API_KEY}`;
export const FETCH_RECIPES = "FETCH_RECIPES";
export function fetchRecipes(term){
const url = `${URL}&q=${term}`;
const request = axios.get(url);
return {
type: FETCH_RECIPES,
payload: request
};
}
I don't get any specific error. The view just doesn't update. I tried to spread some console.log around the files to try to understand where the problem is.
It seems like the Reducer is not successfully delivering the payload to the component.
NOTE: I'm using react-promise so the promise returned from axios is automatically resolved.
Any ideas?
===================================================================
EDIT:
Thank you for the useful links but there is clearly something that I'm still missing here.
I have modified the action index:
function getStuffSuccess(response) {
return {
type: FETCH_RECIPES,
payload: response
};
}
function getStuffError(err) {
return {
type: ERROR_FETCH_RECIPES,
payload: err
};
}
export function fetchRecipes(term) {
const url = `${URL}&q=${term}`;
return function(dispatch) {
axios.get(url)
.then((response) => {
dispatch(getStuffSuccess(response));
})
.catch((err) => {
dispatch(getStuffError(err));
});
};
}
I have also included redux-thunk to the store:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from "redux-promise";
import Thunk from 'redux-thunk';
import App from './components/app';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware(Thunk, ReduxPromise) (createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>
, document.querySelector('.container'));
The behaviour hasn't changed from before. The view is still not updating.
NOTE: If I console.log the payload from the Reducer the data is in there. But when I try to do the same in the View nothing happens.

Your action is not synchronus here, you need to use Async Action to deliver the response to reducer, meaning, you have to dispatch the response instead of returning it. Check the given link for more details.

I would try refactoring your /actions/index.js like so:
import axios from 'axios';
export const FETCH_RECIPES = 'fetch_recipes';
export const CREATE_RECIPE = 'create_recipe';
const ROOT_URL = '<url-of-api-endpoint>';
const API_KEY = '<api-key>';
export function fetchRecipes() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_RECIPES,
payload: request
};
}
export function createRecipe(values, callback){
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, values)
.then(() => callback());
return {
type: CREATE_RECIPE,
payload: request
}
}

Related

Reducer is not called even after action is dispatched and payload is received

I've created a react app using create-react-app and react-redux. I dispatch an action using mapDispatchToProps on clicking a button and it returns a payload. But when I try to retrieve the props using mapStateToProps in my component, it returns the initial state.
What am I doing wrong?
I have tried to debug thoroughly and I realize that the action is dispatched and the payload makes it to the action creator. But the reducer isn't triggered after the action is dispatched.
It might be how I am calling it or how I have set up my reducer as well.
index.js file:
import React from 'react';
import './css/index.css';
import App from './App/App';
import * as serviceWorker from './serviceWorker';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk));
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
reducers/Reducer.js
reducers/Reducer.js
import { GET_RESP } from '../modules/actions'
function getInitialState () {
return {
predictions: [],
score: null
}
}
function gesResp (state, payload) {
return {
...state,
predictions: payload.predictions,
score: payload.score
}
}
export function Reducer (state, action) {
if (!state) {
return getInitialState();
}
switch(action.type) {
case GET_RESP:
return gesResp(state, action.payload);
default:
return state;
}
}
export default Reducer;
reducers/index.js
import { combineReducers } from 'redux';
import Reducer from './Reducer'
const rootReducer = combineReducers({
Reducer
});
export default rootReducer;
action.js
import axios from 'axios';
// actions:
export const GET_RESP = 'GET_RESP';
// action creators:
export function gesResp (payload) {
return {
type: GET_RESP,
payload: payload
}
}
export function fetchRecommendations (description, resp) {
let url = 'myendpointurl';
let requestPayload = {
description: description,
resp: resp
}
return (dispatch) => {
return axios.post(url, requestPayload)
.then(function(res) {
gesResp(res.data);
})
}
}
component file: (I'm only posting related code):
handleSubmit () {
this.props.fetchMyRecommendations(Desc,
this.state.htmlContent);
}
const mapStateToProps = state => {
return {
predictions: state.Reducer.predictions,
score: state.Reducer.score
};
}
const mapDispatchToProps = dispatch => {
return {
fetchMyRecommendations: (Desc, userScore) =>
dispatch(fetchRecommendations(Desc, userScore))
};
}
export default connect(mapStateToProps, mapDispatchToProps)
(HomePage);
Ideally what I want is in the mapStateToProps to return the predictions array and the resp score.
I can see that they are being returned in the network call and showing up the actions call as well. Thanks in advance to whoever can help! :)
You need to dispatch getReccommendations to actually trigger the reducer for your asynchronous action. Try the following:
export function fetchRecommendations (job_description, resume) {
let url = 'myendpointurl';
let requestPayload = {
job_description: job_description,
resume: resume
};
return (dispatch) => {
return axios.post(url, requestPayload)
.then(function(res) {
dispatch(getReccommendations(res.data));
});
}
}
Hopefully that helps!

ReactJS: Redux state is not changing

I'm just starting with React and Redux and stumbled upon something I can't figure out by myself - I think that Redux state is not changing and it's causing (some of) errors. I'm checking state with use of remote-redux-devtools#0.5.0.
My code:
Categories.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getCategories } from '../../actions/categories';
export class Categories extends Component {
static propTypes = {
categories: PropTypes.array.isRequired
};
componentDidMount() {
this.props.getCategories();
}
render() {
return (
<div>
Placeholder for categories.
</div>
)
}
}
const mapStateToProps = state => ({
categories: state.categories.categories
});
export default connect(mapStateToProps, { getCategories })(Categories);
../../actions/categories.js:
import axios from "axios";
import { CATEGORIES_GET } from "./types";
export const getCategories = () => dispatch => {
return axios
.get("/api/notes/categories/")
.then(res => {
dispatch({
type: CATEGORIES_GET,
payload: res.data
});
})
.catch(err => console.log(err));
};
reducers/categories.js:
import { CATEGORIES_GET } from '../actions/types.js';
const initialState = {
categories: []
};
export default function (state = initialState, action) {
switch (action.type) {
case CATEGORIES_GET:
return {
...state,
categories: action.payload
};
default:
return state;
}
}
store.js:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'remote-redux-devtools';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)));
export default store;
reducers/index.js
import { combineReducers } from "redux";
import categories from './categories';
export default combineReducers({
categories,
});
Using remote-redux-devtools, I've never seen anything in my state. Currently this code above gives me 3 errors, two of them being
this.props.getCategories is not a function
My guess is that because there is some issue with Categories class, it's not passing anything to state and it could be root cause of errors. I had one more error, connected to Categories not being called with attributes, but for debug purposes I put empty array there - one error dissapeared, but that's it. I've also tried adding constructor to Categories and called super(), but did not help also.
I believe your issue is that you're exporting your Categories class twice, once connected, the other not.
If you remove export from export class Categories extends Component, does it work as expected?
When you're mapping the state in a component, you must access the desired variable through a reducer.
So instead of:
const mapStateToProps = state => ({
categories: state.categories
});
You must use:
const mapStateToProps = state => ({
categories: state.categories.categories
});
Your props don't have getCategories method, because you didn't pass it as a function to connect.
A better approach is to define only the action code in your actions file and then use mapDispatchToProps.
../../actions/categories.js
import axios from "axios";
export const getCategories = () => {
axios
.get("/api/notes/categories/")
.then(res => res.data);
})
.catch(err => console.log(err));
};
Categories.js
import { getCategories } from '../../actions/categories'
import { CATEGORIES_GET } from "./types";
const mapDispatchToProps = dispatch => {
return {
getCategories: () => dispatch(() => { type: CATEGORIES_GET, payload: getCategories() }),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Categories);

mapDispatchToProps() in Connect(App) must return a plain object. Instead received [object Promise]

I am new to React and building a Spotify App with their API. I am using Redux Promise to resolve all promises. I can see data when I console it in my reducer of the data. But when I check my console it shows mapDispatchToProps() in Connect(App) must return a plain object. Instead received [object Promise]. I am thinking is it because I'm using Redux Promise vs thunk, but shouldn't it be able to resolve them as well?
Reducer
import { NEW_RELEASES } from '../actions/types';
export default function(state = [] , action){
console.log(action)
switch(action.type){
case NEW_RELEASES:
return [ action.payload.data, ...state ];
}
return state
}
Store
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import App from './App';
import reducers from './reducers';
import ReduxPromise from 'redux-promise'; // Look at action creator for ReduxPromise use
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>
, document.querySelector('#root'));
Action Creator
export const getNewReleases = () => {
console.log('ran')
let request = axios.get("https://api.spotify.com/v1/browse/new-releases?country=SE", {
headers: {
'Authorization': 'Bearer ' + accessToken
}
})
return{
type: NEW_RELEASES,
payload: request
}
App.Js
import React, { Component } from 'react';
import './App.css';
import Spotify from 'spotify-web-api-js';
import { getNewReleases } from './actions'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
const spotifyWebApi = new Spotify();
class App extends Component {
constructor(props) {
super(props)
const params = this.getHashParams();
this.state = {
welcome: "Welcome to SpotiDate",
accessToken: params.access_token,
loggedIn: params.access_Token ? true : false,
nowPlaying: {
name: 'Not Checked',
images: ''
}
}
if (params.access_token) {
spotifyWebApi.setAccessToken(params.access_token);
}
}
getHashParams() {
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while (e = r.exec(q)) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
componentWillMount() {
spotifyWebApi.setAccessToken(this.state.accessToken)
localStorage.setItem('accessToken', this.state.accessToken);
const storedToken = localStorage.getItem('accessToken');
getNewReleases(storedToken);
}
render() {
return (
<div className="App">
<h3>{this.state.welcome}</h3>
<div>
<img src={this.state.nowPlaying.image} />
</div>
<button onClick={() => getNewReleases()}>Get New Releases</button>
<div className="new-releases">
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return{
newReleases: state.newReleases
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(getNewReleases, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
the function bindActionCreators will take 1st argument as JSON. so it should be like below.
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
getNewReleases: getNewReleases
},
dispatch
);
};
try to use Object.assign({}, state.newReleases), or you can use the spread operator like assigning the code just like return {...state,state.newReleases}
since your object is unable to be mapped to the object state
For further understanding of this, please check this link git issue - 334
Try returning you actions in mapDispatchToProps like this:
const mapDispatchToProps = (dispatch) => ({
getNewReleases: () => dispatch(getNewReleases())
})

Redux action not triggering reducers

Problem
I wired up my react application with a Redux store, added an api action to gather data from my backend including middleware redux-promise. Most everything seems to work as I can see my store in the React web editor along with the combine reducer keys. When I have my action called, it works and console logs the completed promise. However, my reducers never run. I thought it was an issue with my dispatch on the main container, but I've tried every way that I can think of at this point - regular dispatch() and bindActionCreators. HELP!
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import promiseMiddleware from 'redux-promise';
import RootReducer from './reducers';
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware)(createStore)
let store = createStore(RootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));`
Combine Reducers
import { combineReducers } from 'redux';
import ReducerGetPostings from './reducer_get_postings'
const rootReducer = combineReducers({
postingRecords: ReducerGetPostings
})
export default rootReducer;
Reducer
import { FETCH_POSTINGS } from '../actions/get_postings'
export default function (state = null, action) {
console.log('action received', action)
switch (action.type) {
case FETCH_POSTINGS:
return [ action.payload ]
}
return state;
}
Action API
import axios from 'axios';
import { url } from '../api_route';
export const FETCH_POSTINGS = 'FETCH_POSTINGS'
export function fetchpostings() {
const postingRecords = axios.get(`${url}/api/postings`)
console.log('Postings', postingRecords)
return {
type: FETCH_POSTINGS,
payload: postingRecords
};
}
Container
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchpostings } from '../../actions/get_postings.js'
class Dashboard extends Component {
//....lots of other functionality already built here.
componentDidMount() {
axios.get(`${url}/api/postings`)
.then(res => res.data)
.then(
(postingRecords) => {
this.setState({
postingData: postingRecords,
postingOptions: postingRecords
});
},
(error) => {
this.setState({
error
})
}
)
// primary purpose is to replace the existing api call above with Redux Store and fetchpostings action creator
fetchpostings()
}
}
function mapDispatchToProps(dispatch) {
// return {actions: bindActionCreators({ fetchpostings }, dispatch)}
return {
fetchpostings: () => dispatch(fetchpostings())
}
}
export default connect(null, mapDispatchToProps)(Dashboard);
You are not dispatching your action, when you call fetchpostings() in componentDidMount you are calling the method imported from actions/get_postings.js, not the method that will dispatch.
Try this.props.fetchpostings() instead.
You also did not bind state to props you need to do that as well.

Cannot read state from Redux store. What did I miss?

Here is my index.js where I initially dispatch an action to read my list of locations:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import configureStore from './store/configureStore';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import {loadLocationList} from './actions/locationActions';
import './css/styles.css';
const store = configureStore();
render(
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>,
document.getElementById('app')
);
Then here is my action where I get the data & then create an action out of it:
export function loadLocationListSuccess(alistingData) {
return { type: types.LOAD_LOCATION_LIST_SUCCESS, listingData: alistingData};
}
export function loadLocationList() {
return function(dispatch){ //we return a function that accepts a parameter, we just called it dispatch
//dispatch(fetchCallActions.fetchCallStart("")); // we dispatch a function fetchCallStart to indicate the start of our call, this is to keep in check with our asynchronous function calls
let link = 'http://example.com:8399/location';//our fetch url
console.log(link); //we log our link, just for debug purposes
return fetch(link) //start fetch
.then(function(response) {
return response.json();
}).then(function(json) {
dispatch(loadLocationListSuccess(json));
}).catch(function(ex) {
console.log('parsing failed', ex);
});
};
}
Then here is my reducer:
import * as types from '../actions/actionTypes';
export default function locationReducer(state = [], action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return {listingData: action.listingData};
default:
return state;
}
}
Then here is my mapStateToProps & connect function:
function mapStateToProps(state, ownProps) {
return {
// we'll call this in our component -> this.props.listingData
listingData: state.listingData
};
}
export default connect(mapStateToProps, mapDispatchToProps)(homePage);
For some reason, it cannot read state.listingData or am I actually doing it wrongly? Anyone can help me with this problem?
I tried logging state.listingData and it showed undefined
Here is my configureStore:
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk, reduxImmutableStateInvariant())
);
}
Here is my combined Reducer:
import {combineReducers} from 'redux';
import courses from './courseReducer';
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
export default rootReducer;
Did I not connect it to the store properly?
Recent update:
Logging JSON.stringify(state) in mapStateToProps would actually shows the result. Thanks guys.
The correct path turned out to be state.locations.listingData because I think in my combined Reducer I included the reducer as locations so maybe thats why the state for it is state.locations. Hope this helps anyone with the problem.
Can you show the code of configureStore file? The problem might be there, may be you forgot to add reducer to list of reducers.
Does the action works right? Did you log data before dispatch(loadLocationListSuccess(json));?
UPD:
Because of rootReducer. Each reducer creates their own key in store. When you combine your reducers in rootReducer, like:
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
It creates store with this kind of structure:
const store = {
courses: {},
locations: {}
}
So, after that you dispatched action and reducer changed the data to this:
const store = {
courses: {},
locations: {
listingData: someData
}
}
If you want to access to listingData like: state.listingData, you need to change a little your reducer and combineReducer to:
export default function listingData(state = {}, action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return action.listingData;
default:
return state;
}
}
...
import listingData from './locationReducer';
const rootReducer = combineReducers({
courses,
listingData
});

Categories

Resources