keep old content loaded previously in react pagination - reactjs

I'm new using react.
I have an array from php.
React is getting it using axios to print some posts.
I'd like to show 10 posts and when user scroll down I get more 10 posts.
It is working ok. The problem is react is overwriting my old posts from the screen, I'd to keep all posts, not only the new ones loaded... Any ideas why react is only keeping the new ones?
my return:
return (
<>
{posts.map((post, index) => {
if (posts.length === index + 1) {
number = post.id;
return <div className={classes.post} ref={lastPostElementRef} key={post.id}>{post.titulo}</div>
}
else {
return <div className={classes.post} key={post.id}>{post.titulo}</div>
}
})}
<div>{loading && 'Loading...'}</div>
<div>{error && 'Error'}</div>
</>
)
my axios:
axios({
method: 'GET',
url: 'http://localhost/posts.php',
params: { q: query, page: pageNumber },
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(res => {
setPosts(prevPosts => {
return res.data
})
if(res.data.length < 10) {
setHasMore(false); // total pagination items 10
}
else {
setHasMore(true);
}
thanks.

When you set the state in functional component, React will replace the previous state.
When you update a state variable, unlike this.setState in a component class, the function returned by useState does not automatically merge update objects, it replaces them.
Solution : You have to add the old state and the new state in a new array and then set this as the new state. You could use spread syntax to copy the old and the new state.
Example:
axios({
method: 'GET',
url: 'http://localhost/posts.php',
params: { q: query, page: pageNumber },
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(res => {
setPosts(prevPosts => {
return [...prevPosts, ...res.data, ]
})
if (res.data.length < 10) {
setHasMore(false); // total pagination items 10
}
else {
setHasMore(true);
}

you need to add newly fetched results to current state;
Use this;
return [...prevPosts ,...res.data]
instead of
return res.data

Related

RTK query use infinity scroll and pagination at the same time

Describtion:
I am trying to use infinity scroll and pagination at the same time.
I have a component which loads the user orders with infinity scroll and another component in different page which loads user order by pagination.
after i read the documentation I noticed we could use serializeQueryArgs, merge, forceRefetch to create these effects so I did it in my api slice.
Problem:
I have nice infinity scroll but my pagination works just like my infinity scroll
every time I click on a page the new data merged with my prev page.
so I decided to use extera prams for my query just only for serializeQueryArgs mehtod and check if I call this api from the component with infinity scroll or pagination and it works better and now i have another problem for example when I'm in the page 1 and go to page 2 everything seems normal but when i back to page 1 i have duplicated the page one array.
here is my api Slice:
export const orderApi = ApiSlice.injectEndpoints({
tagTypes: ['Orders'],
endpoints: (builder) => ({
getOrdersCount: builder.query({
query: ({ page, size, other }) => ({
url: `/order/orders/count?${page ? `page=${page}` : ''}${
size ? `&size=${size}` : ''
}${other || ''}`,
method: 'GET'
}),
transformResponse: (res) => res // just only one number
}),
getOrderList: builder.query({
providesTags: (result) => {
return result.map((item) => ({ type: 'Orders', id: item.id }));
},
async queryFn({ page, size, other }) {
const portfolioList = await axios.get(
`/order/orders?${page ? `page=${page}` : ''}${size ? `&size=${size}` : ''}${
other || ''
}`
);
if (portfolioList.error) return { error: portfolioList.error };
const endpoints = portfolioList.data.map((item) =>
axios.get(`/market/instruments/${item.insRefId}`)
);
let error = null;
let data = null;
try {
data = await Promise.all(endpoints).then((res) => {
return res.map((item, index) => {
return { ...item.data, ...portfolioList.data[index] };
});
});
} catch (err) {
error = err;
}
return data ? { data } : { error };
},
// Only have one cache entry because the arg always maps to one string
serializeQueryArgs: ({ endpointName, queryArgs }) => {
const { infinity, page, size } = queryArgs;
if (infinity) return endpointName;
return endpointName + page + size;
},
// Always merge incoming data to the cache entry
merge: (currentCache, newItems) => {
currentCache.push(...newItems);
},
// Refetch when the page arg changes
forceRefetch({ currentArg, previousArg }) {
console.log('currentArg', currentArg);
return currentArg !== previousArg;
},
})
})
});
any solution or best practice ?

What is the correct way to call updateCachedData on a click event in a component that uses the RTKQ query?

I can only think of storing a reference to updateCachedData somewhere globally and use it in that click event but I am not sure this is the React way of doing this.
I have a notifications feed built with a Socket.IO server.
By clicking on a notification it should get deleted from the list. (The list should show only unread notifications.)
But when deleting from the list I create a new array as state in the notifications pane.
When I receive a new notification, all the deleted notifications return back - this is not what I intended.
How can I change the cache entry, more precisely remove items from it without remaking the request for all the notifications?
There are no error messages.
Code
getNotifications: build.query<
IDbNotification[],
IGetNotificationsQueryParams
>({
query: (params: IGetNotificationsQueryParams) => ({
url: `notifications?authToken=${params.authToken || ""}&limit=${
params.limit
}&userId=${params.userId || ""}${
params.justUnread ? "&justUnread" : ""
}`,
method: "GET"
}),
keepUnusedDataFor: 0,
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved }
) {
const { myIo, connectHandler } = getWebSocketConnection(
"notifications",
clone({
subscribtions: arg.userId
? getFollowedUserIds().concat({
uid: arg.userId,
justUnread: arg.justUnread
})
: getFollowedUserIds()
})
);
const listener = (eventData: IDbNotification) => {
if (
(eventData as any).subscriber === arg.userId &&
(!arg.justUnread || typeof eventData.readDateTime === "undefined")
) {
updateCachedData(draft => {
draft.unshift(eventData);
if (draft.length > arg.limit) {
draft.pop();
}
});
}
};
try {
await cacheDataLoaded;
myIo.on("notifications", listener);
} catch {}
await cacheEntryRemoved;
myIo.off("notifications", listener);
myIo.off("connect", connectHandler);
}
})
You can use updateQueryData - updateCachedData is just a shortcut for the current cache entry for convenience.
dispatch(
api.util.updateQueryData('getNotifications', arg, (draft) => {
// change it here
})
)
See this for more context: https://redux-toolkit.js.org/rtk-query/usage/optimistic-updates

Can't convert null or undefined with React Hooks

I have a state that I set with Hooks in react, like this :
useEffect(() => {
if (uid !== null) {
axios.get(`${process.env.REACT_APP_API_URL}api/balance/${uid}`)
.then((res) => {
setUserWalletIncomes(res.data[0].incomes)
})
}
}, [uid])
This state, to be clear, give me this :
Everything works fine I can delete my incomes etc.
Problem is, when I delete the last income (which makes the state empty), I have this error and a page blank :
TypeError: Cannot convert undefined or null to object
Here is my request to delete :
const handleDeleteIncome = (key) => {
let data = { [key]: "" }
axios({
method: "delete",
url: `${process.env.REACT_APP_API_URL}api/balanceOneIncome/${uid}`,
data: data
}).then((res) => {
if {(userWalletIncomes === null) setUserWalletIncomes({})}
setUserWalletIncomes(res.data[0].incomes)
setformIncomes(false)
})
}
I tried to add a condition : if {(userWalletIncomes === null) setUserWalletIncomes({})}, but I still have the same problem.
Can someone help me with this ?
[EDIT]
My state is initialized like this :
const [userWalletIncomes, setUserWalletIncomes] = useState({})
PS : If I reload the page, everything is fine.
Is this the problem? (I am assuming you don't really have the weird {} brace positioning as in your post?)
You have this:
if (userWalletIncomes === null) { setUserWalletIncomes({}) }
setUserWalletIncomes(res.data[0].incomes)
But after you call setUserWalletIncomes({}) you fall through and immediately call it again with the null value in the next line. Maybe you just need to explicitly return after setting it to the empty object? Or, to simplify, you could remove that conditional and do this instead:
setUserWalletIncomes(res.data[0].incomes || {})
Answer, if it helps someone, is this :
const handleDeleteIncome = (key) => {
let data = { [key]: "" }
axios({
method: "delete",
url: `${process.env.REACT_APP_API_URL}api/balanceOneIncome/${uid}`,
data: data
}).then((res) => {
if (res.data[0].incomes) {
setUserWalletIncomes(res.data[0].incomes)
} else {
setUserWalletIncomes({})
}
})
}
Just a condition that tells if response is null, then set the state to an empty object.

Display data from API

i have the following code in parent.js file which gets data from API using axios which is working fine
//Parent.js
componentDidMount() {
axios.get('URL', {
method: 'GET',
headers: {
'key': 'apikeygoeshere'
}
})
.then((response) => {
this.success(response);
})
}
successShow(response) {
this.setState({
person: response.data.data.Table
});
}
render() {
return (
<div class="row">
{this.state.person.map(results => (
<h5>{results.first_name}</h5>
)
)
}
and the above code display data from api perfect. i want to display api data in child component instead of displaying data from json file. In child component i have the following code which display data from local.json file
//
The issue is in the successShow function the you cannot change the array state value like that. According to the answer from here :
Correct modification of state arrays in ReactJS
You can update the state like this:
this.setState(prevState => ({
person: [...prevState.person,response.data.data.Table]
});
or
this.setState({
person: this.state.person.concat([response.data.data.Table])
});
Try using
const data = this.state.person && this.state.person.find(item => item.id === id);
const relatedTo = this.state.person && this.state.person.filter( item => item.manager_id === data.manager_id && item.id !== data.id );

How do I replace one object by another in an array of objets with VueJS?

I'm building a website with VueJS and AdonisJS, and I'm setting up a possibility to publish or unpublish an article just by setting the draft attribute to true or false.
This attribute edition is possible from the index, where all the articles can be seen. I made a table where there's an action button that would toggle the attribute, which would send an HTTP POST request to Adonis with the ID.
My concern is for what comes after: I don't believe that replacing the whole array is the good option, but what should I do instead? I tried the following:
handlePublish (id) {
this.$axios.post('/post/publish', {id: id}, {
headers: {
'Authorization': `Bearer [some token]`
}})
.then(response => {
console.log(response)
let toDelete = ''
this.posts.map((post, index) => {
if (post.id === id) {
toDelete = index
}
})
this.posts.splice(toDelete, 1)
this.posts.push(response.data)
})
.catch(e => console.log(e.response))
}
But somehow nothing gets update, excepted in the database.
Thank you in advance
You are using map to iterate over array. Instead you can use map to get updated array by returning the new element in place of old,
.then(response => {
this.posts = this.posts.map((post, index) => {
if (post.id === id) {
return response.data
}
})
}

Resources