react-redux how to render response data from api - reactjs

I followed one of the tutorial from youtube. When I do that it works completely fine. But when I try different api, I'm getting an error items are not defined. Can someone please help me to understand what I am doing wrong.
Thanks
//actions
import axios from 'axios'
export function fetchTweets(brandUrl, responseCode){
let url = brandUrl + '/api/offer/' + responseCode;
return function(dispatch){
axios.get(url)
.then((response) => {
dispatch({
type: 'FETCH_TWEETS_FULFILLED',
payload: response.data
})
})
.catch((error) => {
dispatch({
type: 'FETCH_TWEETS_REJECTED',
payload: error
})
})
}
}
//reducer
export default function reducer(state = {
tweets: [],
fetching: false,
fetched: false,
error: null
}, action) {
switch(action.type){
case 'FETCH_TWEETS_PENDING' :{
return { ...state, fetching: true }
}
case 'FETCH_TWEETS_REJECTED' : {
return { ...state, fetching: false, error: action.payload }
}
case 'FETCH_TWEETS_FULFILLED' : {
return { ...state,
fetching: false,
fetched: true,
tweets: action.payload }
}
}
return state
}
//main component
import React from 'react'
import { connect } from 'react-redux'
import { fetchTweets } from '../actions/tweetsActions'
class Layout extends React.Component{
fetchTweets(){
this.props.dispatch(fetchTweets(brandUrl, responseCode))
}
render(){
const { tweets } = this.props;
if(!tweets.length){
return <button value="Load" onClick={this.fetchTweets.bind(this)}>Load </button>
}
console.log(tweets.response.mainItems.length)
return (
<div>
<p>{tweets.statusMessage}</p>
<ul>
</ul>
</div>
);
}
}
function mapStateToProp(state){
return {
tweets : state.tweets.tweets
}
}
export default connect(mapStateToProp)(Layout)
//store
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import logger from 'redux-logger'
import reducer from './reducers'
const middleware = applyMiddleware(promise(), thunk, logger())
export default createStore(reducer, middleware)
// API Response

