Unable to get state updated when dispatching an action to the reducer - reactjs

I'm on my Home Component where I need to show the article feed and for that, I have to have the articleList array. But for some reason when I look into the store, articleList is null. Also, the console.log that I have placed after fetching the data is also not working. It all seems strange.
Home.js
import React, { Component } from "react"
import { connect } from "react-redux"
import { listAllArticles } from "../actions/articles"
class Home extends Component {
componentDidMount() {
this.props.dispatch(listAllArticles)
}
render() {
console.log(this.props)
return (
<div style={{ textAlign: "center" }}>
<h1>Conduit</h1>
<h5>A place to share your knowledge</h5>
</div>
)
}
}
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps)(Home)
listAllArticles
export const listAllArticles = () => {
console.log("inside listAllArticles action creator")
return dispatch => {
fetch("https://conduit.productionready.io/api/articles")
.then(res => res.json())
.then(data => {
console.log(data.articles)
dispatch({
type: "LIST_ALL_ARTICLES",
data: data.articles
})
})
}
}
articleReducer
const initState = {
articleList: null
}
export const articleReducer = (state=initState, action) => {
console.log("inside article reducer")
switch(action.type) {
case "LIST_ALL_ARTICLES":
return {...state, articleList: action.data}
default:
return state
}
}

Related

Props not displaying from fetch call

I am trying to display recipes and not sure if I have this setup correctly. I am pulling recipes from a rails api via get fetch request. At the moment nothing is displaying.
Here is my recipe container:
import React, { Component } from 'react'
import RecipeList from '../components/RecipeList'
import RecipeInput from '../components/RecipeInput'
import { connect } from 'react-redux'
import { postRecipes } from '../actions/postRecipes.js'
import { getRecipes } from '../actions/getRecipes'
class RecipeContainer extends Component{
constructor(props){
super(props)
}
componentDidMount(){
getRecipes()
}
render(){
return (
<div>
<RecipeInput postRecipes={this.props.postRecipes} />
<RecipeList getRecipes={this.props.recipes} />
</div>
)
}
}
const mapStateToProps = state =>({
recipes: state.recipes
})
const mapDispatchToProps = dispatch =>{
return{
postRecipes: (recipe) => dispatch(postRecipes(recipe)),
getRecipes: () => dispatch(getRecipes())
// deleteRecipe: id => dispatch({type: 'Delete_Recipe', id})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(RecipeContainer)
Here is my get request....notice that I am returning my Recipe component here.
export const getRecipes = () => {
const BASE_URL = `http://localhost:10524`
const RECIPES_URL =`${BASE_URL}/recipes`
return (dispatch) => {
dispatch({ type: 'START_FETCHING_RECIPES_REQUEST' });
fetch(RECIPES_URL)
.then(response =>{ return response.json()})
.then(recipes => dispatch({ type: 'Get_Recipes', recipes }));
};
}
This is where I am trying to render the Recipe component from the get request
import React, {Component} from 'react';
// import { getRecipes } from '../actions/getRecipes.js';
import Recipe from './Recipe.js'
class RecipeList extends Component {
// componentDidMount(){
// getRecipes()
// }
render() {
return (
<div>
{this.props.recipes.map(recipe => (<Recipe recipe={recipe} key={recipe.id} />))}
</div>
)
}
}
export default RecipeList;
Edit: Added reducer
switch(action.type){
case 'Add_Recipe':
const recipe = {
name: action.name,
ingredients: action.ingredients,
chef_name: action.chef_name,
origin: action.origin,
category: action.category
}
return{
...state,
recipes: [...state.recipes, recipe],
}
case 'START_FETCHING_RECIPES_REQUEST':
return {
...state,
recipes: [...state.recipes],
requesting: true
}
case 'Get_Recipes':
return {
...state, recipes: action.recipes,
requesting: false
}
default:
return state
}
}
How can I correct this to make it work?
Issue
You are not passing the recipes to the RecipeList component that were fetched and presumably stored in state, and fed back to the UI via RecipeContainer.
Solution
Pass the recipe state from RecipeContainer to RecipeList as a prop. and then render/map the recipes from props.
RecipeContainer
class RecipeContainer extends Component{
componentDidMount() {
getRecipes();
}
render() {
return (
<div>
<RecipeInput postRecipes={this.props.postRecipes} />
<RecipeList getRecipes={this.props.recipes} /> // <-- pass recipe state
</div>
)
}
}
const mapStateToProps = state => ({
recipes: state.recipes,
});
const mapDispatchToProps = dispatch => {
return {
postRecipes: (recipe) => dispatch(postRecipes(recipe)),
getRecipes: () => dispatch(getRecipes())
}
};
RecipeList
class RecipeList extends Component {
render() {
const { recipes } = this.props;
return (
<div>
{recipes.map(recipe => (
<Recipe recipe={recipe} key={recipe.id} />
))}
</div>
);
}
}
The actual solution to this was I needed to have an explicit return in my mapStateToProp function.
Eg.
const mapStateToProp = state =>{
return {
recipes: state.recipes
}
}

Component is not showing

Component is not showing. I don't get any error messages. I am trying to fetch data from a url and build a simple list on PollList from that data. I can console.log(polls) from the action and it works but it just doesn't build the list
Here is the code.
pollsactions.js
import { GET_POLLS, POLLS_LOADING } from './types';
export const getPolls = () => dispatch => {
return fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({
type: GET_POLLS,
payload: polls
})
})
}
pollsreducers.js
import {
GET_POLLS,
POLLS_LOADING
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
default:
return state
}
}
export default pollReducer;
PollList.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { getPolls } from '../redux/actions/pollsActions';
class PollList extends Component {
componentDidMount() {
this.props.getPolls();
}
render() {
const { polls } = this.props.polls
return (
<div>
{
polls && polls.map((poll) => (
<div key={poll.id}>
{(poll.type)}
</div>
))
}
</div>
)
}
}
const mapStateToProps = state => ({
polls: state.polls
});
export default connect(
mapStateToProps,
{ getPolls }
)(PollList);
You are destructuring polls incorrectly. polls is on this.props based on your mapStateToProps(), not on this.props.polls. Try changing:
const { polls } = this.props.polls;
to:
const { polls } = this.props;
Otherwise, without destructuring, it would look like:
const polls = this.props.polls;
Hopefully that helps!

