How to dispatch an action on page load in react-redux? - reactjs

I have to display a table with lot of data. There is a pagination button which display 10 record per page. I have one button called "FETCH" on-click of it table is populating. How do load my table on load of the page.
action.js
import fetch from 'isomorphic-fetch';
export const ADD_BENEFICIARY = 'ADD_BENEFICIARY';
export const REMOVE_BENEFICIARY = 'REMOVE_BENEFICIARY';
export const ADD_PLAN_TO_COMPARE = 'ADD_PLAN_COMPARE';
export const REMOVE_PLAN_FROM_COMPARE = 'REMOVE_PLAN_COMPARE';
export const SHOW_PLAN_COMPARE = 'SHOW_PLAN_COMPARE';
export const NEXT_PAGE_PLAN_LIST = 'NEXT_PAGE_PLAN_LIST';
export const PREV_PAGE_PLAN_LIST = 'PREV_PAGE_PLAN_LIST';
export const REQUEST_PAGE_PLAN_LIST = 'REQUEST_PAGE_PLAN_LIST';
export const RECEIVE_PAGE_PLAN_LIST = 'RECEIVE_PAGE_PLAN_LIST';
export const showNextPageOfPlans = () => {
return {
type: NEXT_PAGE_PLAN_LIST
}
}
export const showPreviousPageOfPlans = () => {
return {
type: PREV_PAGE_PLAN_LIST
}
}
export const requestPageOfPlans = (startIdx, pageSize) => {
return {
type: REQUEST_PAGE_PLAN_LIST,
start: startIdx,
pageSize: pageSize
}
}
export const receivePageOfPlans = (startIdx, json) => {
return {
type: RECEIVE_PAGE_PLAN_LIST,
start: startIdx,
plans: json
}
}
export const fetchPlans = (startIdx, pageSize) => {
var str = sessionStorage.getItem('formValue'); //JSON.stringify(formValues);
return function (dispatch) {
dispatch(requestPageOfPlans(startIdx, pageSize));
return fetch('http://172.16.32.57:9090/alternatePlans/plans/list/', {method: 'post', body: str, headers: new Headers({'Content-Type': 'application/json'}) })
.then(response => response.json())
.then(json =>
dispatch(receivePageOfPlans(startIdx, json))
)
}
}
reducer.js
import { REQUEST_PAGE_PLAN_LIST, RECEIVE_PAGE_PLAN_LIST,
NEXT_PAGE_PLAN_LIST, PREV_PAGE_PLAN_LIST } from './actions';
const initialPaging = {
startIndex: 0,
lastIndex: 0,
pageSize: 10
}
const paging = (state = initialCurrentPage, action) => {
switch (action.type) {
case NEXT_PAGE_PLAN_LIST:
if (state.startIndex+state.pageSize <= state.lastIndex) {
return { ...state, startIndex: state.startIndex+state.pageSize };
}
else {
return state;
}
case PREV_PAGE_PLAN_LIST:
if (state.startIndex-state.pageSize >= 0) {
return { ...state, startIndex: state.startIndex-state.pageSize };
}
else {
return state;
}
case REQUEST_PAGE_PLAN_LIST:
return { ...state, isFetching: true };
case RECEIVE_PAGE_PLAN_LIST:
return { ...state, isFetching: false };
default:
return state;
}
}
var initialPlans = [];
const plans = (state = initialPlans, action) => {
switch (action.type) {
case RECEIVE_PAGE_PLAN_LIST:
return action.plans.plans;
default:
return state;
}
}
const allReducers = (state = {}, action) => {
let items = plans(state.plans, action);
return {
plans: items,
paging: paging({ ...initialPaging, ...state.paging, lastIndex: items.length-1 }, action)
}
}
export default allReducers;
P.S. I am new to react-redux. Official Documentation is good but very less explanation is given.

