I'm trying to use React-Redux to render the content of a small JSON.
I'm able to get the content via an Action :
(part of actions.js)
export const getUsersCount = () => {
return function (dispatch, getState) {
connectBack.get('users/count', {
headers: {
'Authorization': getState().current_user.token
}
})
.then( response => {
console.log(response.data)
dispatch(countUsers(response.data))
})
.catch( error => {
console.log('Count ', error)
})
}
}
export const countUsers= (users) => {
return {
type: 'COUNT_USERS',
payload: users
}
}
The console will correctly render the content of response.data (in this case : {users: 6}).
Then I want to render it on a simple page but it's undefined, I'm missing something...
import React from 'react'
import { connect } from 'react-redux'
import { getUnfulfilledCount, getUsersCount } from './../../tools/actions'
class Dashboard extends React.Component {
render () {
let statsUsers = this.props.usersCount()
console.log(statsUsers)
return(
<div className="container">
<div className="row">
Dashboard <br/>
Number of registered users: <br/>
{statsUsers}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
}
}
const mapDispatchToProps = (dispatch) => {
return {
usersCount: () => { dispatch(getUsersCount()) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
In addition to it, I've got the reducer set as follow:
import * as actionTypes from './actionTypes'
const initialState = {
users:[]
}
const reducer = (state = initialState, action ) => {
switch (action.type) {
case actionTypes.COUNT_USERS:
return {
...state,
users: action.payload
}
Where actionTypes.js is :
export const COUNT_USERS = 'COUNT_USERS'
The exact render from the console :
undefined Dashboard.js:12
{users: 6} actions.js:315
Obviously on the display the variable is just skipped as statsUsers is undefined:
"Number of registered users:"
I think your main problem lies in how you are mapping the resultant data back to the Dashboard component. You have a call to mapStateToProps that is empty, and a call to mapDispatchToProps that isn't necessary. I would remove the latter, and map the users property from the store in the props where you can access it in the Dashboard component, then render this.props.users instead.
Also note that by having your render function call usersCount, which maps to a call to dispatch(getUsersCount()), you're making an API call inside your render function. Making API calls while rendering is kind of a big no-no, and should be done in another place like componentDidMount.
See working Codepen here. It rewrites a little bit of your code, and adds a feature of an emulated delayed network response.
Code not enough for understand, Maybe this example will help you.
const myJsonData = [
{0: {name: "Amoos John Ghouri"}},
{1: {name: "GMK Hussain"}},
{2: {name: "Talib Hussain"}}
];
class DemoApp extends React.Component {
render() {
const items = this.props.myJsonData.map((d,i) => (<li>{d[i].name}</li>));
return (
<div>
<ul>
{items}
</ul>
</div>
)
}
}
ReactDOM.render(
<DemoApp myJsonData={myJsonData} />,
DemoAppDiv
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="DemoAppDiv"></div>
Related
In my app component i have list of posts that contains user id, i want to display the user name and details against that user id, here's my app component's jsx:
App Component JSX:
render() {
const posts = [...someListOfPosts];
return posts.map((post) => {
return (
<div className="item" key={post.id}>
<div className="content">
<User userId={post.userId} />
</div>
</div>
);
});
}
User Component
import React from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class UserHeader extends React.Component {
componentDidMount() {
this.props.fetchUser(this.props.userId); // getting correct userId
}
render() {
const { user } = this.props;
// Not displaying correct user i.e. showing the last resolved user for each post
return (
<div>
{user && <div className="header">{user.name}</div>}
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
user: state.user
};
}
export default connect(mapStateToProps, { fetchUser })(UserHeader);
I'm getting correct props for userId but for every post it displays the last resolved user from the api. It should be relevant user for every post.
Reducer and Action Creator
// action
export const fetchUser = (id) => {
return async (dispatch) => {
const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
dispatch({
type: 'FETCH_USER',
payload: (response.status === 200 && response.data) ? response.data : null; // it returns single user not array of user
});
}
}
// reducer
export default (state = null, action) => {
switch (action.type) {
case 'FETCH_USER':
return action.payload; // i know it can be fixed by defaulting state to empty array and returning like so [...state, action.payload] but why should i return complete state why not just a single user object here?
default:
return state;
}
}
The fetchUser action creator returns single payload of a user not an array then why it's required to return the state like [...state, action.payload] why can't it be done by returning action.payload only? I've tried it by returning only action.payload but in my user component it displays the last resolved user from the api every time for each post. I'm confused regarding this.
You are subscribing to the store using mapStateToProps which rerenders when ever there is a change in the store. As you are trying to render via props in User component, the application retains the last value of user and re-renders all the old User Components as well. If you want to ignore the props updates make the result local to the component.
You can possibly try this:
import React from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class UserHeader extends React.Component {
constructor(props){
super(props);
this.state={
userDetails:{}
}
}
componentDidMount() {
fetch(https://jsonplaceholder.typicode.com/users/${this.props.userId})
.then(res => res.json())
.then(
(result) => {
this.setState({
userDetails: result.data
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: false
});
}
)
}
render() {
return (
<div>
{this.state.userDetails && <div className="header">{this.state.userDetails.name}</div>}
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
};
}
export default connect(mapStateToProps, { fetchUser })(UserHeader);
I get my action called in Redux Dev Tools and even the new state, but in the actual Component props is undefined.
The component:
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getPromos } from '../../actions/promo';
import PropTypes from 'prop-types';
const Landing = ({ getPromos, data }) => {
useEffect(() => {
getPromos();
console.log(data) // ==>> "UNDEFINED"
}, []);
return (
<div>
<section className='landing'>
<div className='dark-overlay'>
<div className='landing-inner'>
<h1 className='x-large'> Developer Connector </h1>
<p className='lead'>
Create a developer profile/portfolio, share posts and get help
from other developers
</p>
<div className='buttons'>
<Link to='/register' className='btn btn-primary'>
Sign Up
</Link>
<Link to='/login' className='btn btn-light'>
Login
</Link>
</div>
</div>
</div>
</section>
</div>
);
};
Landing.propTypes = {
getPromos: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
data: state.data
});
export default connect(
mapStateToProps,
{ getPromos }
)(Landing);
Actions:
import axios from 'axios';
import { setAlert } from './alert';
import { GET_PROMOS, REGISTER_FAIL } from './types';
export const getPromos = () => async dispatch => {
try {
const res = await axios.get('/api/promo');
dispatch({
type: GET_PROMOS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({ type: REGISTER_FAIL });
}
};
And reducer:
import { GET_PROMOS } from '../actions/types';
const initialState = {
data: null,
title: ''
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROMOS:
return { ...state, data: payload };
default:
return state;
}
}
Like I said, in Redux Dev Tools I get my desired output. But for some reason I cant get to echo this state in the component. What im getting wrong? Can it be something about the hooks?
Thanks !
First thing that jumps at me is that you have a naming conflict with the getPromos in your component, it's defined in the imports as getPromos then it's destructured in the component as { getPromos } as well. I'm surprised you didn't get an error there for naming conflicts.
You will want to NOT destructure getPromos in the component and instead call it as (props) => { props.getPromos } to actually call the connected action creator instead of the unconnected one.
Second, Is that reducer the main root reducer? or is it nested in the root reducer? if the latter is true then in your mapStateToProps the data prop should be one level deeper, as in state: state.rootLevelState.data
(sorry can't ask questions in the comments due to reputation < 50)
enter image description here
Here's a screenshot of the redux dev tools
I'm wanting to update my trending array with the results calling the tmdb api. I'm not sure if im going about this the wrong way with calling the api or if im messing up somewhere else along the way. So far I've really been going in circles with what ive tried. Repeating the same things and not coming to a real solution. Havent been able to find another question similar to mine.
my actions
export const getTrending = url => dispatch => {
console.log("trending action");
axios.get(url).then(res =>
dispatch({
type: "TRENDING",
payload: res.data
})
);
};
my reducer
const INITIAL_STATE = {
results: [],
trending: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case "SEARCH_INFO":
return {
results: [action.payload]
};
case "TRENDING":
return { trending: action.payload };
default:
return state;
}
};
and my component im trying to get the results from
import React, { Component } from "react";
import Trending from "./Treding";
import "../App.css";
import { getTrending } from "../actions/index";
import { connect } from "react-redux";
export class Sidebar extends Component {
componentDidMount = () => {
const proxy = `https://cors-anywhere.herokuapp.com/`;
getTrending(`${proxy}https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
render() {
return (
<div>
<h3 className="trending">Trending</h3>
{
this.props.trending ? (
<Trending movies={this.props.trending} />
) : (
<div>Loading</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
trending: state.trending
};
};
export default connect(mapStateToProps)(Sidebar);
Since you are directly calling the getTrending without passing it to connect method, it might be the issue.
Instead that you can pass getTrending to connect method so it can be available as props in the component. After that it can be dispatched and it will be handled by redux/ redux-thunk.
export default connect(mapStateToProps, { getTrending })(Sidebar);
And access it as props in the component.
componentDidMount = () => {
// const proxy = `https://cors-anywhere.herokuapp.com/`;
this.props.getTrending(`https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
I have a web app that fetches recipe from a backend API. When the feed component mounts, I set an axios get method to receive data from the API and update my redux store and then update the components state to the props matched to state of the redux store using mapPropsToState.
It works when the component is rendered initially, but moving to another component, say Create Recipe and then switching back to the Feed component, the content flashes for a mini second ad then disappears. And shows 'No Recipes To Show' which is what I set to display when there are no recipes.
I have tried using the setState in the componentDidMount method and then also in the .then method of axios, and also in both, simultaneously. Still same result. I have also tried logging the state to the console and it shows that it received the data well all the times that I switched back and forth between components, but the data wont display on screen.
FEED.JS
import React, {Component} from 'react';
import RecipeCard from './RecipeCard';
import {connect} from 'react-redux';
import {updateRecipes} from '../actions/recipeActions'
import axios from 'axios'
class Feed extends Component {
state = {
recipes: []
};
feedTitleStyle = {
color: 'rgba(230, 126, 34, 1)',
margin: '28px 0'
};
componentDidMount() {
axios.get('http://127.0.0.1:8000/api/recipes/')
.then(res =>{
console.log(res);
this.props.updateRecipesFromAPI(res.data);
this.setState({
recipes: this.props.recipes
})
})
.catch(err => {
console.log(err)
});
let recipes = [...this.state.recipes, this.props.recipes];
this.setState({
recipes
})
}
render() {
const {recipes} = this.state;
console.log(this.props.recipes);
console.log(recipes);
const recipesList = recipes.length ? (
recipes.map(recipe => {
return (
<div className="container" key={recipe.id}>
<div className='col-md-10 md-offset-1 col-lg-9 mx-auto'>
<div className="row">
<div className="col s12 m7">
<RecipeCard recipe={recipe}/>
</div>
</div>
</div>
</div>
)
})
) : (
<div className='center'>No recipes yet</div>
);
return (
<div>
<div className='container'>
<h4 style={this.feedTitleStyle} className='center feed-title'>Feed</h4>
{recipesList}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return{
recipes: state.recipes
}
};
const mapDispatchToProps = (dispatch) => {
return {
updateRecipesFromAPI: (recipes) => {dispatch({
type: 'UPDATE_RECIPES',
recipes
}}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Feed)
Here is my reducer:
const initialState = {
recipes: [],
};
const recipeReducer = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_RECIPES':
let updatedRecipes = [...state.recipes, action.recipes];
console.log(updatedRecipes[0]);
return {
...state,
recipes: updatedRecipes[0]
};
default:
return state
}
};
export default recipeReducer
You are juggling between REDUX and State which is wrong, you should not be doing this, instead, the ideal solution would be to stick on with REDUX and let REDUX do the async call and fill in the store, and make use of the mapStateToProps to get it into props.
use Action Creators ( Async ) to solve this, you should be using middleware like thunk (Thunk) to do this.
Action creators:
export const updateRecipesFromAPI_Async = () => { // async action creator
return dispatch => {
axios.post('http://127.0.0.1:8000/api/recipes/')
.then(response => {
console.log(response.data);
dispatch(updateRecipesFromAPI_Success(response.data.name, orderData)); // calls a sync action creator
})
.catch(error => {
console.log(error);
});
}
}
export const updateRecipesFromAPI_Success = (recipes) => { // sync action creator
return {
type: 'UPDATE_RECIPES',
orderData: recipes
}
}
Im not 100% sure if it is working correct, but it does noet give the result of the video course that I followed.
The renderPosts is just suppose to render the list, but instead it get a blank array the first time round. and when mapStateToProps is called the second time, the array is filled with the expected values.
it is as if the first time mapStateToProps is invoked, it did not pass through the action creator first or something.
COMPONENT
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
// console.log("renderPosts - this.props.posts",this.props.posts);
if(this.props.posts){
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
ACTION
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const CREATE_POST = 'CREATE_POST';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty123';
export function fetchPosts(){
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
export function createPost(props) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return{
type: CREATE_POST,
payload: request
}
}
REDUCER
import { FETCH_POSTS } from '../actions/index';
const INITIAL_STATE = { postsList:[], post:null };
export default function(state = INITIAL_STATE, action){
console.log("action.type",action.type);
switch (action.type) {
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
default:
return state;
}
}
mapStateToProps is called twice. on the initial call the array is empty. on the second call I have my ten posts inside the array.
Problem is that it seems to want to render the first array and ignores the second
I have put an consol.log in the
renderPosts
and
mapStateToProps
and it renders as follows.
Console
any Ideas?
I think the error is coming from the way you handle the Promise. The first time you see the mapStateToProps in the console you can see you have no data so this is PENDING, the second is when it's FULFILLED. You need to find a way to handle this.
Example but not the best, I think you can just change you if statement.
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.props.posts !== [] this.renderPosts() : <h1>Loading...</h1>}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
The second one should be by changing the way you do the promise. A good library is redux-promise-middleware
This is a example of my app what I did.
Actions
export const reqAllGames = games => {
const promise = new Promise((resolve, reject) => {
request
.get(`${config.ROOT_URL}/${config.API_KEY}`)
.end((err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body.top);
}
});
});
return {
type: types.RECEIVE_ALL_GAMES,
payload: promise
};
};
Reducer
import * as types from "../constants/";
const gameReducer = (games = { isFetched: false }, action) => {
switch (action.type) {
case `${types.RECEIVE_ALL_GAMES}_PENDING`:
return {};
case `${types.RECEIVE_ALL_GAMES}_FULFILLED`:
return {
games: action.payload,
err: null,
isFetched: true
};
case `${types.RECEIVE_ALL_GAMES}_REJECTED`:
return {
games: null,
err: action.payload,
isFetched: true
};
default:
return games;
}
};
export default gameReducer;
Component
const Games = ({ games, err, isFetched }) => {
if (!isFetched) {
return <LoadingCircular />;
}
else if (err === null) {
return (
<div>
<GamesList games={games} />
</div>
);
} else {
return <h1>Games not find!</h1>;
}
};
const mapStateToProps = (state) => state.games;
export default connect(mapStateToProps)(Games);
If you using react-router you can use the onEnter api and do the actions right here. With that you know your component gonna get the post. A good tutorial is this one from RallyCoding https://www.youtube.com/watch?v=JicUNpwLzLY
Hope that can help you
https://www.udemy.com/react-redux/learn/v4/questions/1693796
In your reducer you're assigning the list of posts to the key postsList.
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
We can confirm that they are properly being assumed to postsList by looking at the mapStateToProps console log you have in your screenshot.
Your mapStateToProps, however, is looking at the property state.posts.all
return {posts: state.posts.all}
The list of posts are not assigned to the all property, they are assigned to the postsList property. This is why you don't see the updated list of posts in your component. You'll need to update either the property the reducer is placing the list of posts on or update your mapStateToProps to pull the list of posts from the correct property.
-Stephen Grider