componentDidMount did not fire the render() - reactjs

I am calling an async function in componentDidMount(), I expect after the state got updated with fetched data, the component should re-render, but no.
component code:
function mapStateToProps(state){
return {
posts: state.posts
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators, dispatch)
}
export default class Main extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
this.fetchData()
}
fetchData(){
this.props.getAllPosts().then(() => {
console.log('props: ' + JSON.stringify(this.props))
this.props.posts.data.map( post => {
console.log(post.content)
})
})
}
render(){
return(
<div>
{!this.props.loaded
? <h1>loading...</h1>
:
<div>
{this.props.posts.data.map(post => {
return(
<div>
<h2>{post.title}</h2>
<p>{post.content}</p>
</div>
)
})}
</div>
}
</div>
)
}
}
const Home = connect(mapStateToProps, mapDispatchToProps)(Main)
action:
export function fetchAllPosts(){
return{
type: 'FETCH_ALL_POSTS'
}
}
export function receivedAllPosts(posts){
return{
type: 'RECEIVED_ALL_POSTS',
post_list: posts
}
}
export function getAllPosts(){
return (dispatch) => {
dispatch(fetchAllPosts())
return fetch('/api/posts')
.then(response => response.json())
.then(json => {
dispatch(receivedAllPosts(json.data))
})
.catch(error => {
})
}
}
reducer:
export function posts(state = {loaded: false}, action){
switch(action.type){
case 'FETCH_ALL_POSTS':
return Object.assign({}, state, {
'loaded': false
})
case 'RECEIVED_ALL_POSTS':
return Object.assign({}, state, {
'data': action.post_list,
'loaded': true
})
default:
return state
}
}
in the console.log() in the componentDidMount(), I do see the data got fetched, so it means it is in the state, but not applied into the render(), i don't know why.

It is because of a simple reason: you should use this.props.posts.loaded, instead of this.props.loaded.
When you set your state to props:
function mapStateToProps(state){
return {
posts: state.posts
}
}
Here state.posts is actually the object from your reducer:
{
'data': action.post_list,
'loaded': true
}
So similar to use access your posts list via this.props.posts.data, you should use this.props.posts.loaded. I believe you can debug through debugger or console.log easily.
A live code: JSFiddle

If you're using multiple reducers with a root reducer, then you should also provide your reducer's name to the mapStateToProps function.
e.g:
rootreducer.js:
import posts from './posts';
const rootReducer = combineReducers({ posts });
component:
function mapStateToProps(state){
return {
posts: state.posts.posts
}
}

Related

How i can dispatch one list in redux?

I have this class that shows a list of cars:
render() {
return (
<div>
{this.props.cars.map((car) => <Car key={car.Id} car={car} />)}
</div>
);
}
I'm making a http request to get the cars in my api:
componentDidMount() {
axios.get(`http://localhost:8000/api/cars`)
.then(res => {
const cars= res.data.records;
this.props.dispatch({
type:'GET_CARS',
cars});
})
}
}
export default connect(mapStateToProps)(ListCars);
How i can make a reducer that add to props.cars the return of axios get? my actually reducer is don't working:
const CarReducer= (state = [], action) => {
switch(action.type) {
case 'ADD_CAR':
return state.concat([action.data]);
case 'GET_CARS':
return state.map(car =>
car
)
default:
return state;
}
}
export default carReducer
You dispatched this plain object to Redux:
this.props.dispatch({ type:'GET_CARS', cars })
So, you will be able to receive your data from action.cars in your reducer and add it to your Redux store:
const CarReducer = (state = [], action) => {
switch(action.type) {
case 'ADD_CAR':
return state.concat([action.data]);
case 'GET_CARS':
// Receive data from action.cars
// I am assuming your data is in an array
return [...state, ...action.cars];
default:
return state;
}
}
Then, in your component, you should be able to receive the data from the API by using connect() from react-redux:
import { connect } from 'react-redux';
class App extends React.Component {
render() {
return (
<div>
{this.props.cars.map((car) =>
<Car key={car.Id} car={car} />
)}
</div>
);
}
}
const mapStateToProps = state => {
return { cars: state.cars }
}
export default connect(mapStateToProps)(App)