You call it from the componentDidMount() of the react component for your page, or wherever it makes sense. So in that component file:
import { requestPageOfPlans } from 'actions';
import React from 'react';
import { connect } from 'react-redux';
class MyComponent extends React.Component {
componentDidMount() {
this.props.requestPageOfPlans();
}
}
export default connect((state) => state, { requestPageOfPlans })(MyComponent);
So the key here is the connect setup. The first parameter is how you wish to transform state (your reducers) into data for props. Adjust that as you need to. The second one is what actions you wish to bind. You can also import dispatch manually, but I personally like this pattern. It sets up the actions as props on the component, and you can call it that way. Just pass the arguments to it as you need to. This page here: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options explains the connect function in more detail.
Edit
Given you're paginating on the front end, you'll need to adjust the connect mapStateToProps function to pass the pagination data down to the component, and then loop through it to display what you need. Personally I would do the pagination on the back end, and just do new requests for each page. Depends on how many records you're expecting to have.
So in the connect do something like this:
export default connect((state) => {
return {
startIndex: state.paging.lastIndex,
pageSize: state.paging.pageSize,
lastIndex: state.paging.lastIndex,
plans: state.plans
};
}, { requestPageOfPlans })(MyComponent);
Then in your component, loop through the plans using those indexes:
render() {
const plans = [];
const maxIndex = Math.min(this.props.startIndex + this.props.pageSize, this.props.lastIndex);
for (let i = this.props.startIndex || 0; i < maxIndex; i++) {
const plan = this.props.plans[i];
plans.push(<Plan key={plan.id} plan={plan} />);
}
return (
<ul>{plans}</ul>
);
}
Making some assumptions again on how you plan to render it, if you have a component called Plan, etc. But that's roughly how you can work with it.

componentDidMount is a good timing to load your fist page.
BTW, In my opinion, the page change is not the action, it is just some param for load your data.
ActionCreator
const userurl = '/rest/users'
export const loadUsers = (page = 0) => {
return (dispatch) => {
axios.get(userurl+"?page="+page)
.then(function(resp) {
dispatch(loadUsersOK(resp.data._embedded.users, resp.data.page));
})
.catch(function(error) {
...
})
};
}
Component JSX
<span style={{marginRight:15, fontSize: 14, color: '#333'}}>{page.number*page.size+1}-{page.number*page.size+page.size} of {' '} {page.totalElements}</span>
<FlatButton style={flatButtonStyle} icon={<NavigationChevronLeft/>} onTouchTap={this.prevPage} />
<FlatButton style={flatButtonStyle} icon={<NavigationChevronRight/>} onTouchTap={this.nextPage} />
Component Handlers
prevPage() {
const { page } = this.props.users;
this.props.loadUsers(page.number - 1);
}
nextPage() {
const { page } = this.props.users;
this.props.loadUsers(page.number + 1);
}
Connect
const mapStateToProps = state => {
return {
users: state.users
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
loadUsers: (page=0) => dispatch(loadUsers(page))
}
}
const Wrapped = connect(
mapStateToProps,
mapDispatchToProps
)(Users)
ONLY ONE ACTION
export const loadUsersOK = (result, page) => ({
type: LOAD_USERS_OK,
result,
page
})
May it helps.

Related

using realm-web with React

