Fetch function in React Native with mobx-react-lite and React hooks - javascript

I'm newbie in mobx-react and i need to write fetch function that get data from API and after renders it to FlatList. I have already created fetch function, set initial state with useContext hook and wrapped my app by observer mobx class. But now i need to implement of getting the data from the server. Can you tell me please which will be the best way to do it?
import { createContext } from 'react'
import { action, decorate, observable, computed, runInAction } from 'mobx'
import fetchData from '../utils/fetchData'
import mapObjects from '../utils/mapObjects'
class DataStore {
data = null
error = false
loading = true
get getData(){
return this.data
}
get getError(){
return this.error
}
get getLoading(){
return this.loading
}
async fetchData(url) {
this.data = null
this.error = false
this.loading = true
try {
console.log('TRY')
const response = await fetch(url)
const jsonResponse = await response.json()
const obj = await mapObjects(jsonResponse)
runInAction(() => {
console.log('WRITE!!!')
this.loading = false
this.data = obj
})
} catch (err) {
runInAction(() => {
console.log(err)
this.loading = false
this.error = err
})
}
}
}
decorate(DataStore, {
data: observable,
error: observable,
loading: observable,
fetchData: action
})
export default createContext(new DataStore())
My component:
import React, { useContext, useEffect, useState } from 'react'
import { ActivityIndicator, FlatList, Platform, StyleSheet, View } from 'react-native'
import DataStore from '../mobx/DataStore'
import { autorun } from 'mobx'
import { ChartsHeader, CryptoItem, IconsHeader, ProjectStatusBar } from '../components'
import { useFetch } from '../hooks/useFetch'
import { WP, HP } from '../constants'
const styles = StyleSheet.create({
container: {
flex: 1
}
})
const ChartsScreen = ({ navigation }) => {
const { container } = styles
const store = useContext(DataStore)
const url = 'https://poloniex.com/public?command=returnTicker'
console.log('store', store)
useEffect(() => {
store.fetchData(url)
}, [])
//*Call custom hook and data distruction
//const { data, error, loading } = useFetch(url)
//*Change percent amount color depends on the amount
const percentColorHandler = number => {
return number >= 0 ? true : false
}
return (
<View style={container}>
{Platform.OS === 'ios' && <ProjectStatusBar />}
<IconsHeader
dataError={store.error}
header="Charts"
leftIconName="ios-arrow-back"
leftIconPress={() => navigation.navigate('Welcome')}
/>
<ChartsHeader />
<ActivityIndicator animating={store.loading} color="#068485" style={{ top: HP('30%') }} size="small" />
<FlatList
data={store.data}
keyExtractor={item => item.key}
renderItem={({ item }) => (
<CryptoItem
name={item.key}
highBid={item.highestBid}
lastBid={item.last}
percent={item.percentChange}
percentColor={percentColorHandler(item.percentChange)}
/>
)}
/>
</View>
)
}
export { ChartsScreen }

For everybody who is still looking for the approach for the fetch function in React with MobX. I checked a lot of information, but could not find a good decision. But in the end I created mine. Maybe it will help for somebody:
MobX store:
import { action, observable, runInAction } from 'mobx'
class DataStore {
#observable data = null
#observable error = false
#observable fetchInterval = null
#observable loading = false
//*Make request to API
#action.bound
fetchInitData() {
const response = fetch('https://poloniex.com/public?command=returnTicker')
return response
}
//*Parse data from API
#action.bound
jsonData(data) {
const res = data.json()
return res
}
//*Get objects key and push it to every object
#action.bound
mapObjects(obj) {
const res = Object.keys(obj).map(key => {
let newData = obj[key]
newData.key = key
return newData
})
return res
}
//*Main bound function that wrap all fetch flow function
#action.bound
async fetchData() {
try {
runInAction(() => {
this.error = false
this.loading = true
})
const response = await this.fetchInitData()
const json = await this.jsonData(response)
const map = await this.mapObjects(json)
const run = await runInAction(() => {
this.loading = false
this.data = map
})
} catch (err) {
console.log(err)
runInAction(() => {
this.loading = false
this.error = err
})
}
}
//*Call reset of MobX state
#action.bound
resetState() {
runInAction(() => {
this.data = null
this.fetchInterval = null
this.error = false
this.loading = true
})
}
//*Call main fetch function with repeat every 5 seconds
//*when the component is mounting
#action.bound
initInterval() {
if (!this.fetchInterval) {
this.fetchData()
this.fetchInterval = setInterval(() => this.fetchData(), 5000)
}
}
//*Call reset time interval & state
//*when the component is unmounting
#action.bound
resetInterval() {
if (this.fetchInterval) {
clearTimeout(this.fetchInterval)
this.resetState()
}
}
}
const store = new DataStore()
export default store

