How to get the first elements of an array and then get the next few elements? - javascript

I'm building my blog page for my website and I have a posts folder with markdown files of my blogs. I'm just figuring out a way to display all the blogs on a page, but I want to optimize it a bit so it doesn't try to load all blog posts at once but only the first 6 for example. And then when you click on a Load More button the next 6 get loaded and displayed.
This is the code I'm using to get the data from my blog posts:
async function getBlogPosts(n: number) {
const files = fs.readdirSync('posts');
const posts = files.slice(0, n).map((fileName) => {
const slug = fileName.replace('.md', '');
const readFile = fs.readFileSync(`posts/${fileName}`, 'utf-8');
const { data: frontmatter } = matter(readFile);
return {
slug,
frontmatter,
};
});
return posts;
}
And then display the title of the posts:
export default async function Blogs() {
const posts = await getBlogPosts(6);
return (
<div className="mx-auto flex">
{posts.map(({ slug, frontmatter }) => (
<div
key={slug}
className="m-2 flex flex-col overflow-hidden rounded-xl border border-gray-200 shadow-lg"
>
<Link href={`/blog/${slug}`}>
<h3 className="p-4">{frontmatter.title}</h3>
</Link>
</div>
))}
</div>
);
}
How would one go about implementing this?
Because I think if I were to call GetBlogPosts(12) it would load 12 posts but also the first 6 which have already been loaded.

You code is perfect.
Just implement pagination during slice method, increase page number as user clicks show more. and slice REQUIRED FILES data only.
Your code would look like this:
async function getBlogPosts(blogsPerPage:number, pageNumber:number) {
const files = fs.readdirSync('posts');
!IMPORTANT. sort files array here before slicing, so we wont get repeated posts...
const posts = files.slice((pageNumber-1)* blogsPerPage,pageNumber*blogsPerPage).map((fileName) => {
const slug = fileName.replace('.md', '');
const readFile = fs.readFileSync(`posts/${fileName}`, 'utf-8');
const { data: frontmatter } = matter(readFile);
return {
slug,
frontmatter,
};
});
return posts;
}
Now you can implement a state in your functional component and call this function somewhat like this:
export default async function Blogs() {
const [pageNumber, setPageNumber] = useState(0);
const posts = await getBlogPosts(6,pageNumber);
......
}

import { useState } from 'react';
export default function Blogs() {
const [numPosts, setNumPosts] = useState(6); // number of posts to display
const [posts, setPosts] = useState([]); // array of blog post data
const loadMore = () => {
setNumPosts(numPosts + 6); // increase the number of posts to display by 6
};
// fetch the blog post data when the component mounts or when the number of posts to display changes
useEffect(() => {
async function fetchData() {
const files = fs.readdirSync('posts');
const data = files.map((fileName) => {
const slug = fileName.replace('.md', '');
const readFile = fs.readFileSync(`posts/${fileName}`, 'utf-8');
const { data: frontmatter } = matter(readFile);
return {
slug,
frontmatter,
};
});
setPosts(data);
}
fetchData();
}, [numPosts]);
return (
<div className="mx-auto flex flex-wrap">
{posts.slice(0, numPosts).map(({ slug, frontmatter }) => (
<div
key={slug}
className="m-2 flex flex-col overflow-hidden rounded-xl border border-gray-200 shadow-lg"
>
<Link href={`/blog/${slug}`}>
<h3 className="p-4">{frontmatter.title}</h3>
</Link>
</div>
))}
{numPosts < posts.length && (
<button onClick={loadMore} className="my-4 mx-auto bg-blue-500 text-white px-4 py-2 rounded">
Load More
</button>
)}
</div>
);
}

Related

React - I can't render the correct amount of items in from the map function