First of all, I am new with react. It has been two weeks since I am working with it
I am trying to use "realm-web" with react (web). I would like to write realm provider in order to access realmApp everywhere in my application. However my attempts didn't go well. The application is giving " Uncaught (in promise) RangeError: Maximum call stack size exceeded". But beside this error, I am sure there is more to be handled.
Here is my implementation:
const RealmProvider: React.ForwardRefRenderFunction<HTMLElement, PropTypes> = ({ app: realmApp, store, children }) => {
let realm = useTypedSelector(store => store.realm);
let app = useRef<Realm.App>();
const credentials = React.useMemo(() => Realm.Credentials.apiKey(process.env.REACT_APP_REALM_APP_API_KEY!), []);
useEffect(() => {
app.current = realmApp
console.log(realmApp)
if (app.current.currentUser == null) {
loadingRealmApp()
app.current.logIn(credentials)
.then((res) => store.dispatch(storeRealmApp(res)))
//.catch(reason => console.log("RealmError", reason));
}
//Specify how to clean up after this effect:
return function cleanup() {
app.current?.currentUser?.logOut().then(() =>
store.dispatch(unloadRealmApp())
);
};
}, [realmApp, store]);
return (
<>
{ React.Children.only(children)}
</>
);
}
export default RealmProvider;
Reducer
// Actions
const STORE_REALM_APP = 'atlas/realm/STORE_REALM_APP'
const STORE_REALM_APP_ERROR = 'atlas/realm/STORE_USER_ERROR'
const UNLOAD_REALM_APP = 'atlas/realm/UNLOAD_REALM_APP'
const LOADING_REALM_APP = 'atlas/realm/LOADING_REALM_APP'
type State = {
//realmApp?: Realm.App
user?: Realm.User
isLoadingRealmApp: boolean
}
const initialState: State = {
//realmApp: undefined,
user: undefined,
isLoadingRealmApp: false
};
type RealmAction = {
payload: Realm.User
} & Action;
// Reducer
const realmReducer = function (state: State = initialState, action: RealmAction): State {
switch (action.type) {
case STORE_REALM_APP:
return {
...state,
isLoadingRealmApp: false,
user: action.payload
}
case LOADING_REALM_APP:
return {
...state,
isLoadingRealmApp: true
}
case STORE_REALM_APP_ERROR:
case UNLOAD_REALM_APP:
return {
...state,
user: undefined,
isLoadingRealmApp: false
}
default:
return state
}
}
export default realmReducer;
export function storeRealmApp(app: Realm.User) {
console.log("storeRealmApp", app)
return {
type: STORE_REALM_APP,
payload: app
}
}
export function loadingRealmApp() {
return {
type: LOADING_REALM_APP
}
}
export function storeRealmAppError(reason: any) {
return {
type: STORE_REALM_APP_ERROR,
payload: reason
}
}
export function unloadRealmApp() {
return {
type: UNLOAD_REALM_APP
}
}
Realm.ts
const realmApp: Realm.App = new Realm.App({ id: process.env.REACT_APP_REALM_APP_ID!, app: { name: "Atlas" } });
//const mongodb = realmApp.currentUser!.mongoClient("mongodb-atlas")
const useMongodb = () => {
const user = useTypedSelector(store => store.realm.user);
console.log(user)
return user!.mongoClient("mongodb-atlas");
}
export { realmApp, useMongodb }
I guess I am doing something wrong, or this is not the way of doing this. I need some help :)
In case someone needs it, I figure it out.
import React from "react";
import * as Realm from "realm-web";
type ContextType = {
currentUser: Realm.User,
logIn: (credentials: Realm.Credentials) => void
logOut: () => void
} & Realm.App
const RealmAppContext = React.createContext<ContextType>({
} as any);
export const useRealmApp = () => {
const app = React.useContext(RealmAppContext);
if (!app) {
throw new Error(
`You must call useRealmApp() inside of a <RealmAppProvider />`
);
}
return app;
};
export const useMongodb = () => {
const app = React.useContext<ContextType>(RealmAppContext);
const mongodb = app.currentUser.mongoClient("mongodb-atlas")
if (!mongodb) {
throw new Error(
`You must call useRealmApp() inside of a <RealmAppProvider />`
);
}
return mongodb;
};
type PropTypes = {
appId: string,
//children: JSX.Element
}
export const RealmAppProvider: React.ForwardRefRenderFunction<HTMLElement, PropTypes> = ({ appId, children }) => {
const [app, setApp] = React.useState(new Realm.App(appId));
React.useEffect(() => {
setApp(new Realm.App(appId));
}, [appId]);
// Wrap the Realm.App object's user state with React state
const [currentUser, setCurrentUser] = React.useState(app.currentUser);
async function logIn(credentials: Realm.Credentials) {
await app.logIn(credentials);
// If successful, app.currentUser is the user that just logged in
setCurrentUser(app.currentUser);
}
async function logOut() {
// Log out the currently active user
await app.currentUser?.logOut();
// If another user was logged in too, they're now the current user.
// Otherwise, app.currentUser is null.
setCurrentUser(app.currentUser);
}
const wrapped = { ...app, currentUser, logIn, logOut };
return (
<RealmAppContext.Provider value={wrapped as any}>
{children}
</RealmAppContext.Provider>
);
};