Related

How to make a refetch wait for a POST in React-Query

There are two requests, a POST and a GET. The POST request should create data and after it has created that data, the GET request should fetch the newly created data and show it somewhere.
This are the hooks imported into the component:
const { mutate: postTrigger } = usePostTrigger();
const { refetch } = useGetTriggers();
And they are used inside an onSubmit method:
const onAddSubmit = async (data) => {
await postTrigger(data);
toggle(); // this one and the one bellow aren't important for this issue
reset(emptyInput); //
refetch();
};
Tried to add async / await in order to make it wait until the POST is finished but it doesn't.
Any suggestions?
I added here the code of those 2 hooks if it's useful:
POST hook:
import { useMutation } from 'react-query';
import { ICalculationEngine } from '../constants/types';
import calculationEngineAPI from '../services/calculation-engine-api';
export const usePostTrigger = () => {
const apiService = calculationEngineAPI<ICalculationEngine['TriggerDailyOpt1']>();
const mutation = useMutation((formData: ICalculationEngine['TriggerDailyOpt1']) =>
apiService.post('/trigger/DailyOpt1', formData)
);
return {
...mutation
};
};
export default usePostTrigger;
GET hook:
import { useMemo } from 'react';
import { useInfiniteQuery } from 'react-query';
import { ICalculationEngine } from '../constants/types';
import { calculationEngineAPI } from '../services/calculation-engine-api';
export const TAG_PAGE_SIZE = 20;
export interface PaginatedData<D> {
totalPages: number;
totalElements: number;
content: D[];
}
export const useGetTriggers = () => {
const query = 'getTriggers';
const apiService = calculationEngineAPI<PaginatedData<ICalculationEngine['Trigger']>>();
const fetchTriggers = (pageNumber: number) => {
const search = {
pageNumber: pageNumber.toString(),
pageSize: TAG_PAGE_SIZE.toString()
};
return apiService.get(`/trigger/paged/0/${search.pageSize}`);
};
const {
data: response,
isError,
isLoading,
isSuccess,
isFetching,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
refetch,
...rest
} = useInfiniteQuery(query, ({ pageParam = 1 }) => fetchTriggers(pageParam), {
getNextPageParam: (lastPage, pages) => {
const totalPages = lastPage.data.totalPages || 1;
return totalPages === pages.length ? undefined : pages.length + 1;
}
});
const data = useMemo(
() => response?.pages.map((page) => page.data.content).flat() || [],
[response?.pages]
);
return {
data,
isError,
isLoading,
isSuccess,
isFetching,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
refetch,
...rest
};
};
export default useGetTriggers;
You can use the onSuccess method of react-query (https://react-query.tanstack.com/reference/useMutation)
onSuccess: (data: TData, variables: TVariables, context?: TContext) => Promise | void
Optional
This function will fire when the mutation is successful and will be passed the mutation's result.
If a promise is returned, it will be awaited and resolved before proceeding
const { mutate, isLoading, error, isSuccess } = useMutation(
(formData: ICalculationEngine['TriggerDailyOpt1']) =>
apiService.post('/trigger/DailyOpt1', formData),
{
mutationKey: 'DailyOpt1',
onSuccess: (_, { variables }) => {
// Execute your query as you see fit.
apiService.get(...);
},
}
);
As a best practice thought I would suggest the POST request to return the updated data if possible to avoid this exact need.

React component not re-rendering on state change due to Memoize

I'm using memoize-one on a React component that is basically a table with a rows that can be filtered.
Memoize works great for the filtering but when I want to insert a new row, it won't show up on the table until I either reload the page or use the filter.
If I check the state, the new row's data is in it, so presumably what is happening is that memoize is not allowing the component to re-render even if the state has changed.
Something interesting is that the Delete function works, I am able to delete a row by removing its data from the state and it will re-render to reflect the changes...
Here's the part of the code I consider relevant but if you would like to see more, let me know:
import React, { Component } from "react";
import memoize from "memoize-one";
import moment from "moment";
import {
Alert,
Card,
Accordion,
Button,
Table,
Spinner,
} from "react-bootstrap";
import PropTypes from "prop-types";
import { getRoleMembersDetailed } from "../libs/permissions-manager-client-v1.0";
import RoleMember from "./RoleMember";
import CreateMemberModal from "./CreateMemberModal";
class RoleContainer extends Component {
filter = memoize((roleMembers, searchValue, searchCriterion) => {
const searchBy = searchCriterion || "alias";
return roleMembers.filter((item) => {
if (item[searchBy]) {
if (searchValue === "") {
return true;
}
const value = searchValue.toLowerCase();
if (searchBy !== "timestamp") {
const target = item[searchBy].toLowerCase();
return target.includes(value);
}
// Case for timestamp
const target = moment(Number(item[searchBy]))
.format("MMM DD, YYYY")
.toLowerCase();
return target.includes(value);
}
return false;
});
});
constructor(props) {
super(props);
this.state = {
collapsed: true,
roleAttributes: [],
roleMembers: [],
isLoading: true,
};
}
componentDidMount = async () => {
const roleMembers = Object.values(await this.fetchRoleMembers());
roleMembers.forEach((e) => {
e.alias = e.alias.toLowerCase();
return null;
});
roleMembers.sort((a, b) => {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
});
// TODO - This logic should be replaced with an API call that describes the roleAttributes.
let roleAttributes = Object.values(roleMembers);
roleAttributes = Object.keys(roleAttributes[0]);
this.setState({
roleMembers,
roleAttributes,
isLoading: false,
});
};
fetchRoleMembers = async () => {
const { roleAttributeName } = this.props;
return getRoleMembersDetailed(roleAttributeName);
};
createRoleMember = (newRoleMembers) => {
const { roleMembers } = this.state;
newRoleMembers.forEach((e) => {
roleMembers.push(e);
});
this.setState(
() => {
roleMembers.sort((a, b) => {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
});
return { roleMembers };
},
() => {
console.log("sss", this.state);
}
);
};
deleteRoleMember = (alias) => {
this.setState((prevState) => {
const { roleMembers } = prevState;
return {
roleMembers: roleMembers.filter((member) => member.alias !== alias),
};
});
};
render() {
const {
role,
roleAttributeName,
searchValue,
searchCriterion,
userCanEdit,
} = this.props;
const { collapsed, isLoading, roleAttributes, roleMembers } =
this.state;
const filteredRoleMembers = this.filter(
roleMembers,
searchValue,
searchCriterion
);
return (
// continues...
I don't know if it's obvious but there are two functions called filter: this.filter that belongs to memoize and Array.prototype.filter().
I did look around and found these post that says Memoize can be overridden:
If you’ve ran into a UI bug, it is simple to just return false from myComparison to temporarily override the memoization, forcing a refresh on every re-render and returning to the default component behaviour.
But I'm not sure what they mean with "return false from component"
Here's a refactoring of your code to idiomatic React Hooks style (naturally dry-coded).
Note how filtering and sorting the role members is done using useMemo() in a way that doesn't modify state; that's because they can be always recomputed from the stateful data. So long as the useMemo()s' deps array is kept in sync (there're ESLint rules to help with this), this should work with no extra re-renders. :)
Similarly, if you use useCallback (which is a special case of useMemo), you need to keep their deps arrays in sync. If you don't use useCallback, those callbacks may cause re-renders since their identity changes per-render.
import React, { Component } from "react";
import moment from "moment";
import { getRoleMembersDetailed } from "../libs/permissions-manager-client-v1.0";
function filterRoleMembers(
roleMembers,
searchValue,
searchCriterion,
) {
const searchBy = searchCriterion || "alias";
return roleMembers.filter((item) => {
if (item[searchBy]) {
if (searchValue === "") {
return true;
}
const value = searchValue.toLowerCase();
if (searchBy !== "timestamp") {
const target = item[searchBy].toLowerCase();
return target.includes(value);
}
// Case for timestamp
const target = moment(Number(item[searchBy]))
.format("MMM DD, YYYY")
.toLowerCase();
return target.includes(value);
}
return false;
});
}
// TODO: maybe use lodash's `sortBy`?
function compareByAlias(a, b) {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
}
async function fetchRoleMembers(roleAttributeName) {
return getRoleMembersDetailed(roleAttributeName);
}
async function loadData(roleAttributeName) {
const roleMembers = Object.values(
await fetchRoleMembers(roleAttributeName),
);
roleMembers.forEach((e) => {
e.alias = e.alias.toLowerCase();
});
// TODO - This logic should be replaced with an API call that describes the roleAttributes.
let roleAttributes = Object.values(roleMembers);
roleAttributes = Object.keys(roleAttributes[0]);
return {
roleMembers,
roleAttributes,
};
}
const RoleContainer = ({
role,
roleAttributeName,
searchValue,
searchCriterion,
userCanEdit,
}) => {
const [collapsed, setCollapsed] = React.useState(true);
const [isLoading, setIsLoading] = React.useState(true);
const [roleAttributes, setRoleAttributes] = React.useState([]);
const [roleMembers, setRoleMembers] = React.useState([]);
React.useEffect(() => {
loadData(roleAttributeName).then(
({ roleMembers, roleAttributes }) => {
setRoleAttributes(roleAttributes);
setRoleMembers(roleMembers);
setIsLoading(false);
},
);
}, [roleAttributeName]);
const createRoleMember = React.useCallback(
(newRoleMembers) => {
const updatedRoleMembers = roleMembers.concat(newRoleMembers);
setRoleMembers(updatedRoleMembers);
},
[roleMembers],
);
const deleteRoleMember = React.useCallback(
(alias) => {
const updatedRoleMembers = roleMembers.filter(
(member) => member.alias !== alias,
);
setRoleMembers(updatedRoleMembers);
},
[roleMembers],
);
const filteredRoleMembers = React.useMemo(
() =>
filterRoleMembers(roleMembers, searchValue, searchCriterion),
[roleMembers, searchValue, searchCriterion],
);
const sortedRoleMembers = React.useMemo(
() => [].concat(filteredRoleMembers).sort(compareByAlias),
[filteredRoleMembers],
);
return <>{JSON.stringify(sortedRoleMembers)}</>;
};

How to change the internal state property of a component in an action creator function

I am working on a React application and I am using Redux to store the state. I have the following code:
category-arrows.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increaseCategoryRank, decreaseCategoryRank, fetchCategoryRanks } from '../../redux/menu/menu.actions';
import './category-arrows.styles.scss';
class CategoryArrows extends Component {
state = {
isSending: false
}
render() {
const { category } = this.props;
const categoryClicked = true;
return (
<div className="arrows-container">
<div className="up-arrow" onClick={
() => {
if(this.state.isSending === false) {
this.props.increaseCategoryRank(category, categoryClicked)
}
this.props.fetchCategoryRanks(this.props.menu);
}}></div>
<div className="category-rank">
<p>{category.rank}</p>
</div>
<div className="down-arrow" onClick={
() => {
if(this.state.isSending === false) {
this.props.decreaseCategoryRank(category, categoryClicked)
}
this.props.fetchCategoryRanks(this.props.menu);
}}></div>
</div>
)
}
}
const mapStateToProps = state => ({
menu: state.menu
})
export default connect(mapStateToProps, { increaseCategoryRank, decreaseCategoryRank, fetchCategoryRanks } )(CategoryArrows);
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK, FETCH_CATEGORY_RANKS } from './menu.types';
export const decreaseCategoryRank = (category, categoryClicked) => dispatch => {
dispatch({ type: DECREASE_CATEGORY_RANK, category, categoryClicked })
}
export const increaseCategoryRank = (category, categoryClicked) => dispatch => {
dispatch({ type: INCREASE_CATEGORY_RANK, category, categoryClicked })
}
export const fetchCategoryRanks = menu => async dispatch => {
console.log("Printing menu (fetch category ranks)");
console.log(menu);
var sentRequests = 0;
menu.map(async (category) => {
const menuLength = menu.length;
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
const response = await fetch(`${apiUrl}/category/${category._id}`, options)
let data = await response.json()
if (response.ok) {
console.log("It got sent")
sentRequests++;
console.log("Printing sentRequests");
console.log(sentRequests);
if(sentRequests === menuLength) {
console.log("All the requests have been sent");
}
} else {
alert(data.error)
}
});
dispatch({ type: FETCH_CATEGORY_RANKS, menu });
}
menu.types.js:
export const INCREASE_CATEGORY_RANK = "INCREASE_CATEGORY_RANK";
export const DECREASE_CATEGORY_RANK = "DECREASE_CATEGORY_RANK";
export const FETCH_CATEGORY_RANKS = "FETCH_CATEGORY_RANKS";
menu.reducer.js:
// import INITIAL_STATE from './menu.data';
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK, FETCH_CATEGORY_RANKS } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK: {
console.log("Went into increase category rank");
if(action.categoryClicked === false) {
return state;
}
const menuArray = [...state];
var index = menuArray.map(category => category._id).indexOf(action.category._id);
//if it's the first element in array it won't move up
if(index === 0) {
return state;
} else {
const temp = menuArray[index];
menuArray[index] = menuArray[index - 1];
menuArray[index - 1] = temp;
var newrank = 0;
menuArray.forEach(category => {
category.rank = newrank++;
});
return menuArray;
}
}
case DECREASE_CATEGORY_RANK: {
console.log("Went into decrease category rank");
if(action.categoryClicked === false) {
return state;
}
const menuArray = [...state];
console.log(menuArray);
var index = menuArray.map(category => category._id).indexOf(action.category._id);
//if it's the last element in the array, it won't move down
if(index === menuArray.length - 1) {
return state;
} else {
const temp = menuArray[index];
menuArray[index] = menuArray[index + 1];
menuArray[index + 1] = temp;
var newrank = 0;
menuArray.forEach(category => {
category.rank = newrank++;
});
return menuArray;
}
}
case FETCH_CATEGORY_RANKS:
return state;
default:
return state;
}
}
In my CategoryArrows component I have a state property called isSending which is set to false. In my fetchCategoryRanks action creator, I am sending information about categories from the menu array in the state to a server using fetch.
I would like to be able to set the isSending property from the CategoryArrows component to true or false, depending on certain conditions in the function fetchCategoryRanks.
However, I am not sure what the best way to do this is. Any insights are appreciated.
First way
You can change internal state by sending a callback function to your axios api call. Before the axios request starts you can call that callback function from axios api function to set isSending=true and after request completed again call callback function to set isSending=false. Callback function implementation must be in component from where you are calling axios api.
Api call
this.props.fetchCategoryRanks(this.props.menu, (response) => {
if (isRequestStart) {
this.setState({
isSending: true
});
}
if (!isRequestStart) {
this.setState({
isSending: false
});
}
});
Below is your fetch request
export const fetchCategoryRanks = (menu, callback) => async dispatch => {
var sentRequests = 0;
menu.map(async (category) => {
const menuLength = menu.length;
callback({
isRequestStart: true
});
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
const response = await
fetch(`${apiUrl}/category/${category._id}`, options)
let data = await response.json()
if (response.ok) {
callback({
isRequestStart: false
});
console.log("It got sent")
sentRequests++;
console.log("Printing sentRequests");
console.log(sentRequests);
if (sentRequests === menuLength) {
console.log("All the requests have been sent");
}
} else {
alert(data.error);
callback({
isRequestStart: false
});
}
});
dispatch({
type: FETCH_CATEGORY_RANKS,
menu
});
}
Second way
You can use a reducer where you can set initial state of isSending by dispatching a function from the axios api, calling the dispatch function same as above. And you can use that reducer state into your component.