Image of the unwanted behaviourI'm using DaisyUi / Tailwindcss to render a Carousel and it's child item. The item are images that are fetched from a firebase storage.
The issue is when I try to map the image urls, which is a state of an array of strings, I only get 1 image instead of all the items.
There's 2 items in my storage and it's complete when I log it's content / arrays' length but the state(imageUrls) is not filled and it only shows 1 item.
Ask for any clarification.
Here is my code for my carousel component.
const MyCarousel = () => {
const [imageUrls, setImageUrls] = useState<string[]>([]);
const storage = useStorage();
const getImages = async () => {
const storageRef = ref(storage, 'Tech/');
const files = await listAll(storageRef);
const imageAppender: string[] = Array(files.items.length);
console.log('img', imageAppender.length);
files.items.forEach(async (item, key) => {
const imageRef = ref(storage, item.fullPath);
getDownloadURL(imageRef).then((url) => {
imageAppender[key] = url;
setImageUrls(imageAppender);
});
});
};
useEffect(() => {
getImages();
}, []);
return (
<>
<div className="carousel w-full">
{imageUrls.map((image, key) => {
return (
<div key={key} id={'item' + key} className="carousel-item w-full">
<img src={image} className="w-full" />
</div>
);
})}
</div>
<div className="flex justify-center w-full py-2 gap-2">
{imageUrls.map((_, key) => {
return (
<a key={key} href={'#item' + key} className="bth btn-ghost btn-circle text-center">
{key}
</a>
);
})}
</div>
</>
);
};
export default MyCarousel;
Problem is with the way you update your imageUrls state. You always pass same imageAppender in your setImageUrls, so in that case DOM is re-rendered only after first setImageUlrs state update.
files.items.forEach(async (item, key) => {
const imageRef = ref(storage, item.fullPath);
getDownloadURL(imageRef).then((url) => {
setImageUrls(currentUlrs => {
const updatedUrls = [...currentUlrs];
updatedAppender[key] = url;
return updatedAppender;
});
});
})
Please read useState functional updates - to get latest state on update.
So basically you need to pass new reference to new array (create updatedAppender) to trigger re-render.
In addition
It is better to not use forEach with async tasks. Please read this thread for alternatives What is the difference between async/await forEach and Promise.all + map

Add item from Fetch API to Array and Displaying new array in React

I'm learning react for the first time, I have an app where it fetches some data from a public API. I currently have it show 10 cards with random items from the API, and I have added a button to fetch a random item from the API and add it to the array, I managed to get the new item added to the array using push() but it does not show in the app itself. How can I make it that the new item is shown in the app as well?
Here is my code
Home.js
import { useState, useEffect} from "react";
import Card from './Card';
const Home = () => {
const [animals, setAnimals] = useState([]);
const handleDelete = (id) => {
const newAnimals = animals.filter(animal => animal.id !== id);
setAnimals(newAnimals);
}
useEffect(() => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/10')
.then(res => {return res.json()})
.then(data => {
setAnimals(data);
});
}, []);
const handleAddAnimal = () => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/')
.then(res => {return res.json()})
.then(data => {
animals.push(data);
console.log(animals);
//what to do after this
})
}
return (
<div className="home">
<h2>Animals</h2>
<button onClick={handleAddAnimal}>Add Animal</button>
<Card animals={animals} handleDelete={handleDelete}/>
</div>
);
}
export default Home;
Card.js
const Card = ({animals, handleDelete}) => {
// const animals = props.animals;
return (
<div className="col-3">
{animals.map((animal) => (
<div className="card" key={animal.id}>
<img
src={animal.image_link}
alt={animal.latin_name}
className="card-img-top"
/>
<div className="card-body">
<h3 className="card-title">{animal.name}</h3>
<p>Habitat: {animal.habitat}</p>
<button onClick={() => handleDelete(animal.id)}>Delete Animal</button>
</div>
</div>
))}
</div>
);
}
export default Card;
App.js
import Navbar from './navbar';
import Home from './Home';
function App() {
return (
<section id="app">
<div className="container">
<Navbar />
<div className="row">
<Home />
</div>
</div>
</section>
);
}
export default App;
Screenshot of what I see now
screenshot
(I was also wondering how to fix the items going down instead of side by side but wanted to fix the add button first)
Let me know if there's anything else I should add, any help is appreciated, thank you!
Rather using array.push() method. You try using
setTheArray([...theArray, newElement]); e.g in your case it will be setAnimals([...animals,data]) in your onClick event.
Let me know doest it solve your issue or not.
const handleAddAnimal = () => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/')
.then(res => {return res.json()})
.then(data => {
setAnimals([...animals,data])
console.log(animals);
//what to do after this
})
}

Unable to retrieve individual blog in react using axios

