Universal React/redux not rendering on state update - javascript

I'm using this boilerplate and am having issues where my container is not rerendering after my state has updated. I have two actions that get dispatched, 1) LOAD - dispatched during the start of the AJAX request and 2) LOAD_SUCCESS - once the data has been successfully returned.
The LOAD_SUCCESS action gets called from the reducer but nothing happens after that. This code used to work a couple weeks ago (project was on hold for a bit) but no longer works. Any thoughts ?
import superagent from 'superagent';
import config from '../../config';
const LOAD = 'cnnm/sectionFront/LOAD';
const LOAD_SUCCESS = 'cnnm/sectionFront/LOAD_SUCCESS';
const LOAD_FAIL = 'cnnm/sectionFront/LOAD_FAIL';
const initialState = {
loaded: false,
loading: false,
currentSection: null,
data: {}
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case LOAD:
return {
...state,
loading: true,
loaded: false
};
case LOAD_SUCCESS:
const result = {
...state,
loading: false,
loaded: true,
error: null,
currentSection: action.sectionID
};
result.data[action.sectionID] = action.data;
return result;
case LOAD_FAIL:
return {
...state,
loading: false,
loaded: true,
data: null,
error: action.error
};
default:
return state;
}
}
export function load() {
return (dispatch, getState) =>{
dispatch({
type: LOAD
});
const globalState = getState();
const endpoint = config.endpoints.sectionFronts[globalState.routing.locationBeforeTransitions.pathname];
if ( globalState.sectionFront.data[globalState.routing.locationBeforeTransitions.pathname] === undefined ){
superagent.get( endpoint )
.end( (err, resp) => {
if ( err ){
dispatch({
type: LOAD_FAIL,
error: err
});
}
else {
try {
const data = JSON.parse( resp.text );
dispatch({
type: LOAD_SUCCESS,
sectionID: globalState.routing.locationBeforeTransitions.pathname,
data
});
}
catch ( error ){
console.warn('Error trying to parse section front', error);
dispatch({
type: LOAD_FAIL,
error: error
});
}
}
});
}
else {
// Already have the data cached
dispatch({
type: LOAD_SUCCESS,
sectionID: globalState.routing.locationBeforeTransitions.pathname,
data: globalState.sectionFront.data[globalState.routing.locationBeforeTransitions.pathname]
});
}
};
}
Container code:
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import Helmet from 'react-helmet';
import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row';
import { asyncConnect } from 'redux-async-connect';
import { load as loadSectionFront } from 'redux/modules/sectionFront';
import {bindActionCreators} from 'redux';
import { Loading } from 'components';
import Grid from 'react-bootstrap/lib/Grid';
import { cards as cardComponents } from 'components';
#asyncConnect([{
deferred: true,
promise: ({store: {dispatch, getState}}) => {
return dispatch(loadSectionFront());
}
}])
#connect(
state => {
return {
section: state.sectionFront.data,
currentSection: state.sectionFront.currentSection,
loaded: state.sectionFront.loaded,
loading: state.sectionFront.loading
};
},
dispatch => bindActionCreators( {loadSectionFront}, dispatch)
)
export default class SectionFront extends Component {
static propTypes = {
section: PropTypes.object,
loaded: PropTypes.bool,
loading: PropTypes.bool,
currentSection: PropTypes.string,
loadSectionFront: PropTypes.func,
}
mapSecionNameToPrettyName = ( sectionID ) => {
switch ( sectionID ){
case '/':
return 'Top News';
case '/video':
return 'Video';
case '/technology':
return 'Technology';
case '/media':
return 'Media';
case '/investing':
return 'Investing';
case '/news/economy':
return 'Economy';
case '/pf':
return 'Personal Finance';
case '/retirement':
return 'Retirement';
case '/autos':
return 'Autos';
case '/smallbusiness':
return 'Small Business';
case '/news/companies':
return 'Companies';
case '/luxury':
return 'Luxury';
case '/real_estate':
return 'Real Estate';
default:
return 'Not found';
}
}
render() {
const {section, loaded, currentSection, loading } = this.props;
const sectionName = this.mapSecionNameToPrettyName( currentSection );
const styles = require('./SectionFront.scss');
let cardNodes = '';
if (loaded){
cardNodes = section[currentSection].cards.map((card, index) => {
switch ( card.cardType ) {
case 'summary':
if ( card.images === undefined || card.cardStyle === 'text'){
return <cardComponents.SummaryTextComponent card={card} key={index} />;
}
else {
if ( card.cardStyle === 'default' ){
return <cardComponents.SummaryDefault card={card} key={index} />;
}
else if ( card.cardStyle === 'jumbo' || card.cardStyle === 'jumboBlack' ){
return <cardComponents.SummaryJumboComponent card={card} key={index} />;
}
else if ( card.cardStyle === 'jumboOverlay' ){
return <cardComponents.SummaryJumboOverlayComponent card={card} key={index} />;
}
}
break;
case 'marketIndices':
return <cardComponents.MarketIndicesComponent key={index} />;
case 'ad':
return <cardComponents.AdComponent card={card} key={index} />;
case 'bizDev':
return <cardComponents.BizDevComponent card={card} key={index} />;
default:
return ( <div key={index}> </div>);
}
});
}
return (
<div className={styles.sectionFront}>
{ loading ? <div className={styles.overlay}><Loading /></div> : null }
<Grid className={styles.container}>
<Row className={styles.sectionTitle}>
<Col className={ 'col-xs-12 ' + styles.sectionCol}>
<h1 className="container">{sectionName}</h1>
</Col>
</Row>
{cardNodes}
</Grid>
</div>
);
}
}

