React redux thunk and component rendering too early before actions - reactjs

I'm sending request to API and then saving the response to array for later use with thunk, but the problem is that my component is calling render function too fast before my action that saves the response from API to array.
The error that I'm getting is "Consider adding an error boundary to your tree to customize error handling behavior". I set "isFetching" bool state accordingly true/false each action but the render function seems to completely ignore if I put like a condition if isFetching is true then return <p>loading</p> in render. The action thats responsible for setting data from API works and sets properly but then its already too late.
Question is how do I delay rendering the component so by the time it renders I already have the data saved to array and ready to work with?
UPDATE WITH CODE:
Action.js:
import axios from "axios";
export let startFetch = () => {
return {
type: "START_FETCH"
}
}
export let endFetch = (array) => {
return {
type: "END_FETCH",
array
}
}
export let fetchApi = () => {
let url = "http://127.0.0.1:5000/api/stats"
return (dispatch) => {
dispatch(startFetch())
return axios.get(url).then(
(response) => {
dispatch(endFetch(response.data))
},
(err) => {
console.log(err);
}
)
}
}
Reducer.js:
export let fetchApiReducer = (state={isFetching : false, array : []},action) => {
switch(action.type){
case 'START_FETCH':
return {
isFetching : true
}
break;
case 'END_FETCH':
return{
isFetching : false,
array : action.array
}
break;
default:
return state;
}
}
Container.js:
import React, {Component} from "react";
import {connect} from "react-redux";
import {bindActionCreators} from 'redux';
import {fetchApi} from "../actions/adsActions"
class AdsList extends Component {
componentWillMount() {
this.props.fetchApi();
}
renderList(ad) {
return (
<a className="list-group-item list-group-item-action flex-column align-items-start">
<div className="d-flex w-100 justify-content-between">
<h5 className="mb-1">{ad.text}</h5>
<small className="text-muted">{ad.date}</small>
</div>
</a>
);
}
render() {
if(this.props.isFetching == true) {
return (<p>Loading</p>);
} else if (this.props.isFetching == false && this.props.array.length >= 1) {
return (
<div>
{this.props.array.map(this.renderList)}
</div>
);
}
}
}
function mapStateToProps(state) {
return {
isFetching: state.isFetching,
array: state.array
};
}
function matchDispatchToProps(dispatch){
return bindActionCreators({fetchApi: fetchApi}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(AdsList);
Thanks a lot.

Related

How to fix problem with React-Redux(mapDispatchToProps() in Connect() must return a plain object. Instead received undefined.)

I ported my application to redux-thunk and the error started to appear in the console(
mapDispatchToProps() in Connect(UsersContainer) must return a plain object. Instead received undefined.).
But with the appearance of this error, nothing has changed. How to fix it?
Reducer:
import {getTeamApi} from "./api";
let date = {
teamDate: []
};
const realtorsDate = (state = date, action) => {
switch (action.type) {
case "GetTeam":
return {...state, teamDate: [...state.teamDate, ...action.team]};
default:
return state
}
}
export let GetTeam = (team) => ({
type: "GetTeam",
team
})
export default realtorsDate;
export const getTeamThunks = () => {
return (dispatch) => {
getTeamApi.then(response => {
dispatch(GetTeam(response.items));
});
}
}
Container component:
import {connect} from "react-redux";
import {getTeamThunks} from "../../store/realtorsDate";
import ScrollableAnchor from "react-scrollable-anchor";
import MainFourth from "./main-fourth";
import Photo from "../../Images/pivo-3.jpg";
import React from "react";
class UsersContainer extends React.Component {
render() {
return (
<section>
<ScrollableAnchor id={"team"}>
<h2>Наша команда</h2>
</ScrollableAnchor>
<div className="MainFourth">
{this.props.realtorsDate.map((el, i) => (
<MainFourth key={i} el={el} Photo={Photo}></MainFourth>))}
</div>
</section>
);
}
}
let MapStateToProps = (state) => {
return {
realtorsDate: state.realtorsDate.teamDate
}
}
let FourthContainerBlock = connect(MapStateToProps, getTeamThunks)(UsersContainer)
export default FourthContainerBlock
Component:
import React from "react";
import "./../../css/App.css";
import {FontAwesomeIcon} from "#fortawesome/react-fontawesome";
import {faAt, faMobileAlt} from "#fortawesome/free-solid-svg-icons";
class MainFourth extends React.Component {
render() {
return (
<div className="team" key={this.props.i}>
<div className="about-team">
<h2 className="team-name">
{this.props.el.SecondName}
{this.props.el.Name}
</h2>
<h2 className="team-position">{this.props.el.Position}</h2>
<p><FontAwesomeIcon icon={faMobileAlt}></FontAwesomeIcon> : {this.props.el.Phone}</p>
<p><FontAwesomeIcon icon={faAt}></FontAwesomeIcon> : {this.props.el.Mail}</p>
</div>
<img className="TeamsPhoto" src={this.props.Photo} alt="" />
</div>
);
}
}
export default MainFourth;
Ty all
The error is actually pretty straight forward, getTeamThunks should return a js object and that should be synchronous. You can do asynchronous things in the action creators.
The second argument to connect is simply used to map the dispatch to props(the reason why most tend to name it as such).
For example, mapDispatchToProps can look like this:
const mapDispatchToProps = (dispatch) => {
return {
getTeamThunks: () => dispatch(actions.getTeamThunksData())
}
}
let FourthContainerBlock = connect(MapStateToProps, mapDispatchToProps)(UsersContainer)
Now the getTeamthunksData can be written like this:
export const getTeamThunksData = () => {
return (dispatch) => {
getTeamApi.then(response => {
dispatch(GetTeam(response.items));
});
}
}
In your component, you can dispatch it by using this.props.getTeamThunks(). Where you execute it depends on your requirements.

× Maximum update depth exceeded

Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
this is bossinfo.js
import React from 'react';
import { NavBar, InputItem, TextareaItem, Button } from 'antd-mobile';
import {connect} from 'react-redux';
import {Redirect} from 'react-router-dom';
import AvatarSelector from '../../component/avatar-selector/avatar-selector';
import {updata} from '../../redux/user.redux';
#connect(
state=>state.user,
{updata}
)
class BossInfo extends React.Component{
constructor(props){
super(props)
this.state={
title:'',
company:'',
money:'',
desc:'',
}
}
onChange(key,v){
this.setState({
[key]:v
})
}
render(){
return (
<div>
{this.props.redirectTo?<Redirect to={this.props.redirectTo}></Redirect>:null}
<NavBar mode="dark">Boss完善信息页面</NavBar>
<AvatarSelector selectAvatar={(imagename)=>{
this.setState({
avatar:imagename
})
}}></AvatarSelector>
<InputItem onChange={(v)=>{this.onChange('title',v)}}>
招聘职位
</InputItem>
<InputItem onChange={(v)=>{this.onChange('company',v)}}>
公司名称
</InputItem>
<InputItem onChange={(v)=>{this.onChange('money',v)}}>
职位薪资
</InputItem>
<TextareaItem
title='职位要求'
rows={3}
autoHeight
onChange={(v)=>{this.onChange('desc',v)}}
>
</TextareaItem>
<Button onClick={()=>this.props.updata(this.state)} type='primary'>保存</Button>
</div>
)
}
}
export default BossInfo;
this is user.redux.js
import axios from "axios";
import { getRedirectPath } from '../util' ;
const AUTH_SUCCESS = 'AUTH_SUCCESS';
const ERROR_MSG = 'ERROR_MSG';
const LOAD_DATA = 'LOAD_DATA';
const initStatus={
redirectTo:'',
msg:'',
user:'',
type:'',
}
//reducer
export function user(state=initStatus,action){
switch(action.type){
case AUTH_SUCCESS:
return {...state,msg:'',redirectTo:getRedirectPath(action.data),...action.data}
case LOAD_DATA:
return {...state,...action.data}
case ERROR_MSG:
return {...state,isAuth:false,msg:action.msg}
default:
return state;
}
}
//action
function authSuccess(data){
return { type:AUTH_SUCCESS, data:data }
}
function errorMsg(msg){
return { type:ERROR_MSG, msg:msg }
}
export function register({user,pwd,repeatpwd,type}){
if(!user || !pwd){
return errorMsg('用户名密码必须输入!')
}
if(pwd!=repeatpwd){
return errorMsg('两次密码输入不一致!')
}
//redux-thunk支持返回函数的写法(这边是异步)
return dispatch =>{
axios.post('/user/register',{user,pwd,type}).then(res=>{
if(res.status==200&&res.data.code==0){
dispatch(authSuccess({user,pwd,type,_id:res.data.data._id}))
}else{
dispatch(errorMsg(res.data.msg))
}
})
}
}
export function login({user,pwd}){
if(!user || !pwd){
return errorMsg('用户名和密码不能为空!')
}
return dispatch =>{
axios.post('/user/login',{user,pwd}).then(res=>{
if(res.status==200 && res.data.code==0){
dispatch(authSuccess(res.data.data))
}else{
dispatch(errorMsg(res.data.msg))
}
})
}
}
export function loadData(userinfo){
return {type:LOAD_DATA,data:userinfo}
}
export function updata(data){
return dispatch=>{
axios.post('/user/updata',data)
.then(res=>{
if(res.status==200 && res.data.code==0){
dispatch(authSuccess(res.data.data))
}else{
dispatch(errorMsg(res.data.msg))
}
})
}
}
Why do you keep reporting this problem?Is this pointing in the wrong direction?Asking for help
The onChange function is executing on each render change the function to arrow function like
onChange=(key,v)=>{
v.stopPropagation();
this.setState({
[key]:v
})
}

How to "bubble up" errors from thrown from redux connect functions?

I want my react native app to display error messages with a toast. I want to detect errors within the root level App component using componentDidCatch so I can handle all errors in the same manner.
Currently, if one of my async actions throw an error, mapDispatchToProps can catch it. How do I "bubble" up these errors to my App component?
Alternatively, I could add a redux state for errors and set it on every async error. I can then check for this state in App. It would be cleaner, however, if I could catch all errors in componentDidCatch
Well, here's what I did in my project. I use https://github.com/fkhadra/react-toastify
App.js
import Toaster from './components/Toaster/Toaster';
class App extends Component {
render() {
return (
<div>
<Toaster/>
<Routes />
</div>
);
}
}
export default (App);
Toaster.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import { toast, ToastContainer } from 'react-toastify';
import PropTypes from 'prop-types';
import { toastConstants } from '../../_constants';
const Message = ({ type, content }) => {
let icon = '';
switch(type){
case 'success':
icon = <i className="fa fa-check-circle"></i>;
break;
case 'error':
icon = <i className="fa fa-times-circle"></i>;
break;
case 'info':
icon = <i className="fa fa-info-circle"></i>;
break;
case 'warning':
icon = <i className="fa fa-exclamation-circle"></i>;
break;
default:
icon = '';
break;
}
return (
<div>
{icon} {content}
</div>
);
};
class Toaster extends Component {
componentWillReceiveProps(nextProps) {
if (nextProps.toast.message && nextProps.toast.type) {
toast.dismiss();
switch (nextProps.toast.type) {
case toastConstants.SUCCESS:
toast.success(<Message content={nextProps.toast.message} type="success" />);
break;
case toastConstants.INFO:
toast.info(<Message content={nextProps.toast.message} type="info" />);
break;
case toastConstants.WARN:
toast.warn(<Message content={nextProps.toast.message} type="warning" />);
break;
case toastConstants.ERROR:
toast.error(<Message content={nextProps.toast.message} type="error" />);
break;
default:
break;
}
}
}
render() {
return (
<ToastContainer autoClose={5000} />
);
}
}
function mapStateToProps(state) {
const { toast } = state;
return {
toast
};
}
Message.propTypes = {
type: PropTypes.string,
content: PropTypes.string
};
export default connect(mapStateToProps)(Toaster);
SomeActions.js
function getAll(){
return dispatch => {
dispatch(request());
companyService.getAll()
.then(
response => {
if(response.status === 'fail'){
dispatch(failure(response));
dispatch(toastActions.error(response.message));
}else{
dispatch(success(response));
}
},
error => {
dispatch(toastActions.error(error.toString()));
dispatch(failure(error.toString()));
}
);
}
function request() { return { type: companyConstants.LIST_REQUEST } }
function success(data) { return { type: companyConstants.LIST_SUCCESS, data } }
function failure(error) { return { type: companyConstants.LIST_FAILURE, error } }
}
toastActions.js
import { toastConstants } from '../_constants';
export const toastActions = {
success,
error,
clear
};
function success(message) {
return { type: toastConstants.SUCCESS, message };
}
function error(message) {
return { type: toastConstants.ERROR, message };
}
function clear() {
return { type: toastConstants.CLEAR };
}
toastReducer.js
import { toastConstants } from '../_constants';
const initialState = {
type: toastConstants.CLEAR,
message: null
};
export function toast(state = initialState, action) {
switch (action.type) {
case toastConstants.SUCCESS:
return {
type: toastConstants.SUCCESS,
message: action.message
};
case toastConstants.ERROR:
return {
type: toastConstants.ERROR,
message: action.message
};
case toastConstants.CLEAR:
return {};
default:
return initialState
}
}
Hope its of any use for you!
Cheers.
so the issue is not specific to redux connector functions. in fact, all errors thrown from event handlers will not trigger componentDidCatch. see https://reactjs.org/docs/error-boundaries.html#how-about-event-handlers.
I did not want use some redux error state to capture these errors as that would require more boilerplate. ex: it would force me to connect all my components to redux for error handling, even for components that did not need redux state or would not update state (except for error state). not the idea solution, but to get around this I created a separate function for all my event handlers to use.
//componentEventHandler.js
export function handleEvent () {
const args = Array.from(arguments);
const fn = args.shift();
fn(...args).catch(e => this.setState(() => { throw e }));
}
I then import this function in my components and use as follows.
onPress={handleEvent.bind(this, this.props.signIn, this.state.email, this.state.password)}
now all my child components of App.js will event errors up to App.js

The reducer does not transmit the initialState

The this.props does not have the loading, error key.
Why?
My code on :
https://github.com/jiexishede/react-redux-demo01
You can fork it and pull request.
Because you don't pass them in your mapStateToProps function
https://github.com/jiexishede/react-redux-demo01/blob/0c1407935cd6c461705d6ca37f3e33484afac327/src/views/Home.js#L8-L10
This should be something like:
#connect(state => {
return {
articleList: state.home.list.articleList,
loading: state.home.list.loading,
error: state.home.list.error,
};
You didn't set up your component to receive updates from your store. Your component won't know that the reducer has updated the state. Check out the code below:
import React, { Component } from 'react';
import * as Redux from 'react-redux'; // Import redux
import Preview from './Preview'
class PreviewList extends Component {
static propTypes = {
loading:React.PropTypes.bool, // 注意 bushi PropTypes.bool, 前面要价 React
error:React.PropTypes.bool,
articleList: React.PropTypes.arrayOf(React.PropTypes.object),
loadArticles: React.PropTypes.func
};
componentDidMount(){
this.props.loadArticles();
}
render(){
const { loading, error, articleList } = this.props;
if(error){
return <p className="message">)0ops, something is wrong. </p>
}
if(loading){
return <p className="message">Loading....</p>
}
// return this.props.articleList.map(item => (
// <Preview {...item} key={item.id}/>
// ))
return (
<div>
{articleList.map(item => {
return <Preview {...item} key={item.id} push={this.props.push} />
})}
</div>
);
}
}
// Connect your component to your store and
// receive updates from your previewList reducer:
export default Redux.connect(state => {
return {
loading: state.previewList.loading,
error: state.previewList.error,
articleList: state.previewList.articleList
};
})(PreviewList);
The problem is that you're not connecting your component to the redux store. You need to install the react-redux package then use it's connect function to connect your component to the store like the following:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Preview from './Preview';
import { loadArticles } from './PreviewListRedux';
class PreviewList extends Component {
static propTypes = {
loading:React.PropTypes.bool,
error:React.PropTypes.bool,
articleList: React.PropTypes.arrayOf(React.PropTypes.object),
loadArticles: React.PropTypes.func
};
componentDidMount(){
this.props.loadArticles();
}
render(){
if (!this.props.loading) {
return <div>Loading...</div>
}
const { loading, error, articleList } = this.props;
if(error){
return <p className="message">)0ops, something is wrong. </p>
}
if(loading){
return <p className="message">Loading....</p>
}
return (
<div>
{articleList.map(item => {
return <Preview {...item} key={item.id} push={this.props.push} />
})}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.list.loading,
error: state.list.error,
articleList: state.list.articleList
}
};
export default connect(mapStateToProps, { loadArticles })(PreviewList);
Also, your code needs some major restructuring, it's really difficult to read through it and see how the different pieces are connected together.