Dispatching action calls incorrect reducer

Summary
In order to learn Redux, I am incorporating some state, actions, reducers, and trying to see how they are used in React Components.
I have set up a test object...
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
...and aim to:
increment the navigationCount by 1 when visiting pages
add or subtract from someNumber
push() & pop() elements from someList.
Versions
Currently using gatsby ^2.5.0, react ^16.8.6, and react-redux ^6.0.1.
Code
actions & reducers
import { combineReducers } from 'redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from './actionTypes.js';
// state
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
// action creators returning actions
export const pageIncrementer = navigationCount => {
return {
type: PAGE_INCREMENT,
navigationCount,
};
};
export const numberAdder = numberToAdd => {
return {
type: NUMBER_INCREASE,
numberToAdd,
};
};
export const numberMinuser = numberToMinus => {
return {
type: NUMBER_DECREASE,
numberToMinus,
};
};
export const listPusher = itemToAdd => {
return {
type: LIST_PUSH,
itemToAdd,
}
};
export const listPopper = () => {
return {
type: LIST_POP,
}
};
// reducers
const pageIncrementReducer = (state = initialState, action) => {
switch (action.type) {
case PAGE_INCREMENT:
return Object.assign({}, ...state, {
navigationCount: action.navigationCount+1
});
default:
return state.navigationCount;
}
};
const numberChanger = (state = initialState, action) => {
switch (action.type) {
case NUMBER_INCREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber+action.numberToAdd,
});
case NUMBER_DECREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber-action.numberToMinus,
});
default:
return state.someNumber;
};
};
const listChanger = (state = initialState, action) => {
switch (action.type) {
case LIST_POP:
return Object.assign({}, ...state, {
someList: state.someList.pop(),
});
case LIST_PUSH:
return Object.assign({}, ...state, {
someList: state.someList.push(action.itemToAdd),
});
default:
return state.someList;
}
}
// store
const rootReducer = combineReducers({
pageIncrementReducer,
numberChanger,
listChanger,
});
export default rootReducer;
React Component
import React from 'react';
import Layout from '../components/common/Layout.jsx';
import LandingBanner from '../components/landing/LandingBanner.jsx';
import LandingNavgrid from '../components/landing/LandingNavgrid.jsx';
import LandingApp from '../components/landing/LandingApp.jsx';
import { connect } from 'react-redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from '../state/actionTypes';
class LandingPage extends React.Component {
constructor(props){
super(props);
this.state = {
appliedNum: 2000,
};
}
componentDidMount(){
// this.props.pageIncrement(); // => numberChanger returned undefined
// this.props.numberIncrease(4444); // => pageIncrementReducer returned undefined
// this.props.numberDecrease(4444); // => pageIncrementReducer returned undefined
// this.props.listPush(4444); // => pageIncrementReducer returned undefined
this.props.listPop();
}
render(){
return (
<Layout>
<LandingBanner/>
<LandingNavgrid/>
<LandingApp/>
</Layout>
)
}
}
const filterNumbers = (list=[]) => {
console.log('filterNumbers list: ', list);
return list.filter(listElement => !!Number(listElement));
};
const mapStateToProps = (state, ownProps) => {
return {
someNumber: state.someNumber,
someList: filterNumbers(state.someList),
navigationCount: state.navigationCount,
};
};
const mapDispatchToProps = (dispatch) => {
return {
pageIncrement: () => dispatch({ type: PAGE_INCREMENT }),
numberIncrease: () => dispatch({ type: NUMBER_INCREASE }),
numberDecrease: () => dispatch({ type: NUMBER_DECREASE }),
listPush: () => dispatch({ type: LIST_PUSH }),
listPop: () => dispatch({ type: LIST_POP }),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LandingPage);
Errors
redux.js:449 Uncaught Error: Given action "LIST_POP", reducer
"pageIncrementReducer" returned undefined. To ignore an action, you
must explicitly return the previous state. If you want this reducer to
hold no value, you can return null instead of undefined.
first of all, you always need to return state on the default switch case.
default:
return state;

React / Redux - is a function prop for deferred state loading in mapStateToProps bad?

This is my component:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Divider } from "antd";
import MovieList from "../components/MovieList";
import IncreaseCountButton from "../components/IncreaseCountButton";
import DeleteButton from "../components/DeleteButton";
import { deleteMovie, increaseCount } from "../actions/movies";
import { getIsDeleting, getIsIncreasing } from "../reducers/actions";
export class MovieListContainer extends Component {
constructor(props) {
super(props);
this.handleIncrease = this.handleIncrease.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
static propTypes = {
isIncreasing: PropTypes.func.isRequired,
isDeleting: PropTypes.func.isRequired,
};
async handleIncrease(movie) {
await this.props.increaseCount(movie, this.props.token);
}
async handleDelete(movie) {
await this.props.deleteMovie(movie.id, this.props.token);
}
render() {
return (
<MovieList movies={this.props.movies}>
{(text, movie) => (
<div>
<IncreaseCountButton
onIncrease={() => this.handleIncrease(movie)}
loading={this.props.isIncreasing(movie.id)}
/>
<Divider type="vertical" />
<DeleteButton
onDelete={() => this.handleDelete(movie)}
loading={this.props.isDeleting(movie.id)}
/>
</div>
)}
</MovieList>
);
}
}
export const mapStateToProps = state => ({
isIncreasing: id => getIsIncreasing(state, id),
isDeleting: id => getIsDeleting(state, id),
});
export default connect(
mapStateToProps,
{ deleteMovie, increaseCount }
)(MovieListContainer);
I feel like this might be bad for performance/reconciliation reasons, but not sure how else to retrieve the state in a way that hides implementation details.
Gist link: https://gist.github.com/vitalicwow/140c06a52dd9e2e062b2917f5c741727
Any help is appreciated.
Here is how you can handle these asynchronous actions with redux. You can use thunk to perform 2 actions and can store a flag to determine what is being done to an object (Deleting, Changing, etc):
action
export const deleteMovieAction = id => {
return dispatch => {
dispatch({ type: "MOVIE_DELETING", id });
setTimeout(() => {
dispatch({ type: "MOVIE_DELETED", id });
}, 2000);
};
};
reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case "MOVIE_DELETING": {
const movies = [...state.movies];
movies.find(x => x.id === action.id).isDeleting = true;
return { ...state, movies };
}
case "MOVIE_DELETED": {
const movies = state.movies.filter(x => x.id !== action.id);
return { ...state, movies };
}
default:
return state;
}
};
https://codesandbox.io/s/k3jnv01ymv
An alternative is to separate out the ids into a new array that are being deleted
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case "MOVIE_DELETING": {
const movieDeletingIds = [...state.movieDeletingIds, action.id];
return { ...state, movieDeletingIds };
}
case "MOVIE_DELETED": {
const movieDeletingIds = state.movieDeletingIds.filter(
x => x.id !== action.id
);
const movies = state.movies.filter(x => x.id !== action.id);
return { ...state, movieDeletingIds, movies };
}
default:
return state;
}
};
https://codesandbox.io/s/mj52w4y3zj
(This code should be cleaned up, but is just to demo using thunk)