React is not passing state to mapStateToProps

I'm trying to pass an array of items(state) in mapStateToProps. However i get an empty array or it shows undefined.
App.js
import React, { Component } from 'react';
import PostList from './PostList';
import Axios from '../Axios';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {DeletePost, GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
}
getPosts = () => {
Axios.get(process.env.REACT_APP_GET_POSTS)
.then( (res) => {
this.setState({
posts: res.data,
loading: false
})
})
// console.log(this.state.posts);
}
componentWillMount(){
this.getPosts();
}
componentDidMount(){
// doesn't show posts in here
console.log(this.props.posts)
this.props.GetPosts(this.state.posts);
}
onDelete = (id) => {
Axios.post(`/api/posts/delete/${id}`);
this.setState({
posts: this.state.posts.filter(post => post.id !== id)
})
}
render() {
const {loading, posts} = this.state;
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList DeletePost={this.onDelete} posts={posts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
posts: state.user.posts
})
const mapDispatchToProps = (dispatch, state) => ({
// newPost: (post) => dispatch(newPost(post)),
// DeletePost: (id) => dispatch( DeletePost(id))
GetPosts: (posts) => dispatch( GetPosts(posts))
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Posts));
Reducer.js
import { GET_POSTS} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[]
}
export default (state = initialState, action) => {
switch (action.type) {
// doesn't get posts
case GET_POSTS:
return({
...state,
posts: action.posts
})
default:
return state
}
actions
export const GetPosts = (posts) => {
return (dispatch, getState) => {
dispatch({type: GET_POSTS, posts })
console.log('this works i guess', posts);
}
}
I would advice you not to save posts in two places. That somewhat defeats the purpose of using redux. You actually don't need post as a state variable in Posts class. Whenever there is a new state in redux store associated Class will fall into updation cycle.
Also, you can have a look at redux-thunk if you are making api calls.
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
It will help you move api fetching logic to actions and reducers and thus rendering your views clean.
Change this
export const GetPosts = (posts) => {
return (dispatch, getState) => {
dispatch({type: GET_POSTS, posts })
console.log('this works i guess', posts);
}
}
to
export const GetPosts = (posts) => {
return (dispatch, getState) => {
Axios.get(process.env.REACT_APP_GET_POSTS)
.then( (res) => {
dispatch({type: GET_POSTS, res.data })
})
})
}
}
Change this
componentWillMount(){
this.getPosts();
}
to
componentWillMount(){
this.props.GetPosts();
}
Now you wont be needing a componentDidUpdate.
Also, if you are wondering how to show Loading... till the api call is not completed, you can add a key isFetching to your store.
const initialState = {
post: [],
postError: null,
posts:[],
isFecthing: false
}
and can add an action something like ChangeFetchStats
export const GetPosts = (posts) => {
return (dispatch, getState) => {
dispatch({type: CHANGE_STATE, false});
Axios.get(process.env.REACT_APP_GET_POSTS)
.then( (res) => {
dispatch({type: CHANGE_STATUS, true);
dispatch({type: GET_POSTS, res.data })
})
})
}
}
Sometimes, it may take time over a network to get a POST response. In such case, if ur component gets mounted, it will make a call to the action, but because it takes time, you will get response empty/ undifined posts array.
To prevent this from happening, you can go with following :
componentDidMount(){
this.props.GetPosts(this.state.posts);
if(!this.props.posts){
console.log(this.props.posts);
}
}
A little tweek in the render method may help too:
render() {
const {loading, posts} = this.props;
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
{ posts &&(
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList DeletePost={this.onDelete} posts={posts}/>
</div>
);)}
}
So i finally found a purpose for componentDidupdate
The app took a little bit long to load posts maybe half a second.
So by calling componentDidUpdate, i get the posts after its finished rendering.
componentDidUpdate(){
this.props.GetPosts(this.state.posts);
}
Along with another solution by #stevek
change this
case GET_POSTS:
return({
...state,
posts: state.posts
})
to this
import { GET_POSTS} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[]
}
export default (state = initialState, action) => {
switch (action.type) {
// doesn't get posts
case GET_POSTS:
return{...state, posts: action.posts}
default:
return state
}
}
And i can see it after its rendered