I have spent a couple of time trying to figure out why I'm not able to obtain individual blog post detail page using axios. The code does not return any data (It is returning undefined)
I have the follow code:
/public
/src
/components
/blog
BlogPosts.js
BlogDetail.js
...
App.js
import BlogDetail from './components/blog/BlogDetail';
The routing for the DETAIL_POST is:
<Route exact path='/blog/:id' component={BlogDetail} />
DETAIL_POST COMPONENT
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
export default const BlogDetail = (props) => {
const [blog, setBlog] = useState({});
useEffect(() => {
const slug = props.match.params.id;
const fetchData = async () => {
try {
const res = await axios.get(`https://example.com/blog/${slug}`);
setBlog(res.data);
}
catch (err) {
}
};
fetchData();
}, [props.match.params.id]);
const createBlog = () => {
return {__html: blog.body}
};
const capitalizeFirstLetter = (word) => {
if (word)
return word.charAt(0).toUpperCase() + word.slice(1);
return '';
};
return (
<div>
<div dangerouslySetInnerHTML={createBlog()} />
</div>
);
};
BlogPost COMPONENT
const Blog = () => {
const [blogs, setBlogs] = useState([]);
useEffect(() => {
const fetchBlogs = async () => {
try {
const res = await axios.get(`${process.env.REACT_APP_API_URL}/blog/post`);
setBlogs(res.data);
}
catch (err) {
}
}
fetchBlogs();
}, []);
const getBlogs = () => {
let list = [];
let result = [];
blogs.map(blogPost => {
return list.push(
<div className="row no-gutters border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<p className="card-text mb-auto">{blogPost.introduction}</p>
<Link to={`/blog/${blogPost.slug}`} className="stretched-link">Read More</Link>
</div>
);
});
for (let i = 0; i < list.length; i += 2) {
result.push(
<div key={i} className='row mb-2'>
<div className='col-md-6'>
{list[i]}
</div>
<div className='col-md-6'>
{list[i+1] ? list[i+1] : null}
</div>
</div>
)
}
return result;
};
return (
<div className="jumbotron p-4 p-md-5 text-white rounded bg-dark">
{getBlogs()}
</div>
);
};
export default Blog;
On checking the browser console I saw this error: Failed to load resource: the server responded with a status of 404 (Not Found) but I can't find where the error is because other components are returning data except the particular one.
The code returns data for those I practice with but never works in my case but everything seems to be similar to theirs.
Check the network tab to see the type of response which is fetched or if the request is made or not then make necessary changes.
The solution to the question happens to be simple, I couldn't have made such a mistake. In case anybody encounters similar issue and the frontend seems to work fine, here is the approach I took. I checked serializers.py in the backend app and saw that I did not add slug to the field even if I have slug in blog post models.py. Adding slug to fields in `serializers.py1 fixed the issue

how to append react component with appendChild method

I have to use the following component more than 1 time.
const OnePen = (props) => {
return (
<div className="cerd" key={props.ID}>
<div className=" card-body">
<h2 className="card-title">{props.title}</h2>
<p className="card-text">{props.desc}</p>
</div>
</div>
);
};
export default OnePen;
Using the above component, the following code is working fine and doing the perfect job.
import OnePen from "./OnePen";
const PensList = ({ pens }) => {
return (
<>
<div id="mainPen" className=" wrapper">
{pens.map((pen) => (
**<OnePen ID={pen.id} title={pen.title} desc={pen.description}></OnePen>**
))}
</div>
</>
);
};
export default PensList;
However, the following code does not work as it says the type of element appending with appendChild should be Node. when I create the same component with react-create-element it works fine but I don't have to do it again and again and I want to reuse the same component.
Here is the code with the problem...
commented code does the job but I don't have to create it like that.. I have to append
const handleSubmit = (e) => {
e.preventDefault();
const pen = { title, description };
axios.post("http://localhost:5000/addPen", pen).then((res) => {
if (res.status === 200) {
// const div1 = document.createElement("div");
// div1.className = "cerd";
// const key = document.createAttribute("key");
// key.value = res.data._id;
// const div2 = document.createElement("div");
// div2.className = "card-body";
// const h2 = document.createElement("h2");
// h2.className = "card-title";
// h2.innerHTML = res.data.title;
// const p = document.createElement("p");
// p.className = "card-text";
// p.innerHTML = res.data.description;
// div2.appendChild(h2);
// div2.appendChild(p);
// div1.appendChild(div2);
**document
.getElementById("mainPen")
.appendChild(
<OnePen ID={res.data._id} title={res.data.title} desc={res.data.description}></OnePen>
);**
}
});
};
return (
//some code
);
};
export default CreatePen;
Thanks for considrations
This is how you could approach this.
If you have an API, create an endpoint to fetch data from (all pens or a specific pen). Then, you could make a component where you fetch the data with axios.get() and put it in a state variable. If you put this code inside the useEffect() hook, the data will be fetched on load of the component.
// state for your pens
const [pens, setPens] = useState([]);
// fetch on load
useEffect(() => {
axios
.get("http://localhost:5000/getPens") // set to your endpoint
.then((response) => {
setPens(response.data);
})
.catch((error) => {
console.log(error);
});
}, []);
Then, you could render the pens by mapping through them like you did before to display all of them, or you could display a single pen.
/***********
* Render
***********/
const pensDisplay = pens.map((p, i) => {
return (
<div key={i}>
{/* for example */}
<p>{p.size}</p>
</div>
);
});
return (
<div>
<h3>Pens</h3>
{pensDisplay}
</div>
);
In React, you should avoid using appendChild() and such and try doing as much as possible in the "React" way.

