I need to fetch data from the MongoDB collection after the user clicks the id properties of the document
[frontend][1]
[1]: https://i.stack.imgur.com/fmW1N.jpg
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
const ViewVehicles = () => {
const [vehicles, setVehicles] = useState(null);
useEffect(() => {
const fetchvehicles = async () => {
const response = await fetch("/headofDeployement/registerVehicle");
const json = await response.json();
if (response.ok) {
setVehicles(json);
}
};
fetchvehicles();
}, []);
return (
<div className="container ">
<div className="row ">
<div className="col justify-content-center align-center">
<h4>Vehicles Registered</h4>
{vehicles &&
vehicles.map((vehicle) => (
<ul key={vehicle._id}>
<a href="" className="text-danger">
{vehicle._id}
</a>
</ul>
))}
</div>
</div>
</div>
);
};
export default ViewVehicles;
I'm not sure if I understand correctly but you should use the Link component you imported from react-router-dom.
For further reading and methodology of how this use case can be handled, check: https://v5.reactrouter.com/web/example/url-params
Related
I'm trying to make a dropdown menu like this
-title 1
|-> "lists of items with a href attached"
-title 2
|-> "lists of items with a href attached"
-title 3
|-> "lists of items with a href attached"
Right now my code is like this
footerMenu.js
import { useState } from "react";
import FooterItem from "./FooterItem";
const FooterMenu = (props) => {
return (
<>
<div className="dropdown-menu shadow-lg shadow-slate-300 ml-[40vw] px-12">
<ul className="list-none">
<FooterItem val={"About Us"} />
<FooterItem val={"Careers"} />
<FooterItem val={"Social Impact"} />
<FooterItem val={"For Business Partners"} />
<FooterItem val={"Order and Pickup"} />
</ul>
</div>
</>
);
};
export default FooterMenu;
footerItem.js
import { useState } from "react";
import { FooterList } from "./FooterList";
const FooterItem = (props) => {
const [open, setOpen] = useState(false);
const showDropdown = () => {
setOpen(!open);
};
return (
<li onClick={showDropdown} className="menu-head p-5">
<h2 className="text-xl font-semibold mb-5">{props.val}</h2>
<ul
className={`menu-item relative px-1 gap-y-5 ${
open ? "open-list" : "close-list"
}`}
>
<FooterList />
</ul>
</li>
);
};
export default FooterItem;
and footerList.js
import axios from "axios";
export const FooterList = async () => {
let response = await axios.get(`http://localhost:5000/footerMenu`);
return response.data[0].content.map((val) => {
return (
<>
<li>
<a href={val.link}>
{val.subtitle}
</a>
</li>
</>
);
});
my plan was adding the lists of items for each menu title in a database, and then using axios to get the list but it says objects are not valid as a react child(found: Object Promise])
this is the db i made
i don't know if there is a simpler way of making this dropdown menu
You must use hooks to call async function in react functioal component.
import { useState, useEffect } from "react";
import axios from "axios";
export const FooterList = () => {
const [footerData, setFooterData] = useState();
useEffect(() => {
axios.get(`http://localhost:5000/footerMenu`)
.then((response) => setFooterData(response.data[0].content));
}, [])
return footerData ? footerData.map((val) => {
return (
<li>
<a href={val.link}>
{val.subtitle}
</a>
</li>
);
}) : <LoadingComponent />;
}
Edit 1
I have updated my code (changed from useState to useEffect) but seems like the same problem. Only if I remove the return code, it runs perfectly. Seems like it happens when I have lots of data to show in view
Edit 2
I updated my useEffect() to below but still the same problem.
useEffect(() => {
let mounted = true;
if (mounted) {
FetchOrderDetails();
}
return () => (mounted = false);
}, []);
Edit Ends
I get this warning sometimes and it crashes Warning: Can't perform a React state update on a component that hasn't mounted yet. This indicates that you have a side-effect in your render function that asynchronously later calls tries to update the component. Move this work to useEffect instead.
Surprisingly, sometimes the code runs perfectly which most of the time it doesn't. What is wrong?
My code
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { useCookies } from "react-cookie";
import * as Constants from "../Constants";
import HeaderTemplate from "../Templates/HeaderTemplate";
const OrderDetails = () => {
const { transactionRefNo, transactionId } = useParams();
const [cookies] = useCookies(["user"]);
const [orderDetails, setOrderDetails] = useState([]);
const FetchOrderDetails = async () => {
let options = {
employeeid: parseInt(cookies.employeeId),
transactionid: parseInt(transactionId),
};
await fetch(Constants.API_ENDPOINT + "/test/orderdetails", {
method: "POST",
body: JSON.stringify(options),
})
.then((response) => response.json())
.then((data) => setOrderDetails(data.order));
};
useEffect(() => {
console.log(transactionRefNo, transactionId);
FetchOrderDetails();
}, []);
return (
<div>
<HeaderTemplate />
<React.Fragment>
{/* Order Details Starts*/}
<div className="panel panel-default mt-4">
<div className="panel-heading">
<h6 className="panel-title">Order Details</h6>
</div>
<div className="panel-body">
<b>
<small>
{"Order #" +
orderDetails["transaction_reference_no"]}
</small>
</b>
<div className="row">
<div className="col-md-6">
<small>Port: {orderDetails["port"]}</small>
</div>
</div>
<div className="row">
<div className="col-md-6">
<small>
Type:{" "}
{orderDetails["transaction_type"] ===
"selling_stages"
? "Selling"
: "Buying"}
</small>
</div>
</div>
</div>
</div>
{/* Order Details Ends*/}
</React.Fragment>
</div>
);
};
export default OrderDetails;
It turns out, useEffect tries to update the state both when it mounts and unmounts. Hence the error occurs. As suggested by #super, I used swr to circumvent the issue in my case
import React from "react";
import { useParams } from "react-router-dom";
import { useCookies } from "react-cookie";
import * as Constants from "../Constants";
import HeaderTemplate from "../Templates/HeaderTemplate";
const fetcher = async (cookies, transactionId) => {
let options = {
employeeid: parseInt(cookies.employeeId),
transactionid: parseInt(transactionId),
};
const res = await fetch(Constants.API_ENDPOINT + "/test/orderdetails", {
method: "POST",
body: JSON.stringify(options),
});
const json = await res.json();
return json;
};
const OrderDetails = () => {
const { transactionRefNo, transactionId } = useParams();
const [cookies] = useCookies(["user"]);
const { data, error } = useSwr([cookies, transactionId], fetcher);
if (!data) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return (
<div>
<HeaderTemplate />
<React.Fragment>
{/* Order Details Starts*/}
<div className="panel panel-default mt-4">
<div className="panel-heading">
<h6 className="panel-title">Order Details</h6>
</div>
<div className="panel-body">
<b>
<small>
{"Order #" +
data.order.transaction_reference_no}
</small>
</b>
<div className="row">
<div className="col-md-6">
<small>Port: {data.order.port}</small>
</div>
</div>
<div className="row">
<div className="col-md-6">
<small>
Type:{" "}
{data.order.transaction_type ===
"selling_stages"
? "Selling"
: "Buying"}
</small>
</div>
</div>
</div>
</div>
{/* Order Details Ends*/}
</React.Fragment>
</div>
);
};
export default OrderDetails;
I need your help with an app that I am building. It has a forum page and I have some issues with the forum and post components.
I am trying to pass the id of the post that the user clicked on, with history.push so on the post page the id in the url that I try to get with useParams, has the value of the one I send with history.push. The purpose is for some queries I do so I show the post with its comments.
For now the layout isn’t great because I have to make this feature work.
I do not understand why it doesn’t. My console.logs show null or undefined which make no sense to me.
Thank you if you can help me with this.
Here you have two routes present in the App component. It is important for the last route, the Post one were I use :id so I can get it with useParams.
{/* Route for Trainings Wakeup Rebirth */}
<Route path='#forum' exact component={TrainingsWakeupRebirth} />
<Route path='#forum/:id' exact component={Post} />
Here you have the entire code of the Forum page. Like that you can see how I use history.push to send the value.id of the post to the Post component and the way the component itself is built.
import React, { useState, useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import ReactPaginate from "react-paginate";
import Post from "../Post/Post";
import './TrainingsWakeupRebirth.scss';
import axios from "axios";
const TrainingsWakeupRebirth = (props) => {
let history = useHistory();
// const forumSectionRef = useRef();
// const postSectionRef = useRef();
const forumSection = document.getElementById('forum-block-wrapper');
const postSection = document.getElementById('post-section');
const showPost = () => {
if (forumSection.style.display === 'block') {
return forumSection.style.display = 'none',
postSection.style.display = 'block';
} else {
return forumSection.style.display = 'block',
postSection.style.display = 'none';
}
}
const [listOfPosts, setListsOfPosts] = useState([]);
const [pageNumber, setPageNumber] = useState(0);
const postsPerPage = 2;
const pagesVisited = pageNumber * postsPerPage;
const displayPosts = listOfPosts.slice(pagesVisited, pagesVisited + postsPerPage).map((value, key) => {
const forParams = () => {
return history.push(`#forum/${value.id}`);
}
const executeAll = () => {
forParams();
showPost();
if(forParams()) {
let id = value.id;
return id;
}
}
return (
<div key={key}>
<div className="topic-row" onClick={() => {executeAll()}}>
<div className="topic-title">{value.title}</div>
<div className="topic-image">
<img src={value.image} alt=""></img>
</div>
<div className="topic-message">{value.postText}</div>
</div>
</div>
);
});
const pageCount = Math.ceil(listOfPosts.length / postsPerPage);
const changePage = ({selected}) => {
setPageNumber(selected);
};
useEffect(() => {
axios.get("http://localhost:3001/posts").then((response) => {
setListsOfPosts(response.data);
});
}, []);
console.log(listOfPosts);
return (
<div className="forum" id="forum">
<div className="forum-section-wrapper page" id="forum-wrapper">
<div className="fluid-grid">
<div className="row">
<div className="col-12">
<div className="title">
<h1><span className="first-title-part">Krishna</span><span className="second-title-part">Hara</span></h1>
</div>
<div className="quote">
<span className="quote-left">FORUM</span><span className="quote-right">Eco Village</span>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="forum-block-wrapper" id="forum-block-wrapper">
{displayPosts}
<ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
pageCount={pageCount}
onPageChange={changePage}
containerClassName={"paginationBttns"}
previousLinkClassName={"previousBttn"}
nextLinkClassName={"nextBttn"}
activeClassName={"paginationActive"}
/>
</div>
</div>
</div>
</div>
</div>
<div className="post-section" id="post-section">
<div className="fluid-grid">
<div className="row">
<div className="col-12">
<Post />
</div>
</div>
</div>
</div>
</div>
)
};
export default TrainingsWakeupRebirth;
Here is some code from the Post component, so you can see the code that should work but doesn't. Also the console.log(id)
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
const Post = (props) => {
let { id } = useParams();
const [postObject, setPostObject] = useState({});
const [comments, setComments] = useState([]);
const [newComment, setNewComment] = useState("");
console.log(id);
useEffect(() => {
axios.get(`http://localhost:3001/posts/byId/${id}`).then((response) => {
console.log(response);
setPostObject(response.data);
});
axios.get(`http://localhost:3001/comments/${id}`).then((response) => {
setComments(response.data);
});
}, [id]);
const addComment = () => {
axios.post("http://localhost:3001/comments", {
commentBody: newComment,
Postid: id,
})
.then((response) => {
const commentToAdd = { commentBody: newComment };
setComments([...comments, commentToAdd]);
setNewComment("");
});
};
console.log(postObject);
return (
<div className="post-section-wrapper">
{/* <div>
<div className="title">
{postObject.title}
</div>
<div className="image">
<img src={postObject.image}></img>
</div>
<div className="message">
{postObject.postText}
</div>
</div> */}
<div className="comments-wrapper">
<div className="">
<input
type="text"
placeholder="Comment..."
autoComplete="off"
value={newComment}
onChange={(event) => {
setNewComment(event.target.value);
}}
/>
<button onClick={addComment}> Add Comment</button>
</div>
<div className="comments-row">
{comments.map((comment) =>
(
<div key={comment.id} className="comment">
{comment.commentBody}
</div>
)
)}
</div>
</div>
</div>
);
}
export default Post;
Thank you very very much!!!
#DrewReese and #JoelHager Thank you so much for checking my code and for your advice. In the meantime I found out that we can pass to a component, aside from the pathname, other values with history.push that we retrieve by using useLocation in the component that we want to. I will answer my own question and add the code.
Here is my Forum component, I prefer adding the entire code so everything is clear. In forParams you will see how I pass the value that I need with useHistory and the attribute state and detail.
import React, { useState, useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import ReactPaginate from 'react-paginate';
import Post from '../Post/Post';
import './TrainingsWakeupRebirth.scss';
import axios from 'axios';
const TrainingsWakeupRebirth = (props) => {
let history = useHistory();
// const forumSectionRef = useRef();
// const postSectionRef = useRef();
const forumSection = document.getElementById('forum-block-wrapper');
const postSection = document.getElementById('post-section');
const showPost = () => {
if (forumSection.style.display === 'block') {
return forumSection.style.display = 'none',
postSection.style.display = 'block';
} else {
return forumSection.style.display = 'block',
postSection.style.display = 'none';
}
}
const [listOfPosts, setListsOfPosts] = useState([]);
const [pageNumber, setPageNumber] = useState(0);
const postsPerPage = 2;
const pagesVisited = pageNumber * postsPerPage;
const displayPosts = listOfPosts.slice(pagesVisited, pagesVisited + postsPerPage).map((value, key) => {
const forParams = () => {
history.push(
{
pathname: `#forum#${value.id}`,
state: { detail: value.id }
}
);
}
const executeAll = () => {
forParams();
showPost();
}
return (
<div key={key} onClick={() => {executeAll()}}>
<div className="topic-row">
<div className="topic-title">{value.title}</div>
<div className="topic-image">
<img src={value.image} alt=""></img>
</div>
<div className="topic-message">{value.postText}</div>
</div>
</div>
);
});
const pageCount = Math.ceil(listOfPosts.length / postsPerPage);
const changePage = ({selected}) => {
setPageNumber(selected);
};
useEffect(() => {
axios.get("http://localhost:3001/posts").then((response) => {
setListsOfPosts(response.data);
});
}, []);
console.log(listOfPosts);
return (
<div className="forum" id="forum">
<div className="forum-section-wrapper page" id="forum-wrapper">
<div className="fluid-grid">
<div className="row">
<div className="col-12">
<div className="title">
<h1><span className="first-title-part">Krishna</span><span className="second-title-part">Hara</span></h1>
</div>
<div className="quote">
<span className="quote-left">FORUM</span><span className="quote-right">Eco Village</span>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="forum-block-wrapper" id="forum-block-wrapper">
{displayPosts}
<ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
pageCount={pageCount}
onPageChange={changePage}
containerClassName={"paginationBttns"}
previousLinkClassName={"previousBttn"}
nextLinkClassName={"nextBttn"}
activeClassName={"paginationActive"}
/>
</div>
</div>
</div>
</div>
</div>
<div className="post-section" id="post-section">
<div className="fluid-grid">
<div className="row">
<div className="col-12">
<Post />
</div>
</div>
</div>
</div>
</div>
)
};
export default TrainingsWakeupRebirth;
In the Post component with useLocation and useEffect I get location.state.detail which is the id of the Post, that with useState I set to the constant postId.
import React, { useEffect, useState } from "react";
import { useParams, useHistory, useLocation } from "react-router-dom";
import axios from "axios";
import './Post.scss';
const Post = (props) => {
// let { id } = useParams();
const location = useLocation();
const [postId, setPostId] = useState();
useEffect(() => {
console.log(location.pathname); // result: '#id'
if(location.state) {
console.log(location.state.detail); // result: postId
setPostId(location.state.detail);
}
}, [location]);
const [postObject, setPostObject] = useState({});
const [comments, setComments] = useState([]);
const [newComment, setNewComment] = useState("");
// console.log(id);
useEffect(() => {
axios.get(`http://localhost:3001/posts/byId/${postId}`).then((response) => {
console.log(response.data);
setPostObject(response.data);
});
axios.get(`http://localhost:3001/comments/${postId}`).then((response) => {
setComments(response.data);
});
}, [postId]);
const addComment = () => {
axios.post("http://localhost:3001/comments", {
commentBody: newComment,
Postid: postId,
})
.then((response) => {
const commentToAdd = { commentBody: newComment };
setComments([...comments, commentToAdd]);
setNewComment("");
});
};
if(postObject !== null) {
console.log(postObject);
}
return (
<div className="post-section-wrapper">
{postObject !== null
?
<div className="posts-wrapper">
<div className="title">
{postObject.title}
</div>
<div className="image">
<img src={postObject.image}></img>
</div>
<div className="message">
{postObject.postText}
</div>
</div>
:
null
}
<div className="comments-wrapper">
<div className="">
<input
type="text"
placeholder="Comment..."
autoComplete="off"
value={newComment}
onChange={(event) => {
setNewComment(event.target.value);
}}
/>
<button onClick={addComment}> Add Comment</button>
</div>
<div className="comments-row">
{comments.map((comment) =>
(
<div key={comment.id} className="comment">
{comment.commentBody}
</div>
)
)}
</div>
</div>
</div>
);
}
export default Post;
Currently i am practising my React skills. Now i am working on my Hooks knowledge. I have used a Coctail API to load some data. Just for fun i divided this up into three categories with a navigation bar.
When i click on the navigation items i go to the subcategories. But my classes won't update so the tab system doesn't work. You guys know what i have to do here? I tried this in a few different ways but none worked.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const OverView = () => {
const [ term, setTerm ] = useState('Gin');
const [ categorie, setCategorie ] = useState('Cocktail');
const [ debouncedTerm, setDebouncedTerm ] = useState(term);
const [ results, setResults] = useState([]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 10);
return () =>{
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const search = async () => {
const { data } = await axios({
'method':'GET',
'url':'https://the-cocktail-db.p.rapidapi.com/filter.php',
'headers':{
'content-type':'application/octet-stream',
'x-rapidapi-host':'the-cocktail-db.p.rapidapi.com',
'x-rapidapi-key':'49adf3a5ccmsh5d525b0005370d3p1b460bjsn7b0add17579e',
'useQueryString':true
},'params':{
'i': debouncedTerm
},
});
setResults(data.drinks);
};
search();
}, [debouncedTerm]);
const renderResults = results.map((result) => {
return (
<div key={result.idDrink} className="four wide column" style={{marginBottom:"20px"}}>
<a href={result.idDrink}>
<div className="card">
<img src={result.strDrinkThumb} width="250"/>
<div className="header">{result.strDrink}</div>
</div>
</a>
</div>
);
});
return (
<div className="ui grid">
<div className="row">
<h3>Select your favourate drink to get started</h3>
<div className="ui top attached tabular menu">
<div
className={'item active'}
onClick={(e) => setTerm('Gin') }>
Gin
</div>
<div
className={'item'}
onClick={(e) => setTerm('Vodka')}>
Vodka
</div>
<div
className={'item'}
onClick={(e) => setTerm('Whiskey')}>
Whiskey
</div>
</div>
</div>
<div className="row">
{renderResults}
</div>
</div>
);
};
export default OverView;
Thanks in advance.My first problem is that i don't know how to add an extra action upon my hook. I can't attach an extra onClick event and don't know how to add this to my useEfect functions.
Set the className based on the term state atom, simple as that.
I also refactored things a bit:
the result component is now, well, a separate component
searching is refactored into a separate function
import React, { useState, useEffect } from "react";
import axios from "axios";
const ResultCard = ({ result }) => (
<div className="four wide column" style={{ marginBottom: "20px" }}>
<a href={result.idDrink}>
<div className="card">
<img src={result.strDrinkThumb} width="250" />
<div className="header">{result.strDrink}</div>
</div>
</a>
</div>
);
async function doSearch(term) {
const { data } = await axios({
method: "GET",
url: "https://the-cocktail-db.p.rapidapi.com/filter.php",
headers: {
"content-type": "application/octet-stream",
"x-rapidapi-host": "the-cocktail-db.p.rapidapi.com",
"x-rapidapi-key": "49adf3a5ccmsh5d525b0005370d3p1b460bjsn7b0add17579e",
useQueryString: true,
},
params: {
i: term,
},
});
return data;
}
const OverView = () => {
const terms = ["Gin", "Vodka", "Whiskey"];
const [term, setTerm] = useState("Gin");
const [results, setResults] = useState([]);
useEffect(() => {
doSearch(term).then((data) => setResults(data.drinks));
}, [term]);
return (
<div className="ui grid">
<div className="row">
<h3>Select your favourate drink to get started</h3>
<div className="ui top attached tabular menu">
{terms.map((t) => (
<div
className={["item", term === t ? "active" : null].filter(Boolean).join(" ")}
onClick={(e) => setTerm(t)}
>
{t}
</div>
))}
</div>
</div>
<div className="row">
{results.map((result) => (
<ResultCard result={result} key={result.idDrink} />
))}
</div>
</div>
);
};
export default OverView;
You may want to look into the classnames module; the arcane [].filter().join() expression would become cx({item: true, active: t === term}) :)
While trying to render a functional component and trying to truncate a large paragraph on a map item using item.biography.substr(0, 20).
I have tried different syntaxes without success. Will appreciate any help. Here is my component.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import axios from 'axios';
import $ from 'jquery';
//import "./styles.css";
function Instructor() {
const [page, setPage] = useState(1);
const [data, setData] = useState(['a', 'b', 'c']);
const [isLoading, setIsLoading] = useState(true);
const loadMoreData = () => {
setPage(page + 1);
};
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'http://www.localhost/ulearn/api/getInstructors',
);
setData(result.data);
};
fetchData();
}, []);
return (
<div>
<h1> API FOR INSTRUCTOR COMPONENT </h1>
{isLoading && <p>Wait I'm Loading comments for you</p>}
{data.length !== 0 && (
<button onClick={loadMoreData}>Load More Data</button>
)}
{data.map((item, index) => (
<div className="col-xl-3 col-lg-4 col-md-6 col-sm-6" key={index}>
<div className="instructor-box mx-auto text-center">
<a href="{{ route(d.view, d.instructor_slug) }}">
<main>
<div className="col-md-12">
<h6 className="instructor-title">{item.first_name}
{item.last_name}
`enter code here`</h6>
<p> {item.biography.substr(0, 20)} </p>
</div>
</main>
</a>
</div>
</div>
))}
</div>
);
}
if (document.getElementById('instructor')) {
ReactDOM.render(<Instructor />, document.getElementById('instructor'));
}
It seems like it is trying run the substring method on an undefined property? This could imply that biogarphy could be undefined.
Given that the initial state of data is ['a', 'b', 'c'], it is certain that biography is undefined while waiting for the response from fetchData() in the useEffect() hook.
In that case, you might want to do a null/undefined check and conditionally run the statement with the substr() method only if item.biography has been populated with the response from the useEffect hook
{item.biography && item.biography.substr(0, 20)}
if string can be null you'll get the error
change this line
<p> {item.biography.substr(0, 20)} </p>
to
{!!item.biography && (<p>{item.biography.substr(0, 20)}</p>)}
You need to add a check for biography is not undefined while using substr on it.
Also i suggest you to use .substring() as .substr is deprecated.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import axios from 'axios';
import $ from 'jquery';
//import "./styles.css";
function Instructor() {
const [page, setPage] = useState(1);
const [data, setData] = useState(['a', 'b', 'c']);
const [isLoading, setIsLoading] = useState(true);
const loadMoreData = () => {
setPage(page + 1);
};
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'http://www.localhost/ulearn/api/getInstructors',
);
setData(result.data);
};
fetchData();
}, []);
return (
<div>
<h1> API FOR INSTRUCTOR COMPONENT </h1>
{isLoading && <p>Wait I'm Loading comments for you</p>}
{data.length !== 0 && (
<button onClick={loadMoreData}>Load More Data</button>
)}
{data.map((item, index) => (
<div className="col-xl-3 col-lg-4 col-md-6 col-sm-6" key={index}>
<div className="instructor-box mx-auto text-center">
<a href="{{ route(d.view, d.instructor_slug) }}">
<main>
<div className="col-md-12">
<h6 className="instructor-title">{item.first_name}
{item.last_name}
`enter code here`</h6>
<p> {item.biography && item.biography.substring(0, 20)} </p>
</div>
</main>
</a>
</div>
</div>
))}
</div>
);
}
if (document.getElementById('instructor')) {
ReactDOM.render(<Instructor />, document.getElementById('instructor'));
}
It is very possible that the content of biography is undefined, so you can add a check by conditionally rendering it to make sure it only displays the biography if biography contains a value or exists.
You can change
<p> {item.biography.substr(0, 20)} </p>
to
{item.biography && <p> {item.biography.substr(0, 20)} </p>}