Last child component overwrites all children

I have a parent react component containing 3 children:
<ChildComponent category="foo" />
<ChildComponent category="bar" />
<ChildComponent category="baz" />
The child component calls an api depending on the prop category value:
http://example.com/listings.json?category=foo
In my action the data is returned as expected. However, when the child component renders the data. The last child baz is overwriting its value in foo and bar as well.
A solution to this problem seems to be given here. But I would like this to be dynamic and only depend on the category prop. Is this not possible to do in Redux?
My child component looks like this:
class TweetColumn extends Component {
componentDidMount() {
this.props.fetchTweets(this.props.column)
}
render() {
const { tweets, column } = this.props
if (tweets.length === 0) { return null }
const tweetItems = tweets[column].map(tweet => (
<div key={ tweet.id }>
{ tweetItems.name }
</div>
)
return (
<div className="box-content">
{ tweetItems }
</div>
)
}
}
TweetColumn.propTypes = {
fetchTweets: PropTypes.func.isRequired,
tweets: PropTypes.array.isRequired
}
const mapStateToProps = state => ({
tweets: state.tweets.items
})
export default connect(mapStateToProps, { fetchTweets })( TweetColumn )
reducers:
export default function tweetReducer(state = initialState, action) {
switch (action.type) {
case FETCH_TWEETS_SUCCESS:
return {
...state,
[action.data[0].user.screen_name]: action.data
}
default:
return state;
}
}
export default combineReducers({
tweets: tweetReducer,
})
action:
export const fetchTweets = (column) => dispatch => {
dispatch({ type: FETCH_TWEETS_START })
const url = `${ TWITTER_API }/statuses/user_timeline.json?count=30&screen_name=${ column }`
return axios.get(url)
.then(response => dispatch({
type: FETCH_TWEETS_SUCCESS,
data: response.data
}))
.then(response => console.log(response.data))
.catch(e => dispatch({type: FETCH_TWEETS_FAIL}))
}
You are making an api call every time TweetColumn is mounted. If you have multiple TweetColumn components and each one makes an api call, then whichever one's response is last to arrive is going to set the value of state.tweets.items. That's because you are dispatching the same action FETCH_TWEETS_SUCCESS every time (the last one overrides the previous one). To solve that issue, assuming the response has a category (foo, bar, baz), I would write the reducer in the following way:
export default function tweetReducer(state = initialState, action) {
switch (action.type) {
case FETCH_TWEETS_SUCCESS:
return {
...state,
[action.data.category]: action.data
}
default:
return state;
}
}
You can then do the following in your TweetColumn component:
class TweetColumn extends Component {
componentDidMount() {
this.props.fetchTweets(this.props.column)
}
render() {
const { column } = this.props;
const tweetItems = this.props.tweets[column].map(tweet => (
<div key={ tweet.id }>
{ tweet.name }
</div>
)
return (
<div className="box-content">
{ tweetItems }
</div>
)
}
}
const mapStateToProps = state => ({
tweets: state.tweets
})
const mapDispatchToProps = dispatch => ({
fetchTweets: column => dispatch(fetchTweets(column))
})
export default connect(
mapStateToProps,
mapDispatchToProps,
)( TweetColumn )
You will have to do some validation to make sure tweets[column] exists, but you get the idea.

React component isn't subscribing to the redux store

I created component and connected it using connect() like this:
function mapStateToProps(state) {
return { users: state.users.users }
}
function mapDispatchToProps(dispatch) {
return { userActions: bindActionCreators(UserActions, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(Test)
Sadly when I open React tools in chrome I get this:
Changing the state isn't forcing component to update. Why it isn't subscribing to it?
EDIT:
I'm changing state through action creator and reducer. This is how it looks like:
export function addUser(user) {
return function (dispatch) {
axios.post('http://localhost:4200/users/add/user', {user:user})
.then(() => dispatch({
type: USER_ADD,
user: user
})).catch(err => console.log(err));
}
}
export function getUsers() {
return function (dispatch) {
axios.get('http://localhost:4200/users')
.then((response) => dispatch({
type: REQUEST_USERS,
users:response.data
})).catch(err => console.log(err));
}
}
and reducer:
export function users(state = initialState, action) {
switch (action.type) {
case 'USER_ADD':
{
return {
...state,
users: [
...state.users,
action.user
]
}
break;
}
case 'REQUEST_USERS':
{
return {
...state,
users: [
action.users
]
}
break;
}
.........
and this is my full component:
class Test extends Component {
constructor(props) {
super(props);
this.state = {
login: "",
password: ""
}
}
handleLoginInputChange = (e) => {
this.setState({login: e.target.value})
}
handlePasswordInputChange = (e) => {
this.setState({password: e.target.value})
}
handleSubmit = (e) => {
e.preventDefault()
let user = {login:this.state.login,
password:this.state.password,userId:Math.floor(Math.random()*(100000))};
this.props.userActions.addUser(user);
}
render() {
return (
<div className="test">
<form>
<input type="text" onChange={this.handleLoginInputChange} value=
{this.state.login} placeholder="login"/>
<input type="text" onChange={this.handlePasswordInputChange}
value={this.state.password} placeholder="pass"/>
<button onClick={this.handleSubmit}>send</button>
</form>
<UsersList users = {this.props.users} />
</div>
)
}
componentDidMount() {
this.props.userActions.getUsers();
}
}
function mapStateToProps(state) {
return { users: state.users.users }
}
function mapDispatchToProps(dispatch) {
return { userActions: bindActionCreators(UserActions, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(Test)
I've added everything to github so you could see it github.
Things that could interest you are in reducers/users.js, actions/useractions and in components/Test.js.
My state after getting data from server looks like this:.
I tried many different approaches. Right now I gave up changing the state after adding new user. Instead of that I've made button which can get data from the server - it's reloading the page so I'm not pleased with that solution
Are you sure that this is correct?
function mapStateToProps(state) {
return { users: state.users.users }
}
how are you binding your reducer to the store? The state might be different from what you expect in connect. Can you post what the store contains after the component gets mounted?

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 :-)

Component structure to handle Async Action with Redux-thunk ?

After a bit of trial and error I finally manage to get my action creator working properly and passing the data I wanted into my redux store. Until now I've been dispatching it "manually" like this store.dispatch(fetchTest()); but It would be great if could use these data into a component.
So here is my action creator :
export const fetchTest = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
return axios.get('http://localhost:3000/authors')
.then(data => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching:false,
data: data
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
Here is my reducer :
const initialState = {data:null,isFetching: false,error:null};
export const ThunkData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
So far everything is working properly when using store.dispatch(fetchTest());.
Based on this example I tried to build the following component :
class asyncL extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchTest(this.props.thunkData)
// got an error here : "fetchTest is not a function"
}
render() {
if (this.props.isFetching) {
return console.log("fetching!")
}else if (this.props.error) {
return <div>ERROR {this.props.error}</div>
}else {
return <p>{ this.props.data }</p>
}
}
}
const mapStateToProps = (state) => {
return {
isFetching: state.ThunkData.isFetching,
data: state.ThunkData.data.data,
error: state.ThunkData.error,
};
};
const AsyncList = connect(mapStateToProps)(asyncL);
export default AsyncList
It doesn't work, I have an error on the componentWillMount() and probably somewhere else.
Also my data structure is kind of weird. To actually get to the data array I have to do state.ThunkData.data.data. The first data object is full of useless stuff like request, headers, etc...
So how should I write this component so I can at least passed the Async data into a console.log.
Thanks.
You need to mapDispatchToProps as well.
import { fetchTest } from './myFetchActionFileHere';
import { bindActionCreators } from 'redux';
function mapDispatchToProps(dispatch) {
return {
fetchTest: bindActionCreators(fetchTest, dispatch)
};
}
const AsyncList = connect(mapStateToProps, mapDispatchToProps)(asyncL);
export default AsyncList
documentation link: http://redux.js.org/docs/api/bindActionCreators.html

Resources