How to display list of mapped array onClick - React - javascript

I have a component in which I am trying to add pagination functionality. I have made good progress in the structure so far. I have category buttons, where when a category button is selected, the element below shows a list of posts within that category.
For the pagination functionality, I using the WordPress REST API to pull in 5 posts at a time for each category. I am trying to create a "load more" button after the 5th post, where onClick it will load the next 5 posts:
const Posts = ({ state, actions }) => {
const [categories, setCategories] = useState([]);
const [categoryId, setCategoryId] = useState();
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(state.source.api + "/wp/v2/categories")
.then(response => response.json())
.then(data => {
setCategories(data);
})
}, []);
useEffect(() => {
if (categoryId) {
fetch(state.source.api + "/wp/v2/posts?categories=" + categoryId + "&per_page=5")
.then((response) => response.json())
.then((data) => {
setPosts(data);
});
}
}, [categoryId]);
const [morePosts, setMorePosts] = useState([]);
useEffect(() => {
if (categoryId) {
fetch(state.source.api + "/wp/v2/posts?categories=" + categoryId + "&per_page=5&page=" + 2)
.then((response) => response.json())
.then((data) => {
setMorePosts(data);
});
}
}, [categoryId]);
return (
<>
{categories.length > 0 ? (
categories.map((category, i) => {
return (
<button key={i} onClick={() => setCategoryId(category.id)}>{category.name}</button>
)
})
) : (
<p>Loading...</p>
)
}
<div>
{posts.length === 0 ? (
<p>No posts...</p>
) : (
<>
<ol>
{posts.map((post, i) => {
return (
<li key={i}>{post.title.rendered}</li>
)
})}
</ol>
<button onClick={() => setMorePosts(category.id)}>Load More</button>
{console.log(morePosts.map((post) => post.title.rendered))}
</>
)}
</div>
</>
)
}
As you can see, under the last button I am consoling the array, and it does return the next 5 posts for the selected category. I am stuck on how to turn that array from the console into actually being shown on the page.

I would do it this way:
Load more button changes the page number.
When page number is changed: useEffect fires and posts are refetched/rerendered based on page number param.
const [page, setPage] = useState(1);
const [posts, setPosts] = useState([]);
useEffect(() => {
if (!categoryId) {
return;
}
let url = `${state.source.api}/wp/v2/postscategories=${categoryId}&per_page=5"`;
if (page > 1) {
url += `&page=${page}`;
}
fetch(url)
.then((response) => response.json())
.then((data) => {
setPosts([...posts, ...data]);
});
}, [categoryId, page]);
<button onClick={() => { setPage(page + 1); }}>Load more</button>

Related

How can I truncate text of a specific element inside .map function in React?

const { useEffect, useState } = React;
function App() {
const [data, setData] = useState([]);
const [show, setShow] = useState(false)
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((res) => setData(res))
.catch((err) => console.log("error", err.message))
}, [])
let showNames = (users, index) => {
return (
<h3 key={index} onMouseOver={() => setShow(true)} onMouseLeave={() => setShow(false)}>
{show ? users.name : `${users.name.substring(0, 5)}...`}
</h3>
)
}
return (
<div className="App">
<header className="App-header">
{
data && data.map((users, index) => {
return (
showNames(users, index)
)
})
}
</header>
</div>
);
}
ReactDOM.createRoot(document.body).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
I'm currently learning ReactJS, and I am facing an issue while trying to hide/show truncated text. The following code is working, but when I hover over the text it truncates every name from the list. I would instead like it to show/hide the name only where I hover.
Your problem is that you only have one show state that controls the visibility of all your text items. There are a few options to fix this, one way would be to .map() the data you get from your API to include a show property, which you can update for particular objects within data when you hover over the associated item. This option is good if you need multiple options expanded at the same time, however, with your current set up you can only have one item expanded (by the nature of your mouse enter/leave to toggle the expanded state). The other option to deal with this is to store the id of the currently expanded item, and then within your .map() only expand the item if the item id matches the stored id within your state.
See working example below:
const { useEffect, useState } = React;
function App() {
const [data, setData] = useState([]);
const [showId, setShowId] = useState(-1); // invalid id that matches no users
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((res) => setData(res))
.catch((err) => console.log("error", err.message))
}, [])
let showNames = user => {
return (
<h3 key={user.id} onMouseOver={() => setShowId(user.id)} onMouseLeave={() => setShowId(-1)}>
{showId === user.id ? user.name : `${user.name.substring(0, 5)}...`}
</h3>
)
}
return (
<div className="App">
<header className="App-header">
{
data && data.map(user => showNames(user))
}
</header>
</div>
);
}
ReactDOM.createRoot(document.body).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
I've also updated your component to use the user id as the key, without relying on the index (using the index as the key can cause rendering bugs if your users are removed from the list)
you can simply set show status pre user.name and check if users.name is true or not
here is what I did :
const { useEffect, useState } = React;
function App() {
const [data, setData] = useState([]);
const [show, setShow] = useState({});
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((res) => setData(res))
.catch((err) => console.log("error", err.message));
}, []);
let showNames = (users, index) => {
return (
<h3
key={index}
onMouseOver={() =>
setShow((prevState) => ({ ...prevState, [users.name]: true }))
}
onMouseLeave={() =>
setShow((prevState) => ({ ...prevState, [users.name]: false }))
}
>
{show[users.name] ? users.name : `${users.name.substring(0, 5)}...`}
</h3>
);
};
return (
<div className="App">
<header className="App-header">
{data &&
data.map((users, index) => {
return showNames(users, index);
})}
</header>
</div>
);
}
ReactDOM.createRoot(document.body).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>