Per the Redux FAQ at http://redux.js.org/docs/FAQ.html#react-not-rerendering, 99.9% of the time the reason a component isn't re-rendering is because of a reducer directly mutating state. This line appears to be doing just that: result.data[action.sectionID] = action.data;. You need to return a cloned version of data inside of result as well.
Now, I would actually expect that the changes in "loading" and "loaded" would correctly cause the component to re-render even if the "data" reference stayed the same, but there might be something else I'm missing here.

Related

Can someone show me what's wrong with my Redux state?

EDIT
The component that state is not rendering in is called TournamentShow, which calls the state and whatever functions I need to use for the Show page.
Nested within it is a conditional to call one of 3 pages, based on
Tournament.Status === "Open",
Tournament.Status === "Closed", and
Tournament.Status === "Complete"
Tournament Show:
import React, { Component } from 'react';
import { SignUpPage, HostUI, StartBracket, Results } from './TournamentScreens';
import {
showTournament,
addParticipant,
closeTournament,
shuffleParticipants
} from '../../actions/tournamentActions';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Spinner } from 'reactstrap';
class TournamentShow extends Component {
constructor(props) {
super(props);
this.onSignUp = this.onSignUp.bind(this);
this.onStartTournament = this.onStartTournament.bind(this);
this.onShuffleParticipants = this.onShuffleParticipants.bind(this);
};
componentDidMount() {
const id = this.props.match.params.id;
this.props.showTournament(id);
};
static propTypes = {
tournament: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
onSignUp(tournamentId, user) {
this.props.addParticipant(tournamentId, user);
};
onShuffleParticipants(array) {
let currentIndex = array.length, temporaryValue, randomIndex;
while(0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
};
onStartTournament(tourneyId) {
const { participants } = this.props.tournament.showTournament;
// Randomize participants
let reorderedParticipants = [];
const shuffledParticipants = this.onShuffleParticipants(participants);
shuffledParticipants.forEach(participant => {
reorderedParticipants.push(participant);
});
// Send new participants list to backend
this.props.shuffleParticipants(tourneyId, reorderedParticipants);
// Set Status to "Closed"
this.props.closeTournament(tourneyId);
};
render() {
console.log(this.props.tournament)
const loading = this.props.tournament.loading || !this.props.tournament.showTournament;
if(loading) {
return <Spinner color="light" />
} else {
if(this.props.tournament.showTournament.status === "Complete") {
return (
<Results />
);
} else if(this.props.tournament.showTournament.status === "Closed") {
return (
<div>
<HostUI
tournament={this.props.tournament.showTournament}
/>
<StartBracket
tournament={this.props.tournament.showTournament}
/>
</div>
);
} else {
return (
<SignUpPage
tournament={this.props.tournament.showTournament}
auth={this.props.auth}
onSignUp={this.onSignUp}
onStartTournament={this.onStartTournament}
/>
);
}
};
};
};
const mapStateToProps = state => ({
tournament: state.tournament,
auth: state.auth
});
export default connect(mapStateToProps,
{ showTournament, addParticipant, closeTournament, shuffleParticipants }
)(TournamentShow);
Tournament Show Screens:
import React from 'react';
import moment from 'moment';
import { TournamentSignUp, StartTournament } from './resources/buttons';
import { TournamentRules } from './resources/rulesets';
import { Button } from 'reactstrap';
import { Link } from 'react-router-dom';
// Status === "Open"
export const SignUpPage = ({ tournament, auth, onSignUp, onStartTournament }) => {
};
// Status === "Closed"
export const HostUI = ({ tournament }) => {
const { players } = tournament.bracket;
return (
<div style={{color:"lightgrey"}}>
<h1>Host UI</h1>
{
players && players.map(player => (
<div>
{player.username}
</div>
))
}
</div>
);
};
export const StartBracket = ({ tournament }) => {
const { title, hostedBy, participants } = tournament;
return (
<div className="text-center" style={{color:"lightgrey", backgroundColor: "#333333"}}>
<h1>{ title }</h1>
<h4>By { hostedBy }</h4>
<h4>{participants && participants.length}-player bracket</h4>
<br /><Link to="/">Back to Tournaments main page</Link>
</div>
);
};
// Status === "Complete"
export const Results = () => {
};
Status===Closed shows both of those central components.
HostUI renders just the players array (which had just been updated right before the status switch/re-render)
StartBracket shows stuff from ShowTournament, which is all data that had already been set in the state
ORIGINAL --------------------------
I'll mark with // comment which case does not work
import {
GET_TOURNAMENTS,
SHOW_TOURNAMENT,
ADD_TOURNAMENT,
ADD_TOURNAMENT_FAIL,
EDIT_TOURNAMENT,
EDIT_TOURNAMENT_FAIL,
DELETE_TOURNAMENT,
TOURNAMENTS_LOADING,
TOURNAMENT_LOADING,
USER_JOINS_TOURNAMENT,
TOURNAMENT_SIGN_UP_FAIL,
TOURNAMENT_STATUS_UPDATE,
TOURNAMENT_STATUS_FAILED,
SHUFFLE_PARTICIPANTS,
SHUFFLE_FAILED
} from '../actions/types';
const initialState = {
tournaments: [],
showTournament: {},
loading: false,
};
export default function(state = initialState, action) {
switch(action.type) {
case GET_TOURNAMENTS:
return {
...state,
tournaments: action.payload,
loading: false
};
case SHOW_TOURNAMENT:
return {
...state,
showTournament: action.payload,
loading: false
};
case ADD_TOURNAMENT:
return {
...state,
tournaments: [action.payload, ...state.tournaments]
};
case DELETE_TOURNAMENT:
return {
...state,
tournaments: state.tournaments.filter(tournament => tournament._id !== action.payload)
};
case TOURNAMENTS_LOADING:
case TOURNAMENT_LOADING:
return {
...state,
loading: true
};
case USER_JOINS_TOURNAMENT:
return {
...state,
...state.showTournament.participants.push(action.payload)
};
case TOURNAMENT_STATUS_UPDATE: // Occurs with SHUFFLE_PARTICIPANTS, which doesn't work
return {
...state,
...state.showTournament.status = action.payload
};
case SHUFFLE_PARTICIPANTS: // Does not work
return {
...state,
...state.showTournament.bracket.players.push(action.payload)
}
case EDIT_TOURNAMENT:
case ADD_TOURNAMENT_FAIL:
case EDIT_TOURNAMENT_FAIL:
case TOURNAMENT_SIGN_UP_FAIL:
case TOURNAMENT_STATUS_FAILED:
case SHUFFLE_FAILED:
return {
...state,
}
default:
return state;
};
};
Most of that works.
The ones I'm sure I screwed up are TOURNAMENT_STATUS_UPDATE and SHUFFLE_PARTICIPANTS, although status update works as intended.
This is a tournament app whose show page renders 3 different components based on showTournament.status
...
if(loading) {
return <Spinner color="light" />
} else {
if(this.props.tournament.showTournament.status === "Complete") {
return (
<Results />
);
} else if(this.props.tournament.showTournament.status === "Closed") {
return (
<div>
<HostUI
tournament={this.props.tournament.showTournament}
/>
<StartBracket
tournament={this.props.tournament.showTournament}
/>
</div>
);
} else {
return (
<SignUpPage
tournament={this.props.tournament.showTournament}
auth={this.props.auth}
onSignUp={this.onSignUp}
onStartTournament={this.onStartTournament}
/>
);
}
};
Component button:
randomizes Tournament.participants and sends it to Tournament.bracket.players
sets Tournament.status === "Closed"
That updates the page and renders the Status: "Closed" page correctly.
The problem is, it only displays whatever I already had loaded in the state. (stuff from SHOW_TOURNAMENT)
The bracket.players array I sent the randomized user list to doesn't display until I refresh the page.
You need to shallow copy each level of state you are updating. Also, ...state.showTournament.bracket.players.push(action.payload) will simply attempt to spread in the return value of the push which is just the new length of the array. This isn't what you want.
case TOURNAMENT_STATUS_UPDATE:
return {
...state,
showTournament: {
...state.showTournament,
status: action.payload,
},
};
case SHUFFLE_PARTICIPANTS:
return {
...state,
showTournament: {
...state.showTournament,
bracket: {
...state.showTournatment.bracket,
players: [...state.showTournament.bracket.players, ...action.payload], // spread payload array
},
},
}
Drew's solution worked, it was just that, in order to pass an array into another array, the syntax is
players: [...state.showTournament.bracket.players. ...action.payload]
rather than players: [...state.showTournament.bracket.players, action.payload]
Good day

Fetching data from server through Redux (Action & Reducer) fails to store the data in the State

I'm trying to fetch data through Redux (with actions & reducers) and store it in a ReactTable
Here is the Table :
// MisleadLeadsTable
import React from "react";
import "react-table-v6/react-table.css";
import ReactTable from "react-table-v6";
import { connect } from "react-redux";
import {
getLeadsNotValid,
updateSpecificNotValidLead
} from "../../actions/leads";
import Spinner from "../layout/Spinner";
class MisleadLeadsTable extends React.Component {
constructor(props) {
super();
const { getLeadsNotValid } = props;
// Going to get data from the Server
// Call the Action and use the Reducer
getLeadsNotValid();
// Later put the data in the state
this.state = {
data: []
};
this.renderEditable = this.renderEditable.bind(this);
}
componentDidMount() {
// TODO
const { leadsNotValid } = this.props;
this.setState({
data: leadsNotValid
});
}
// Edit the cells
renderEditable(cellInfo) {
return (
<div
style={{ backgroundColor: "#fafafa" }}
contentEditable
suppressContentEditableWarning
onBlur={e => {
const data = [...this.state.data];
data[cellInfo.index][cellInfo.column.id] = e.target.innerHTML;
this.setState({ data });
}}
dangerouslySetInnerHTML={{
__html: this.state.data[cellInfo.index][cellInfo.column.id]
}}
/>
);
}
render() {
// loading data or not
const { loadingData } = this.props;
// This "data" should hold the fetched data from the server
const { data } = this.state;
return (
<div>
{loadingData ? (
<Spinner />
) : (
<div>
<ReactTable
data={data}
columns={[
{
Header: "Business Name",
accessor: "BusinessName"
// Cell: this.renderEditable
}
]}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
)}
</div>
);
}
}
const mapStateToProps = state => ({
loadingData: state.leadReducer.loadingData,
leadsNotValid: state.leadReducer.leadsNotValid
});
const mapDispatchToProps = { getLeadsNotValid, updateSpecificNotValidLead };
export default connect(mapStateToProps, mapDispatchToProps)(MisleadLeadsTable);
However when I try to store the data in the State (in componentDidMount) it always comes back empty , and when the table is being rendered it gets an empty array.
It is crucial to store the data in the State because I'm trying to implement an editable table.
The data is stored in leadsNotValid , and if I do :
<ReactTable
data={leadsNotValid} // Notice !! Changed this
columns={[
{
Header: "Business Name",
accessor: "BusinessName"
// Cell: this.renderEditable
}
]}
defaultPageSize={10}
className="-striped -highlight"
/>
Then the data is presented successfully to the user , however it's not in the State of the component.
How can I put the leadsNotValid in the State using setState ?
Here are the Action & Reducer if it's needed (THEY WORK GREAT !) :
Action :
import axios from "axios";
import {
REQUEST_LEADS_NOT_VALID,
REQUEST_LEADS_NOT_VALID_SUCCESS,
UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID,
UPDATE_A_SINGLE_NOT_VALID_LEAD
} from "./types";
export const updateSpecificNotValidLead = updatedLead => async dispatch => {
dispatch({
type: UPDATE_A_SINGLE_NOT_VALID_LEAD
});
const config = {
headers: {
"Content-Type": "application/json"
}
};
const body = JSON.stringify({ updatedLead });
const res = await axios.post(
".......API/Something1/....",
body,
config
);
if (res !== null && res.data !== null) {
dispatch({
type: UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID
});
}
};
export const getLeadsNotValid = () => async dispatch => {
dispatch({
type: REQUEST_LEADS_NOT_VALID
});
const res = await axios.get(".......API/Something2/....");
if (res !== null && res.data !== null) {
dispatch({
type: REQUEST_LEADS_NOT_VALID_SUCCESS,
payload: res.data
});
}
};
Reducer :
import {
GET_MAIN_LEADS_SUCCESS,
REQUEST_MAIN_LEADS,
RELOAD_DATA_MAIN_LEADS_TABLE,
REQUEST_LEADS_NOT_VALID,
REQUEST_LEADS_NOT_VALID_SUCCESS,
UPDATE_A_SINGLE_NOT_VALID_LEAD,
UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID
} from "../actions/types";
const initialState = {
mainLeadsClients: [],
loadingData: null, // general loader
reloadMainLeadTable: 0,
reloadMisleadTable: 0,
leadsNotValid: []
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case REQUEST_LEADS_NOT_VALID:
return {
...state,
loadingData: true
};
case REQUEST_LEADS_NOT_VALID_SUCCESS:
return {
...state,
loadingData: false,
leadsNotValid: payload
};
case UPDATE_A_SINGLE_NOT_VALID_LEAD:
return {
...state,
loadingData: true
};
case UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID:
return {
...state,
reloadMisleadTable: state.reloadMisleadTable + 1,
loadingData: false
};
// ... more
default:
return state;
}
}
You may have to write super(props) instead of super() in order to access props in the constructor.

Maximum update depth exceeded in React Infinite Scroll

I am trying to implement a lazy loading in a MERN stack app like in producthunt. I want to have the posts created on the current date shown by default. If the user scroll down, it will fetch more data on the previous date. I am using react infinite scroll. However, it seems like the app requests to api like an infinite loop without listening on scrolling. I got the following error.
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.
The function is async/await so I don't understand why it keeps calling new requests even though the old request is not resolved yet.
In a Post components
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import UserItem from '../users/UserItem';
import TopDiscussion from '../TopDiscussion';
import SmallAbout from '../SmallAbout';
import { getPostsByDate } from '../../actions/post';
import Moment from 'react-moment';
import InfiniteScroll from 'react-infinite-scroller';
const Posts = ({ getPostsByDate, post: { posts, loading } }) => {
const now = new Date();
const startOfToday = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate()
);
// startOfToday = startOfToday -1
useEffect(() => {
getPostsByDate(startOfToday);
}, [getPostsByDate]);
const [date, setDate] = useState(startOfToday);
const [shown, setShown] = useState();
const getPosts = () => {
getPostsByDate(date);
let count = new Date(date);
count.setDate(count.getDate() - 1);
setDate(count);
};
return loading ? (
<Spinner />
) : (
<div className='main-grid'>
<div className='posts-grid'>
<h1 className='large text-primary'>Ideas</h1>
<div className='posts'>
<div className='post-dummy'>
<InfiniteScroll
dataLength={posts.length}
pageStart={0}
loadMore={getPosts}
hasMore={posts && posts.length < 10}
loader={
<div className='loader' key={0}>
Loading ...
</div>
}
>
{posts
.sort((a, b) =>
a.likes.length > b.likes.length
? -1
: b.likes.length > a.likes.length
? 1
: 0
)
.map(post => (
<PostItem key={post._id} post={post} />
))}
</InfiniteScroll>
</div>
</div>
</div>
<div className='right-panel-grid'>
<SmallAbout />
<UserItem />
<TopDiscussion posts={posts} />
<div
className='fb-group'
data-href='https://www.facebook.com/groups/ideatoshare/'
data-width='350'
data-show-social-context='true'
data-show-metadata='false'
></div>
<iframe
title='producthunt'
style={{ border: 'none' }}
src='https://cards.producthunt.com/cards/posts/168618?v=1'
width='350'
height='405'
frameBorder='0'
scrolling='no'
allowFullScreen
></iframe>
</div>
</div>
);
};
Posts.propTypes = {
getPostsByDate: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post
});
export default connect(
mapStateToProps,
{ getPostsByDate }
)(Posts);
Post reducer
import {
GET_POSTS,
POST_ERROR,
UPDATE_LIKES,
UPDATE_LIKE,
UPDATE_COMMENT_LIKES,
DELETE_POST,
ADD_POST,
GET_POST,
ADD_COMMENT,
REMOVE_COMMENT,
ADD_SUB_COMMENT,
REMOVE_SUB_COMMENT,
UPDATE_STATUS
} from '../actions/types';
const initialState = {
posts: [],
post: null,
loading: true,
error: {}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_POSTS:
return {
...state,
posts: [...state.posts, ...payload],
// posts: payload,
loading: false
};
case GET_POST:
return {
...state,
post: payload,
loading: false
};
case ADD_POST:
return {
...state,
post: payload,
// posts: [payload, ...state.posts],
loading: false
};
case POST_ERROR:
return {
...state,
error: payload,
loading: false
};
case UPDATE_COMMENT_LIKES:
return {
...state,
post: {
...state.post,
comments: payload
},
loading: false
};
case UPDATE_LIKES:
return {
...state,
posts: state.posts.map(post =>
post._id === payload.id ? { ...post, likes: payload.likes } : post
),
loading: false
};
case UPDATE_LIKE:
return {
...state,
post: { ...state.post, likes: payload },
loading: false
};
case UPDATE_STATUS:
return {
...state,
posts: state.posts.map(post =>
post._id === payload.id ? { ...post, status: payload.status } : post
),
loading: false
};
case DELETE_POST:
return {
...state,
posts: state.posts.filter(post => post._id !== payload),
loading: false
};
case ADD_COMMENT:
return {
...state,
// payload is all the comments
post: { ...state.post, comments: payload },
loading: false
};
case ADD_SUB_COMMENT:
return {
...state,
// payload is all the comments of a post
post: { ...state.post, comments: payload },
loading: false
};
case REMOVE_COMMENT:
return {
...state,
post: {
...state.post,
comments: state.post.comments.filter(
comment => comment._id !== payload
),
loading: false
}
};
case REMOVE_SUB_COMMENT:
return {
...state,
post: {
...state.post,
comments: payload
// comments: state.post.comments.map(comment =>
// {
// if (comment._id === payload.commentId) {
// comment.subComments.filter(
// subcomment => subcomment._id === payload.subcommentId
// );
// }
// }
// )
},
loading: false
};
default:
return state;
}
}
Post action
//GetTodayPost
export const getPostsByDate = date => async dispatch => {
try {
const res = await axios.get(`/api/posts/${date}`);
dispatch({
type: GET_POSTS,
payload: res.data
});
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
post API
router.get('/:date', async (req, res) => {
try {
const startOfToday = new Date(req.params.date);
const endOfToday = new Date(req.params.date);
endOfToday.setDate(endOfToday.getDate() + 1);
const posts = await Post.find({
date: { $gte: startOfToday, $lte: endOfToday }
}).sort({
date: -1
});
res.json(posts);
} catch (err) {
console.error(err.message);
res.send(500).send('Server Error');
}
});
Edit: I have updated your repo with a working example.. Your issue is that your API is not 'unlimited', as you claimed, and you do in fact need to check if all posts have been loaded or not.. Using the example I supplied along with the updates I made to your repo, you should be able to figure out things from here.
Ok.. so after some testing with InfiniteScroll, this appears to be happening because your hasMore property always equals true... You have to specify some type of condition so that InfiniteScroll knows when to, and when not to, load more data.
I got the same error as you before adding a check, which tells InfiniteScroll that there is no more data to load.
I have built the following example to show how to use InfiniteScroll
You can view a live demo here
PostsContainer.js
import React, { useState, useEffect } from "react";
import Posts from "./Posts";
import InfiniteScroll from "react-infinite-scroller";
const loadingStyle = {
textAlign: "center",
fontSize: "48px",
color: "red"
};
function PostsContainer({ url, itemsToDisplay = 5 }) {
const [data, setData] = useState();
const [shownData, setShownData] = useState();
useEffect(() => {
(async () => {
let items = await fetchPosts(url);
let itemsToShow = selectNItems(items, itemsToDisplay);
setShownData(itemsToShow);
setData(items);
})();
}, [url]);
async function fetchPosts(url) {
let res = await fetch(url);
return await res.json();
}
const selectNItems = (obj, n) => {
return obj.slice(0, n);
}
const loadMorePosts = () => {
let items =
data &&
shownData &&
selectNItems(data, shownData.length + itemsToDisplay)
setShownData(items);
};
return (
<InfiniteScroll
pageStart={0}
loadMore={loadMorePosts}
hasMore={data && shownData && data.length > shownData.length}
loader={<div style={loadingStyle}>Loading ...</div>}
useWindow={true}
>
<Posts posts={shownData} />
</InfiniteScroll>
);
}
export default PostsContainer;
Posts.js
import React from 'react';
import Post from './Post';
const headingStyle = {
textAlign: 'center',
}
function Posts({ posts }) {
return(
<div>
<h1 style={headingStyle}>Posts</h1>
{posts && posts.length > 0 && posts.map((p, i) => <Post key={i} data={p} index={i} />)}
</div>
);
}
export default Posts;
Post.js
import React from "react";
const containerStyle = {
border: "1px solid black",
margin: "10px auto",
maxWidth: "50vw",
padding: '0px 10px 0px 0px'
};
const postHeaderStyle = {
textAlign: "center",
padding: "0px"
};
function Post({ data, index }) {
return (
<div style={containerStyle}>
{index !== "" && <h3 style={postHeaderStyle}>Post #{index}</h3>}
<ul>
<li>
<b>userId:</b> {data.userId}
</li>
<li>
<b>id:</b> {data.id}
</li>
<li>
<b>title:</b> {data.title}
</li>
<li>
<b>body:</b> {data.body}
</li>
</ul>
</div>
);
}
export default Post;
index.js
import React from "react";
import { render } from "react-dom";
import PostsContainer from "./Components/PostsContainer";
function App() {
return (
<PostsContainer
itemsToDisplay={5}
url="https://jsonplaceholder.typicode.com/posts"
/>
);
}
render(<App />, document.getElementById("root"));

React.js: props.state is blank(null)

I want to Todo List in use React.js + Redux.
I make reducer file:
import { ADD_POST, REMOVE_POST } from "../actions/index.jsx";
const initialState = {
title: "",
content: ""
};
export default function Post(state = initialState, action) {
switch (action.type) {
case ADD_POST:
return [
...state,
{
id: action.id,
title: action.title,
content: action.content
}
];
case REMOVE_POST:
return state.filter(({ id }) => id !== action.id);
default:
return state;
}
}
And, I edit App.js :
class App extends Component {
render() {
return (
<div className="App">
<Input />
<List posts={this.props.allPosts} />
</div>
);
}
}
const mapStateToProps = state => {
return {
allPosts: [state.title, state.content]
};
};
export default connect(mapStateToProps, null)(App);
And, List Component is...:
render() {
return (
<div>
<ul>
{this.props.posts.map((post, index) => (
<Item {...post} key={index} />
))}
</ul>
</div>
);
}
}
I am experiencing the error "Can not read property 'map' of undefined" and can not proceed.
How can I fix it?
I'm referring to multiple sources, but I'm having difficulty because I can only see text for one 'text' state, and two sources like 'title' and 'content' states.
-------_FIX
I fix error, but props.state is blank.
I add input tag with texts but it not change everything.
-------Actions
export const ADD_POST = "ADD_POST";
export const REMOVE_POST = "REMOVE_POST";
let nextId = 0;
export function addPost(title, content) {
return {
type: ADD_POST,
id: nextId++,
title,
content
};
}
export function removePost(id) {
return {
type: REMOVE_POST,
id
};
}
I think you're confusing with the data type of your state. The below snippet might work for you. I've kept your state as an array of posts with initialState being an empty array.
So in your reducer file, initialise the initialState as:
import {
ADD_POST,
REMOVE_POST
} from "../actions/index.jsx";
const initialState = [];
export default function Post(state = initialState, action) {
switch (action.type) {
case ADD_POST:
return [
...state,
{
id: action.id,
title: action.title,
content: action.content
}
];
case REMOVE_POST:
return state.filter(({
id
}) => id !== action.id);
default:
return state;
}
}
In App.js, in the function mapStateToProps, map allPosts to state which is an array.
class App extends Component {
render() {
return (
<div className="App">
<Input />
<List posts={this.props.allPosts} />
</div>
);
}
}
const mapStateToProps = state => {
return {
allPosts: state
};
};
export default connect(mapStateToProps, null)(App);

Dispatching an in-progress action in Redux

I have a signup form, which posts data asynchronously to an API and then does some stuff based on the response. I am dispatching a "signup_in_progress" action as soon as the function is called, with a payload of "true", and update my state based on that, then dispatching this action with a payload of "false" when the promise is resolved.
I can see that the dispatch happens as intended by putting a console.log() statement in the reducer.
What I would like to happen is for a spinner to appear instead of the signup form when the signup_in_progress piece of state is "true." But that's not happening. Any idea what I'm missing?
My action:
export function signUpUser(props) {
return function(dispatch) {
dispatch({ type: SIGNUP_IN_PROGRESS, payload: true });
axios
.post(`${ROOT_URL}/signup`, props)
.then(() => {
dispatch({ type: SIGNUP_IN_PROGRESS, payload: false });
dispatch({ type: SIGNUP_SUCCESS });
browserHistory.push(`/signup/verify-email?email=${props.email}`);
})
.catch(response => {
dispatch({ type: SIGNUP_IN_PROGRESS, payload: false });
dispatch(authError(SIGNUP_FAILURE, response.response.data.error));
});
};
}
The relevant part of my reducer:
import {
...
SIGNUP_IN_PROGRESS,
SIGNUP_SUCCESS,
SIGNUP_FAILURE...
} from '../actions/types';
export default function(state = {}, action) {
switch (action.type) {
...
case SIGNUP_IN_PROGRESS:
return { ...state, signing_up: action.payload };
...
My connected component:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import * as actions from '../../actions';
import SignupFirstPage from './signupComponents/signupFirstPage';
import SignupSecondPage from './signupComponents/signupSecondPage';
import SignupThirdPage from './signupComponents/SignupThirdPage';
import Spinner from 'react-spinkit';
class SignUp extends Component {
constructor(props) {
super(props);
this.nextPage = this.nextPage.bind(this);
this.previousPage = this.previousPage.bind(this);
this.state = {
page: 1
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
nextPage() {
this.setState({ page: this.state.page + 1 });
}
previousPage() {
this.setState({ page: this.state.page - 1 });
}
handleFormSubmit(props) {
this.props.signUpUser(props);
}
render() {
const { handleSubmit } = this.props;
const { page } = this.state;
if (this.props.signingUp) {
return (
<div className="dashboard loading">
<Spinner name="chasing-dots" />
</div>
);
}
return (
<div>
{page === 1 && <SignupFirstPage onSubmit={this.nextPage} />}
{page === 2 && (
<SignupSecondPage
previousPage={this.previousPage}
onSubmit={this.nextPage}
/>
)}
{page === 3 && (
<SignupThirdPage
previousPage={this.previousPage}
onSubmit={values => this.props.signUpUser(values)}
/>
)}
<div>
{this.props.errorMessage &&
this.props.errorMessage.signup && (
<div className="error-container">
Oops! {this.props.errorMessage.signup}
</div>
)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
singingUp: state.auth.signing_up,
errorMessage: state.auth.error
};
}
SignUp = reduxForm({ form: 'wizard' })(SignUp);
export default connect(mapStateToProps, actions)(SignUp);
You just have a typo in your code:
function mapStateToProps(state) {
return {
singingUp: state.auth.signing_up, // TYPO!!
errorMessage: state.auth.error
};
}
should be changed to
function mapStateToProps(state) {
return {
signingUp: state.auth.signing_up,
errorMessage: state.auth.error
};
}
I recommend using redux-pack which allows you to handle three phases of the request in the reducer: start, success and error.
Then you can use the start handler to set the boolean to true, and on success set it to false.

Categories

Resources