React / Redux Async not waiting for returned response

First of all my code is working (everything is exported correctly etc ) but it's not waiting for the async return of data.
I'm using redux-thunk for my async middleware
I have an action named async.js
export function itemsHasErrored(bool) {
return {
type: 'ITEMS_HAS_ERRORED',
hasErrored: bool
};
}
export function itemsIsLoading(bool) {
return {
type: 'ITEMS_IS_LOADING',
isLoading: bool
};
}
export function itemsFetchDataSuccess(items) {
return {
type: 'ITEMS_FETCH_DATA_SUCCESS',
items
};
}
export function itemsFetchData(url) {
return (dispatch) => {
dispatch(itemsIsLoading(true));
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(itemsFetchDataSuccess(items)))
.catch(() => dispatch(itemsHasErrored(true)));
};
}
My reducer
export function itemsHasErrored(state = false, action) {
switch (action.type) {
case 'ITEMS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
export function itemsIsLoading(state = false, action) {
switch (action.type) {
case 'ITEMS_IS_LOADING':
return action.isLoading;
default:
return state;
}
}
export function items(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
I have a container component, asyncContainer.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
import {itemsFetchData} from '../../../actions/async';
import AsyncUI from './asyncUI'
class AsyncContainer extends Component {
componentDidMount() {
this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
//This fails to wait
return (
<AsyncUI />
);
}
}
AsyncContainer.propTypes = {
fetchData: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
hasErrored: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired
};
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AsyncContainer);
And finally I have a simple UI component named asyncUI.js written in a functional way
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
const AsyncUI = (items) => {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
);
}
const mapStateToProps = (state) => {
return {
items: state.items
};
};
export default connect(mapStateToProps)(AsyncUI);
In asyncContainer.js you can see the call to my simple UI component
return (
<AsyncUI />
);
But on calling the property of the redux store items in asyncUI.js an empty array, therefore the items.map fails
However, if I remove the code from asyncUI.js and place it in asyncContainer.js it works
This is the code that works in asyncContainer.js
class AsyncContainer extends Component {
componentDidMount() {
this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
//THIS IS WHERE I HAD <Async />
return (
<ul>
{this.props.items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
);
}
}
AsyncContainer.propTypes = {
fetchData: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
hasErrored: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired
};
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AsyncContainer);
I think the problem is that the component is rendering before the items data is ready. This is normal react behavior. So how do I "hold off" the rendering. As you can see I'm trying to use a Container /Component style of architecture. I can always use the solution that works as mentioned above, but I'd like to stick with this Container /Component.
Am I going to have to delve deeper into Promises etc ?
Should I not use the functional way of writing for asyncUI.js ?
I'm a little confused.
Try:
const AsyncUI = ({items}) => {
/* ^ see ^ */
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
); }
This pulls the items value off the props you reacted in the mapStateToProps function, which is an object, not an array (hence no map function).
NOTE: This should fix your issue, but it is still technically trying to render the items before they are ready in 2 instances:
The first time the component renders. The initial state for itemsIsLoading is false, so the first render will fail all the safety checks. The default value for items is [] so it should just render <ul></ul> for a very brief moment until the itemsIsLoading(true) action is dispatched. You can set the initial state to true for stop this, or change the loading check to be
if (this.props.isLoading || this.props.items.length != 0) {
return <p>Loading…</p>;
}
An argument can be made for how necessary either of those solutions actually are.
After the fetch returns the order the actions is dispatched in will result in another brief render of <ul></ul> as the loading state is set to false before the items are set. See dgrijuela's answer for one way to fix this. Another way would be to not dispatch seperate actions and have the ITEMS_FETCH_DATA_SUCCESS and ITEMS_HAS_ERRORED actions also change the itemsIsLoading value back to false (multiple reducers can act on the same action type).
You call dispatch(itemsIsLoading(false)) before dispatch(itemsFetchDataSuccess(items))
Try like this:
// async.js
...
export function itemsFetchData(url) {
return (dispatch) => {
dispatch(itemsIsLoading(true));
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json())
.then((items) => {
dispatch(itemsFetchDataSuccess(items));
dispatch(itemsIsLoading(false));
})
.catch(() => dispatch(itemsHasErrored(true)));
};
}
see Michael Peyper for a good answer
It turns out that the problem was with the functional style of coding of my asyncUI component. I converted it back to the 'standard' stateful component and bingo it worked.
asyncContainer.js
class AsyncContainer extends Component {
componentDidMount() {
this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
return (
<AsyncUI />
)
}
}
asyncUI.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
class AsyncUI extends Component {
render() {
return (
<ul>
{this.props.items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
)
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
export default connect(mapStateToProps)(AsyncUI);
Hope this helps anyone :-)

How do i properly do a GET request in react-redux?

My goal is to basically do a basic GET request in react-redux. I know how to do it with POST but not with GET because there is no event that is triggering the action.
Heres' the code for action
export function getCourses() {
return (dispatch) => {
return fetch('/courses', {
method: 'get',
headers: { 'Content-Type': 'application/json' },
}).then((response) => {
if (response.ok) {
return response.json().then((json) => {
dispatch({
type: 'GET_COURSES',
courses: json.courses
});
})
}
});
}
}
Where do i trigger this to get the data? in component?
import React from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { getCourses } from '../actions/course';
class Course extends React.Component {
componentDidMount() {
this.props.onGetCourses();
}
allCourses() {
console.log(this.props.onGetCourses());
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
return this.props
}
render() {
return (
<div>
<ul>
{ this.allCourses() }
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
courses: state.course.courses
}
}
const mapDispatchToProps = (dispatch) => {
return {
onGetCourses: () => dispatch(getCourses)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Course);
I tried this but it doesn't work.
Course Reducer
const initialState = {
courses: []
};
export default function course(state= initialState, action) {
switch (action.type) {
case 'GET_COURSES':
return Object.assign({}, state, {
courses: action.courses
})
default:
return state;
}
}
First, onGetCourses: () => dispatch(getCourses) should be changed to onGetCourses: () => dispatch(getCourses()) (you need to actually invoke the action creator).
When it comes to where you should call the action, it is absolutely fine to do it in componentDidMount, as you have done.
In case you did not notice, you have two return's in your allCourses().
I have similar code in my codebase, but I don't use return in front of fetch and response.json() because the function should return action object.

Resources