How do I prevent re-render while using infinite scroll in React?

I am fetching data in React, I am fetching users, so I have a state (I am using redux) allUsers, in which I am storing fetched users and adding more users with infinite scroll once scrollbar reaches the bottom. The problem is that when new data is being added to the allUsers state, React is re-rendering all of the users because I am using .map to render them. Could you please tell me how to fix that? Here is the code:
const allUsers = useSelector(state => state.setAllUsersReducer);
const page = useSelector(state => state.setPageReducer);
const loading = useSelector(state => state.setLoadingReducer);
const dispatch = useDispatch();
const fetchAllUsers = () => {
console.log(page)
fetch(`${url}/${page}/15`)
.then(res => res.json())
.then(data => {
dispatch(setAllUsers(data.list))
})
.catch(err => console.log('Error message: ', err))
}
console.log(allUsers)
useEffect(() => {
fetchAllUsers();
}, [page])
const handleScroll = () => {
dispatch(setPage());
}
window.onscroll = function () {
if(window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
handleScroll();
}
}
return (
<div className="allUsersList">
{
allUsers.length > 0 ? (
allUsers.map((user, index) => (
<User key={index + 1} name={user.name} lastName={user.lastName} prefix={user.prefix} title={user.title} img={user.imageUrl}/>
))
) : (
<div> Loading... </div>
)
}
</div>
)
}

How to display posts on the current page from the API