React/Redux Connect issues with mapStateToProps

I have the following React Component
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
import Product from './product';
import { openPaymentModal } from '../../../state/modalActions';
import { fetchAccountProducts } from '../../../lib/ecApi';
import { fetchChargifyCallById } from '../../../lib/chargifyApi';
import { filterProductsForUser, prepProducts } from '../../../_helpers';
class Products extends Component {
constructor () {
super();
this.state = {
products: [],
currentProduct: '',
showSuccess: false,
}
}
componentDidMount() {
const { location, user } = this.props;
fetchAccountProducts()
.then(this.addBasicProduct)
.then(this.filterProducts(user));
this.checkChargifyCall(location.query, user);
}
addBasicProduct(products) {
return prepProducts(products);
}
filterProducts(user) {
return products => {
this.setState({products: filterProductsForUser(products, user)});
}
}
checkChargifyCall (query, user) {
if (_.isEmpty(query)) {
const currentProduct = this.determineProduct(user);
this.setState({currentProduct});
return;
}
fetchChargifyCallById(query.call_id).done(data => {
const { product } = data.response.signup;
const { errors } = data.response.meta;
if (query && query.status_code !== '200') {
this.props.dispatch(openPaymentModal(
product.handle,
errors,
));
} else {
this.setState({
currentProduct: product.handle,
showSuccess: true
});
}
});
}
determineProduct(user) {
const subscription = user.chargifySubscriptions[0];
if (subscription && subscription.product) {
return subscription.product.handle;
}
return this.state.currentProduct;
}
render () {
let calloutEl = (
<div className='callout success'>Success!</div>
);
return (
<div className="row medium-up-2 large-up-3 products-row">
{this.state.showSuccess && calloutEl}
{this.state.products.map((object, i) => {
return <div className="column"><Product
price={object.price}
name={object.name}
interval={object.interval}
intervalUnit={object.interval_unit}
isPaid={object.require_credit_card}
description={object.description}
handle={object.handle}
key={i}
currentProduct={this.state.currentProduct} /></div>;
})}
</div>
);
}
}
const mapStateToProps = state => ({user: state.user});
export default connect(mapStateToProps)(Products);
The problem I am having is that if I console.log(this.props.user) in my componentDidMount method, it is the initial state from the reducer vs the fully propagated user state. Any reasons why that might be happening? I'm fairly new to React/Redux, so my apologies for ignorance
Answer: it is the initial state from the reducer.
reason reducer represents a piece of state. and please your a promise middle to handle your data fetching.
The problem I am having is that if I console.log(this.props.user) in my componentDidMount method, it is the initial state from the reducer vs the fully propagated user state. Any reasons why that might be happening? I'm fairly new to React/Redux, so my apologies for ignorance.
connect is a high order component that passes data to your container component. in your case Products component receives state as props from connect
const mapStateToProps = state => ({user: state.user}); //the state you want
export default connect(mapStateToProps)(Products); //user as state to you products component.

Resources