React setState callback won't update state

So I have 3 functions below. One containing calls to the two (getBooks), which are getting requests. I set my state (isLoading) to true before the calls and then to true after the calls. This is to also make sure that the data is properly loaded. However the state is not updating so, therefore, my data from the get request is invalid. The callbacks in my setstate work in my other components, so I am confused. Below are my 3 functions.
import React from 'react';
import ReactDOM from 'react-dom';
import SidePane from './SidePane.js';
import HomeNavBar from './HomeNavBar.js';
import axios from 'axios';
import qs from 'qs';
import Loading from './Loading.js';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
bookSearch: "",
bookSearchResults: [],
bookSearchFound: false,
isLoading: false
};
this.handleSearch = this.handleSearch.bind(this);
this.alertBookName = this.alertBookName.bind(this);
this.getBooksFromIsbn = this.getBooksFromIsbn.bind(this);
this.getBooks = this.getBooks.bind(this);
axios.defaults.withCredentials = true;
}
changeBookName = (e) => {
var bookName = e.target.value;
bookName = bookName.split(' ').join('+');
this.setState({bookSearch: bookName})
}
getBooksFromIsbn(isbns){
var books = [];
axios
.get('http://localhost:9000/api/getBooksFromIsbn',
{
params: {
books: JSON.stringify(isbns)
}
})
.then(res =>
{
console.log(res.data);
books = res.data;
})
.catch(error => {
console.log(error.response);
});
}
getBooks(){
this.setState({
isLoading: true
},
function(){console.log("setState completed", this.state)}
);
var bookResults = this.handleSearch();
var books = this.getBooksFromIsbn(bookResults);
this.setState({
isLoading: false
},
function(){console.log("setState completed", this.state)}
);
this.props.setBookSearchResults(books);
}
handleSearch(){
var bookResults = [];
var url = 'http://localhost:9000/api/getOpenLibrarySearch';
axios
.get(url,
{
params: {
bookSearch: this.state.bookSearch
}
})
.then(res =>
{
//this.setState({bookSearchResults: res.data});
for(var i=0; i < res.data.docs[i].isbn.length; i++){
bookResults = bookResults.concat(res.data.docs[i].isbn);
}
console.log(bookResults);
})
.catch(error => {
console.log(error.response);
});
return bookResults;
}
render(){
if(this.state.isLoading == false){
return(
<div>
<HomeNavBar authToken = {this.props.authToken} email = {this.props.email} />
<SidePane changeBookName = {this.changeBookName} handleSearch = {this.getBooks} />
</div>
)
}
else
{
return <Loading />;
}
}
}
It looks like you need to actually return the books value from getBooksFromIsbn. Await the axios call resolution and return books.
async getBooksFromIsbn (isbns) {
const books = [];
try {
const res = await axios.get(
'http://localhost:9000/api/getBooksFromIsbn',
{
params: {
books: JSON.stringify(isbns)
}
});
books = res.data;
} catch(error) {
console.log(error.response);
}
return books;
}
Same for handleSearch, the code needs to wait and return the resolution of the GET request.