React component does not re-render reactively (Redux Observable + rxjs + rx-http-requst)

I have an API endpoint that returns a list of users in an 'application/stream+json' type response. The items are separated by a new line character.
Example data can be seen here.
Component
class UserList extends Component {
componentDidMount() {
const { fetchUsers } = this.props;
fetchUsers();
}
render() {
const { isFetching = false, users = [] } = this.props;
if (isFetching) {
return <Loader message="Users are loading..." />;
}
if (!users || users.length === 0) {
return 'No users found.';
}
const children = users
.map(user => <UserListItem key={user.id} user={user} />);
return (
<div className="UserList">
<Paper>
<List>
<Subheader>Users</Subheader>
{children}
</List>
</Paper>
</div>
);
}
}
UserList.propTypes = {
users: PropTypes.arrayOf(PropTypes.any),
isFetching: PropTypes.bool.isRequired,
fetchUsers: PropTypes.func.isRequired,
};
UserList.defaultProps = {
users: [],
};
function mapStateToProps(state) {
const { users, isFetching } = state.users;
return {
users,
isFetching,
};
}
function mapDispatchToProps(dispatch) {
return {
fetchUsers: bindActionCreators(actions.fetchUsers, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserList);
Reducer
const initialState = {
users: [],
isFetching: false,
};
function fetchUsers(state) {
return {
...state,
isFetching: true,
};
}
function fetchUsersItemReceived(state, action) {
const { user } = action;
return {
...state,
users: [...state.users, user],
isFetching: false,
};
}
export default function (state = initialState, action) {
switch (action.type) {
case actionTypes.FETCH_USERS_REQUEST:
return fetchUsers(state);
case actionTypes.FETCH_USERS_ITEM_RECEIVED:
return fetchUsersItemReceived(state, action);
default:
return state;
}
}
Action (the parser is the Streaming JSON Parser found here)
export function fetchUsers() {
return {
type: actionTypes.FETCH_USERS_REQUEST,
};
}
function fetchUsersItemReceived(user) {
return {
type: actionTypes.FETCH_USERS_ITEM_RECEIVED,
user,
};
}
function fetchUsersSuccess() {
return {
type: actionTypes.FETCH_USERS_SUCCESS,
};
}
function fetchUsersFailure(error) {
return {
type: actionTypes.FETCH_USERS_FAILURE,
error,
};
}
function getJsonStream(url) {
const emitter = new Subject();
const req$ = RxHR
.get(url)
.flatMap(resp => resp.body)
.subscribe(
(data) => {
parser.write(data);
parser.onValue = (value) => {
if (!parser.key) {
emitter.next(value);
}
};
},
err => emitter.error(err),
() => emitter.complete(),
);
return emitter;
}
export const fetchUsersEpic = action$ =>
action$.ofType(actionTypes.FETCH_USERS_REQUEST)
.concatMap(() => getJsonStream(`${api.API_BASE_URL}/user`))
.map(user => fetchUsersItemReceived(user));
configureStore.js
const logger = createLogger();
const epicMiddleware = createEpicMiddleware(rootEpic);
const createStoreWithMiddleware = applyMiddleware(epicMiddleware, logger)(createStore);
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
While the list component should be refreshed after EACH item is received, it is refreshed AFTER the whole list is received. Can someone point me to the blocking point in the code?
This isn't a solution to your particular issue (unless by accident lol) but I think a custom Observable is a better fit in this situation instead of a Subject. You can also hook into the Parser's error callback too.
Figured that others searching for streaming JSON with rxjs later might find this handy (untested)
function streamingJsonParse(data$) {
return new Observable((observer) => {
const parser = new Parser();
parser.onError = (err) => observer.error(err);
parser.onValue = (value) => {
if (!parser.key) {
observer.next(value);
}
};
// return the subscription so it's correctly
// unsubscribed for us
return data$
.subscribe({
next: (data) => parser.write(data),
error: (e) => observer.error(e),
complete: () => observer.complete()
});
});
}
function getJsonStream(url) {
return RxHR
.get(url)
.mergeMap(resp => streamingJsonParse(resp.body));
}
When you've had a chance to put together that jsbin let me know!
Turns out the problem was with the jsonParse lib. Switching to oboe.js
fixed it. Using the "!" node selector to select multiple root JSON elements i was able to transform the character stream to a user object stream.
Action
function getJsonStream(url) {
const emitter = new Subject();
const emitter = new Subject();
oboe(url)
.node('!', (item) => {
emitter.next(item);
})
.fail((error) => {
emitter.error(error);
});
return emitter;
}
export const fetchUsersEpic = action$ =>
action$.ofType(actionTypes.FETCH_USERS_REQUEST)
.switchMap(() => getJsonStream(`${api.API_BASE_URL}/user`))
.map(user => fetchUsersItemReceived(user));

React Redux new data replacing current data instead of extend it and function only run once

I'm using this package https://github.com/RealScout/redux-infinite-scroll to make infinite scroll on list of brand. Here is my code:
Container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actions, getBrands } from '../reducer';
import Infinite from 'react-infinite';
import InfiniteScroll from 'redux-infinite-scroll';
import SearchBox from '../components/SearchBox';
import CardList from '../components/CardList';
const { fetchBrands } = actions;
class BrandList extends Component {
componentDidMount() {
this.props.fetchBrands({ page: 1 });
}
renderList() {
const brands = this.props.brands;
return brands.map((brand) => {
return (
<CardList key={brand.id} name={brand.name} avatar={brand.avatar.thumbnail} follower={brand.follows_count} />
);
});
}
toggle() {
return this.props.isFetching;
}
loadMore() {
const {lastPage, currentPage} = this.props;
const nextPage = currentPage ? parseInt(currentPage) + 1 : 1;
if(currentPage && currentPage <= lastPage){
this.props.fetchBrands({page: nextPage});
}
}
render() {
return (
<div>
<SearchBox />
<div className="row">
<InfiniteScroll
items={this.renderList()}
loadMore={this.loadMore.bind(this)}
/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
brands: getBrands(state),
isFetching: state.brand.isFetching,
currentPage: state.brand.currentPage,
lastPage: state.brand.lastPage
};
}
export default connect(mapStateToProps, { fetchBrands })(BrandList);
Reducer:
import axios from 'axios';
// Define Types
export const types = {
// brand list
FETCH_BRANDS: 'fetch_brands',
FETCH_BRANDS_SUCCESS: 'fetch_brands_success',
FETCH_BRANDS_ERROR: 'fetch_brands_failure',
FETCH_BRAND: 'fetch_brand',
FETCH_BRAND_SUCCESS: 'fetch_brand_success',
FETCH_BRAND_ERROR: 'fetch_brand_failure',
};
const { FETCH_BRANDS, FETCH_BRANDS_SUCCESS, FETCH_BRANDS_ERROR } = types;
// Define Reducer
export const INITIAL_STATE = { brands: [], brand: {}, isFetching: false, error: null, currentPage: 1 };
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_BRANDS:
return { ...state, isFetching: true };
case FETCH_BRANDS_SUCCESS:
return { ...state, brands: action.payload.brands.data, currentPage: action.payload.brands.current_page, lastPage: action.payload.brands.last_page };
case FETCH_BRANDS_ERROR:
return { ...state, error: action.payload };
default:
return state;
}
}
// Define Actions
export const actions = {
fetchBrands: ({page, count = 15}) => {
return (dispatch) => {
dispatch({ type: FETCH_BRANDS });
axios.get(`brands?page=${page}&count=${count}`)
.then((response) => {
const {data} = response;
if (data.code == 200) {
dispatch({ type: FETCH_BRANDS_SUCCESS, payload: data });
}
});
};
}
};
// SELECTOR
export const getBrands = (state) => state.brand.brands;
it run loadMore function successfully but it not extend current list, it replace it instead.
loadmore function only run once. it should run 10 times.
do I miss something on my code to make it scroll?
Try adding
brands: [ ...state.brands, ...action.payload.brands.data]
like this in your reducer
case FETCH_BRANDS_SUCCESS:
return { ...state, brands: [ ...state.brands, ...action.payload.brands.data], currentPage: action.payload.brands.current_page, lastPage: action.payload.brands.last_page };
Which means that you are concating current list with upcoming list (versioned data)

Resources