in main component's mapStateToProps function ,why you are accessing tweets by assigning state.tweets.tweets ,though you dont have reducer named tweets ,and you are not using combineReducer function (which one use in case of multiple reducer).
So you can easily access tweets by writing this.state.tweets .and you can print state in mapStateToProps before returning which might be helpful for debugging......
//main component
function mapStateToProp(state){
console.log(state,"=====>")
return {
tweets : state.tweets
}

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`
});

User data coming back undefined with Redux React App

Currently I am trying to pass user data through my react app with Redux. I have created a user API with a django backend that is definately working, as I am able to go the url and see all the json that comes out of it. However, when I try to pass it into a component I keep getting undefined. Here is my code:
userActions.js:
import Axios from "axios";
export function getUser() {
const id = this.params.match.id
return dispatch => {
dispatch(fetchUserBegin());
return Axios.get(`/api/user/${id}`)
.then((res) => {
this.setState({
user: res.data,
})
})
}
}
export const FETCH_USER_BEGIN = 'FETCH_USER_BEGIN';
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
export const fetchUserBegin = () => ({
type: FETCH_USER_BEGIN
});
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: { user }
});
export const fetchUserFailure = error => ({
type: FETCH_USER_FAILURE,
payload: { error }
});
userReducer.js
import { FETCH_USER_BEGIN, FETCH_USER_SUCCESS, FETCH_USER_FAILURE } from '../actions/actionTypes'
const initialState = {
user: {},
loading: false,
error: null
};
export default function productReducer(state = initialState, action) {
switch(action.type) {
case FETCH_USER_BEGIN:
// Mark the state as "loading" so we can show a spinner or something
// Also, reset any errors. We're starting fresh.
return {
...state,
loading: true,
error: null
};
case FETCH_USER_SUCCESS:
// All done: set loading "false".
// Also, replace the items with the ones from the server
return {
...state,
loading: false,
user: action.user
};
case FETCH_USER_FAILURE:
// The request failed, but it did stop, so set loading to "false".
// Save the error, and we can display it somewhere
// Since it failed, we don't have items to display anymore, so set it empty.
// This is up to you and your app though: maybe you want to keep the items
// around! Do whatever seems right.
return {
...state,
loading: false,
error: action.payload.error,
user: {}
};
default:
// ALWAYS have a default case in a reducer
return state;
}
}
And the display component:
UserInformation.js:
import React from "react";
import { connect } from "react-redux";
import { getUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
this.props.dispatch(getUser());
}
render() {
const { user } = this.props;
console.log(user)
return (
<ul>
{user.map(user =>
<li key={user.id}>{user.username}</li>
)}
</ul>
);
}
}
const mapStateToProps = state => ({
user: state.user,
});
export default connect(mapStateToProps)(UserDetailView);
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore, compose, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import reducer from './store/reducers/auth';
const composeEnhances = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhances(
applyMiddleware(thunk)
))
const app = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();
Anyone got any ideas why this isn't working?
You're not supposed to setState() in that action creator:
this.setState({
user: res.data,
})
you should dispatch an action instead
Try this:
export function getUser() {
const id = this.params.match.id
return dispatch => {
dispatch(fetchUserBegin());
return Axios.get(`/api/user/${id}`)
.then( res => {
dispatch(fetchUserSuccess(res.data);
})
}
}
You should pass the mapDispatchToProps function to the connect() method as the second argument, like this:
import React from "react";
import { connect } from "react-redux";
import { getUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
this.props.getUser() //fixed
}
render() {
const { user } = this.props;
console.log(user)
return (
<ul>
{user.map(user =>
<li key={user.id}>{user.username}</li>
)}
</ul>
);
}
}
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({ //added
getUser: dispatch(getUser())
})
export default connect(mapStateToProps,mapDispatchToProps)(UserDetailView); //fixed
And also fix this:
case FETCH_USER_SUCCESS:
// All done: set loading "false".
// Also, replace the items with the ones from the server
return {
...state,
loading: false,
user: action.payload.user //fixed
};

Components not having the expected props, while React and Redux Dev Tools are having the expected States and Props

I am learning react-redux, so I decided to implement what I have been learning. But I am have a bug challenge. So I console.logged this.props.users from mapStateToProps function.
I believe there's something I not doing right which I don't understand. Please an explanation in other to move on. Thanks you so much for helping out.
Here is my code.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUsers } from '../actions/userAction';
import UserList from '../components/UserList';
class UserPage extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchUsers();
}
componentDidMount() {
console.log(this.props.users);
}
render() {
return (
<div>
<h2>Users Page</h2>
<UserList users={this.props.users} />
</div>
);
}
}
const mapStateToProps = state => {
return {
users: state.userReducer.users
};
};
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () => dispatch(fetchUsers())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserPage);
So this is what I get from the chrome console - Empty arrays.
props showing empty arrays
But when I check the React DevTool and Redux DevTool, they display the expected Props and States respectively. Below are the snapshot of the dev tools
React devtool shows the correct Props
Redux devtool show the correct States and Actions
userAction.js
import axios from 'axios';
import * as types from './actionTypes';
export let fetchingUser = () => {
return {
type: types.FETCHING_USERS
};
};
export let fetchedUser = payload => {
return {
type: types.FETCHED_USER,
payload
};
};
export let fetchUser_error = () => {
return {
type: types.FETCH_USER_ERROR
};
};
export let fetchUsers = () => {
let url = 'https://eventcity.herokuapp.com/api/v1/users';
return dispatch => {
dispatch(fetchingUser());
return axios
.get(url)
.then(response => {
const users = response.data.data;
dispatch(fetchedUser(users));
})
.catch(err => {
dispatch(fetchUser_error());
});
};
};
userReducer.js
import * as types from '../actions/actionTypes';
import initialState from './initialState';
const userReducer = (state = initialState, action = {}) => {
switch (action.type) {
case types.FETCHING_USERS:
return { ...state, users: [], error: null, loading: true };
case types.FETCHED_USER:
return { ...state, users: action.payload, error: null, loading: false };
case types.FETCH_USER_ERROR:
return {
...state,
users: [],
error: { message: 'Error loading data from the API' },
loading: false
};
default:
return state;
}
};
export default userReducer;
configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducer/rootReducer';
const configureStore = () => {
return createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
};
export default configureStore;
rootReducer.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
const rootReducer = combineReducers({
userReducer
});
export default rootReducer;
I think you might want to check this
https://github.com/reactjs/react-redux/issues/129. Your problem is using componentDidMount and componentWillMount without having a better understanding of what they are used for.
The problem is not with redux, all you need to understand is that your fetchUsers request is async and componentDidMount function is only executed once after the component has rendered and it may so happen that the data is not present by the time componentDidMount function is executed and hence your console.log(this.props.users); return empty array, Log it in the render method and you will see the correct data
class UserPage extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchUsers();
}
render() {
console.log(this.props.users);
return (
<div>
<h2>Users Page</h2>
<UserList users={this.props.users} />
</div>
);
}
}

reducer isn't triggered (redux-promise with axios)

I'm trying to make an api call in action with axios and pass the results of it to the reducer.
Though action is triggered, reducer isn't. And I can't understand why.
Here's the component that should make api call before mounting
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
//actions
import { getPost } from '../actions/';
class PostShow extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
getPost(this.props.params.id);
}
render() {
console.log(this.props.activePost);
return (
<div>
<h1> hello from a post</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
activePost: state.posts.activePost
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
getPost
}, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(PostShow);
Here's my action
import axios from 'axios';
import { FETCH_POSTS, SEND_POST, FETCH_POST } from './types';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty';
export function fetchPosts() {
const req = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: req
}
}
export function sendPost(props) {
const req = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return {
type: SEND_POST,
payload: req
}
}
export function getPost(id) {
console.log('action triggered');
const req = axios.get(`${ROOT_URL}/posts/${id}${API_KEY}`);
return {
type: FETCH_POST,
payload: req
}
}
And here's my reducer
import { FETCH_POSTS, FETCH_POST } from '../actions/types';
const INITIAL_STATE = {
allPosts: [],
activePost: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
allPosts: action.payload.data
};
case FETCH_POST:
console.log('reducer triggered');
return {
...state,
activePost: action.payload.data
};
default:
return state;
}
}
As a result I see 'action triggered' coming from console.log in action, and null coming from console.log in component, and no console.log from the reducer, so it's not triggered and I have no data to render in my component.
Though I make a request and get a response from server with the data, it doesn't go to the reducer. (moreover case FETCH_POSTS works fine and I can render a list of posts, but not a particular one).
"axios": "^0.17.0"
"redux-promise": "^0.5.3"
You need to use this.props.getPost in componentDidMount instead of getPost.
Connect sends bound action creator to component as a prop

Redux Persist state is not storing when fetching api using axios

I am fetching an api with axios and the action is being fired after the "persist/REHYDRATE" action resulting in the following
"redux-persist/autoRehydrate: 1 actions were fired before rehydration completed...."
If I delete a tweet one by one and then refresh my browser, it does not store the state. Can't seem to crack this..
Client.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from "react-redux"
import { compose, applyMiddleware, createStore } from 'redux';
import logger from "redux-logger"
import thunk from "redux-thunk"
import promise from "redux-promise-middleware"
import {persistStore, autoRehydrate} from 'redux-persist'
import tweetApp from "./reducers"
import Layout from "./components/Layout"
import { REHYDRATE } from 'redux-persist/constants'
import createActionBuffer from 'redux-action-buffer'
//const middleware = applyMiddleware(promise(), thunk, logger())
let enhancer = compose(
autoRehydrate({ log: true }),
applyMiddleware(
promise(), thunk, logger(), createActionBuffer(REHYDRATE)
)
)
const store = createStore(
tweetApp,
enhancer
);
const persistConfig = {
whitelist : ["tweets"]
};
persistStore(store, persistConfig);
render(
<Provider store={store}>
<Layout />
</Provider>,
document.getElementById('app')
);
tweetsReducer.js
import {REHYDRATE} from 'redux-persist/constants'
export default function reducer(state={
tweets: [],
fetching: false,
fetched: false,
error: null,
}, action) {
switch (action.type) {
case "persist/REHYDRATE": {
const incoming = action.payload.tweets; // Carts is the name of the reducer
if (incoming) return {...state, ...incoming}
}
case "FETCH_TWEETS": {
return {...state, fetching: true}
}
case "FETCH_TWEETS_REJECTED": {
return {...state, fetching: false, error: action.payload}
}
case "FETCH_TWEETS_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
tweets: action.payload,
}
}
case "ADD_TWEET": {
return {
...state,
tweets: [...state.tweets, action.payload],
}
}
case "UPDATE_TWEET": {
const { id, text } = action.payload
const newTweets = [...state.tweets]
const tweetToUpdate = newTweets.findIndex(tweet => tweet.id === id)
newTweets[tweetToUpdate] = action.payload;
return {
...state,
tweets: newTweets,
}
}
case "DELETE_TWEET": {
return {
tweets: [
...state.tweets.slice(0, action.payload),
...state.tweets.slice(action.payload + 1)
],
}
}
}
return state
}
tweetsActions.js
import axios from "axios";
export function fetchTweets() {
return function(dispatch) {
axios.get("http://rest.learncode.academy/api/test123/tweets")
.then((response) => {
dispatch({type: "FETCH_TWEETS_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_TWEETS_REJECTED", payload: err})
})
}
}
export function addTweet(id, text) {
return {
type: 'ADD_TWEET',
payload: {
id,
text,
},
}
}
export function updateTweet(id, text) {
return {
type: 'UPDATE_TWEET',
payload: {
id,
text,
},
}
}
export function deleteTweet(id) {
return { type: 'DELETE_TWEET', payload: id}
}
layouts.js
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
import { deleteTweet } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
this.props.dispatch(fetchTweets())
}
fetchTweets() {
//this.props.dispatch(fetchTweets())
}
deleteTweet(idx, e) {
this.props.dispatch(deleteTweet(idx))
}
render() {
const { user, tweets, i } = this.props;
//console.log(this.props)
const mappedTweets = tweets.map((tweet, i) => <li key={i}>{tweet.text}<button onClick={this.deleteTweet.bind(this, i)}>delete</button></li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
UPDATE AND SAME ISSUE:
I tried replacing "componentWILLMount() with componentDidMount()" and the issue still occurs. See logged output:
Move your fetchTweets and fetchUser calls to componentDidMount, otherwise all your code is executed synchronously: from the store being created to your Layout being instantiated and rendered.
componentWillMount is called before render, while componentDidMount is called after the component has been rendered for the first time.

Resources