How can I get step by step data from api in redux/react by infinite scroll

I want to get 20 posts by scroll down each time how can i do? my project have big Data and I use redux for get data, can I get data step by step? for example get 20 posts for first time and when a user scroll down load the next 20 posts.
I use React Hooks for develop
my posts component source is:
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import { getPosts } from '../../actions/post';
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
return loading ? <Spinner /> : (
{posts.map(post => (
<PostItem key={post._id} post={post} />
))}
)
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPosts })(Posts)
my action code is:
import { setAlert } from './alert';
import {
GET_POSTS,
POST_ERROR
} from "../actions/types";
// Get Posts
export const getPosts = () => async dispatch => {
try {
const res = await axios.get('/api/ads');
dispatch({
type: GET_POSTS,
payload: res.data
});
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.satusText, status: err.response.satus }
});
}
}```
///////////////////////////////
///////////////AND REDUCER IS :
import {
GET_POSTS,
POST_ERROR
} from '../actions/types';
const initialState = {
posts: [],
loading: true,
error: {}
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_POSTS:
return {
...state,
posts: payload,
loading: false
}
case POST_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
You can use react-infinite-scroller library. I've tried to change your Posts method, so maybe it would be useful.but as mentioned in comments you should add pagination to your API.
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
const itemsPerPage = 20;
const [hasMoreItems, sethasMoreItems] = useState(true);
const [records, setrecords] = useState(itemsPerPage);
const showItems=(posts)=> {
var items = [];
for (var i = 0; i < records; i++) {
items.push( <PostItem key={posts[i]._id} post={posts[i]} />);
}
return items;
}
const loadMore=()=> {
if (records === posts.length) {
sethasMoreItems(false);
} else {
setTimeout(() => {
setrecords(records + itemsPerPage);
}, 2000);
}
}
return <InfiniteScroll
loadMore={loadMore}
hasMore={hasMoreItems}
loader={<div className="loader"> Loading... </div>}
useWindow={false}
>
{showItems()}
</InfiniteScroll>{" "}
}
Working Codesandbox sample with fake data.

Categories

Resources