I'm getting data from Django Rest API and React for Frontend, and I need to create the pagination with posts. I did it all in pagination component. I created the state with current page and I'm changing it by clicking on the page button in component like this:
const Paginator = () => {
const [count, setCount] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(null);
const [nextPage, setNextPage] = useState(null);
const [previousPage, setPreviousPage] = useState(null);
const [valid, setValid] = useState(false);
useEffect(() => {
fetch(`http://127.0.0.1:8000/api/software/?p=${currentPage}`)
.then(response => response.json())
.then(data => {
setCount(data.count);
setTotalPages(data.total_pages)
setNextPage(data.links.next);
setPreviousPage(data.links.previous);
setValid(true);
})
}, [currentPage]);
...
return (
<>
{
...
<PbStart style={style} totalPages={range(1, totalPages+1)} setCurrentPage={setCurrentPage} />
...
}
</>
);
};
const PagItem = ({key, handleClick, className, title, name }) => {
return (
<li key={key} onClick={handleClick}>
<Link to='/' className={className} title={`Go to page ${title}`}>
{name}
</Link>
</li>
);
};
const PbStart = ({ style, totalPages, setCurrentPage }) => {
return (
...
{totalPages.map(p => (
<PagItem key={p} handleClick={() => setCurrentPage(p)} title={p} name={p} />
))}
...
);
};
And in posts component I don't know how to change current page, or getting it from the paginaton component. I've written that like this:
const Softwares = () => {
const [softwares, setSoftwares] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [valid, setValid] = useState(false);
useEffect(() => {
fetch(`http://127.0.0.1:8000/api/software/?p=${currentPage}`)
.then(response => response.json())
.then(data => {
setSoftwares(data.results);
setValid(true);
})
}, [currentPage]);
return (
<>
{
...
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
...
}
</>
);
};
So, how to do that(get the current page from pagination component or another way)?
I think a Paginator's job is only moving between pages and updating current page state. It should not be fetching data by itself, you can provide functionality to do extra work with props.
I haven't tested this, but this might be a good starting point.
With the example below you'll have a list of articles and then below it next and previous buttons.
In Softwares, as you can see I am passing the same function for handling next and previous pages, you can refactor it to have one function like onPageMove and call this function handleNext and handlePrev.
I added two separate functions if you have want to handle something different in either.
const Paginator = ({
total, // Required: Total records
startPage = 1, // Start from page / initialize current page to
limit = 30, // items per page
onMoveNext = null, // function to call next page,
onMovePrev = null, // function to call previous page
}) => {
const [currentPage, setCurrentPage] = useState(startPage);
const canGoNext = total >= limit;
const canGoPrev = currentPage > 1;
function handleNext(e) {
if (canGoNext) {
setCurrentPage((prevState) => prevState+1);
onMoveNext && onMoveNext({ currentPage });
}
}
function handlePrev(e) {
if (canGoPrev) {
setCurrentPage((prevState) => prevState-1);
onMovePrev && onMovePrev({ currentPage });
}
}
return (
<div>
<button onClick={handlePrev} disabled={!canGoPrev}>Prev</button>
<button onClick={handleNext} disabled={!canGoNext}>Next</button>
</div>
);
};
Here is how you can use Paginator in other components.
const PER_PAGE = 30; // get max # of records per page
const Softwares = () => {
const [softwares, setSoftwares] = useState([]);
const [valid, setValid] = useState(false);
const onFetchData = ({ currentPage }) => {
fetch(`http://127.0.0.1:8000/api/software/?p=${currentPage}&per_page=${PER_PAGE}`)
.then(response => response.json())
.then(data => {
setSoftwares(data.results);
setValid(true);
})
}
useEffect(() => {
onFetchData({ currentPage: 1 })
}, []);
return (
<>
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
<Paginator total={softwares.length} limit={PER_PAGE} onMoveNext={onFetchData} onMovePrev={onFetchData} />
</>
);
};

React component returns list of mapped items, then disappears

I have a React component that renders a list of items that are being mapped over, and shows the id for each item. When the component first loads, the list items appear, but then a second or two later disappears and in the console returns undefined.
The component being rendered is:
const Posts = ({ state }) => {
const [categories, setCategories] = useState([]);
const [categoryId, setCategoryId] = useState();
const [page, setPage] = useState(1);
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(state.source.api + "/wp/v2/categories")
.then(response => response.json())
.then(data => {
setCategories(data);
})
}, []);
useEffect(() => {
if (categoryId) {
fetch(state.source.api + "/wp/v2/posts?categories=" + categoryId + "&per_page=5")
.then((response) => response.json())
.then((data) => {
setPosts(data);
});
}
}, [categoryId]);
useEffect(() => {
if (!categoryId) {
return;
}
let url = state.source.api + "/wp/v2/posts?categories=" + categoryId + "&per_page=5";
if (page > 1) {
url += `&page=${page}`;
}
fetch(url)
.then((response) => response.json())
.then((data) => {
setPosts([...posts, data]);
});
}, [categoryId, page]);
return (
<>
{categories.length > 0 ? (
categories.map((category, i) => {
return (
<button key={i} onClick={() => setCategoryId(category.id)}>{category.name}</button>
)
})
) : (
<p>Loading...</p>
)
}
<div>
{posts.length === 0 ? (
<p>No posts...</p>
) : (
<>
<ol>
{posts.map((post, i) => {
console.log(post.id);
return (
<li key={i}>{post.id}</li>
)
})}
</ol>
<button onClick={() => { setPage(page + 1); }}>Load more</button>
</>
)}
</div>
</>
)
}
And the console shows:
I have a lot of code commented out above this component, so the line in the console is referring to console.log(post.id);.
Does anyone have an idea of what could be causing this?
This useEffect is not needed. Both useEffects on initial load trying to hit same endpoint with same url params.
useEffect(() => {
if (categoryId) {
fetch(state.source.api + "/wp/v2/posts?categories=" + categoryId + "&per_page=5")
.then((response) => response.json())
.then((data) => {
setPosts(data);
});
}
}, [categoryId]);
And I think arrays are not merged correctly here:
try to replace:
setPosts([...posts, data]);
to:
setPosts([...posts, ...data]);