how to show loader in react . using hooks

I am using axios for communicate with server.I want to show loader when user request to server and hide the loader when request is complete
So i make a custom component to do this task .but my UI hang when I click multiple times on same button
const Loader = () => {
const { loadingCount } = useLoadingState(),
{showLoading, hideLoading} = useLoadingActions();
useEffect(()=>{
const self = this
axios.interceptors.request.use(function (config) {
showLoading();
return config
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
// spinning hide
// self.props.loading(false)
hideLoading()
return response;
}, function (error) {
hideLoading();
return Promise.reject(error);
});
})
return (
<div>
{loadingCount > 0 ?<div style={{position:"fixed",display:"flex",justifyContent:"center",alignItems:"center",width:'100%',height:'100%',zIndex:999999}}>
{/*{loadingCount > 0 ? */}
<Spin tip="Loading..." style={{zIndex:999999}}></Spin>
{/*: null}*/}
</div>: null}
</div>
);
};
Problem is on useeffect
when I comment out useEffect code it works perfectly .
NoTe : showloading and hideloading increase and decrease the loading count.
I think I have deallocate axios object the when component is unmount.???
Add empty array to sencod parameter to useEffect.
It works like componentDidMount() in functional component.
const { useState, useEffect } = React;
const Counter = () => {
const [count, setCount] = useState(0)
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
setTimeout(() => {
setIsLoaded(true);
}, 3000);
}, []); // here
return (
<div>
{
isLoaded &&
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
}
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="app"></div>
i usualy use this code to show loading when request data is processing and hide when it's done
const Loader = () => {
const {data, setdata} = useState([])
useEffect(()=>{
axios.get('your host').then(res => {
setdata(res.data);
}).catch(err => {
setdata(res.data);
}
});
return (
<div>
{data.length > 0
?
<div style={{position:"fixed",display:"flex",justifyContent:"center",alignItems:"center",width:'100%',height:'100%',zIndex:999999}}> </div>
:
<Spin tip="Loading..." style= {{zIndex:999999}}>
</Spin>
</div>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
First I created Loading components in shared folder. I am using Daisy UI, that's why you have to first install tailwind & daisy otherwise it will not work. My Loading Code:
import React from 'react';
const Loading = () => {
return (
<div className="flex items-center justify-center ">
<div className="w-16 h-16 border-b-2 border-gray-900 rounded-full animate-spin"></div>
</div>
);
};
export default Loading;
Then I am using this Loading component in my Allproduct component. For viewing Loading i created Reload useState.You will see below in my code, that will help my loader show when fetching time is very long.
import React, { useEffect, useState } from 'react';
import Loading from '../Shared/Loading';
import AllProduct from './AllProduct';
const AllProducts = () => {
const [products, setProduct]=useState([])
const [Reload, setReload] = useState(true);
useEffect(()=>{
fetch('https://stormy-hamlet-97462.herokuapp.com/products/')
.then(res=>res.json())
.then(data=>{setProduct(data)
setReload(false)})
},[])
if(Reload){
return <Loading></Loading>
}
return (
<div>
<h4 className='text-4xl text-primary text-center sm:w-full px-32 mx-5
lg:my-12 '>All Products</h4>
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5'>
{
products.map(product=><AllProduct key={product._id} product={product} ></AllProduct>)
}
</div>
</div>
);
};
export default AllProducts;

Categories

Resources