Conditionally show button in React

I have a component that performs a pagination feature with blog posts. Each time the "load more" button is clicked, five more posts are loaded to the bottom of the page. I am drawing a blank right now on how to solve an issue with it though.
The Problem:
Once the "load more" button is clicked and all of the available posts are visible, the "load more" button should go away.
The Code:
const Posts = ({ state }) => {
const [categories, setCategories] = useState([]);
const [categoryId, setCategoryId] = useState();
const [page, setPage] = useState(1);
const [posts, setPosts] = useState([]); // Posts for each category
const [allPosts, setAllPosts] = useState([]); // All posts from all categories
// Get all of the available categories
useEffect(() => {
fetch(state.source.api + "/wp/v2/categories")
.then(response => response.json())
.then(data => {
setCategories(data);
})
}, []);
useEffect(() => {
if (categoryId) {
fetch(state.source.api + "/wp/v2/posts?categories=" + categoryId + "&per_page=5")
.then((response) => response.json())
.then((data) => {
setPosts(data);
});
}
}, [categoryId]);
// Get posts for each category
useEffect(() => {
if (!categoryId) {
return;
}
let url = state.source.api + "/wp/v2/posts?categories=" + categoryId + "&per_page=5";
if (page > 1) {
url += `&page=${page}`;
}
fetch(url)
.then((response) => response.json())
.then((data) => {
setPosts([...posts, ...data]);
});
}, [categoryId, page]);
useEffect(() => {
let url = state.source.api + "/wp/v2/posts?per_page=5";
if (page > 1) {
url += `&page=${page}`;
}
fetch(url)
.then((response) => response.json())
.then((data) => {
setAllPosts([...allPosts, ...data]);
});
}, [page]);
// Add View All button to category buttons array
const allCategories = categories.map((category, i) => (category))
allCategories.push("View All");
return (
<>
{allCategories.map((category, i) => {
return (
(category === "View All") ? (
<button onClick={() => { setPosts([]) }}>View All</button>
) : (
<button className="btn" key={i} onClick={() => {
setPage(1);
setPosts([]);
setCategoryId(category.id);
}}>{category.name}</button>
)
)
})}
<div>
{posts.length === 0 ? (
<>
{allPosts.map((allPost, i) => {
return (
<li key={i}>{allPost.title.rendered}</li>
)
})}
<button onClick={() => { setPage(page + 1); }}>Load more</button>
</>
) : (
<>
<ol>
{posts.map((post, i) => {
return (
<li key={i}>{post.title.rendered}</li>
)
})}
</ol>
<button onClick={() => { setPage(page + 1); }}>Load more</button>
</>
)}
</div>
</>
)
}
Say there are a maximum of 3 pages worth of posts, five being viewed at a time - so 15 posts total... once all 3 pages are visible, and I click "load more" again (even though no more posts are available), I get a console error that ... my api... GET /wp/v2/posts?categories=78&per_page=5&page=4 400 (Bad Request), which obviously means that there was no page 4 to fetch because there are no more posts.
I think this requires an if statement, but I am not sure how I would get started writing it. Does anyone have any input?
You should have a variable called totalPages, which hold the number of pages, and then compare between totalPages and page states, something like that:
{page <= totalPages && <button onClick={() => { setPage(page + 1); }}>Load more</button>}
in this case if page is 3 and totalPages is 4 the button will not display
Try an npm package called "react-paginate", with this you can easily implement the pagination logic without messing up with the code, and here is the link for this package https://www.npmjs.com/package/react-paginate
Just go with the documentation and you can figure out how to do this, but if you face any problem then don't hesitate, just ask me.

Categories