I have started my news project developing using React js. Unfortunately I have an issue. I am using axios for data fetching. I am making a request and I have an error in the console. I tried to use useState instead of variable posts in main file, but I had received the same error. I think, that something wrong either with posts variable, because I think, that useEffect is working slower, than html code, that will be returned or with map method.
Error:
TypeError: Cannot read properties of undefined (reading map) at news.jsx
Post file:
import React from 'react';
function Post(props) {
return (
<div className="post">
<div className="post-name">{props.title}</div>
<div className="post-content">{props.text}</div>
<a className="post-source" href={props.url}>{props.name}</a>
</div>
);
}
export default Post;
Main file with requests:
import React, { useEffect } from "react";
import SyncIcon from "#mui/icons-material/Sync";
import axios from "axios";
import "../css/news.css";
import Post from "./Post";
function News() {
let posts;
useEffect(() => {
const loading = document.querySelector(".loading");
const postsContainer = document.querySelector(".news-posts");
async function loadPosts() {
const date = new Date();
const day = date.getDate();
const month = date.getMonth();
const year = date.getFullYear();
const fullDate = year + "-0" + month + "-0" + day;
let response = [];
try {
const request = await axios.get(
`https://newsapi.org/v2/everything?qInTitle=Ukraine&from=${fullDate}&sortBy=publishedAt&apiKey=363858d3a88f49ffad9b467282270c8a`
);
const data = request.data.articles;
for (let i = 0; i < 20; i++) {
response.push({
source: {
name: data[i].source.name,
url: data[i].url,
},
content: {
title: data[i].title,
text: data[i].content,
},
});
}
} catch {
console.log("error");
}
loading.classList.add("none");
// setPosts(response);
posts = response;
}
loadPosts();
}, []);
return (
<section className="news-container">
<div className="news-posts">
<div className="loading">
<SyncIcon />
</div>
{posts.map((post) => (
<Post
name={post.source.name}
url={post.source.url}
text={post.content.text}
title={post.content.title}
/>
))}
</div>
</section>
);
}
export default News;
You should use useState for updating content in your component.
The if statements I've implemented are in order to ensure the content received is not empty. You can remove those after you've debugged this or implement different actions under those conditions.
import React, { useEffect, useState } from "react";
import SyncIcon from "#mui/icons-material/Sync";
import axios from "axios";
import "../css/news.css";
import Post from "./Post";
function News() {
const [posts, setPosts] = useState([]);
useEffect(async () => {
const loading = document.querySelector(".loading");
const postsContainer = document.querySelector(".news-posts");
const date = new Date();
const day = date.getDate();
const month = date.getMonth();
const year = date.getFullYear();
const fullDate = year + "-0" + month + "-0" + day;
let response = [];
try {
const request = await axios.get(
`https://newsapi.org/v2/everything?qInTitle=Ukraine&from=${fullDate}&sortBy=publishedAt&apiKey=363858d3a88f49ffad9b467282270c8a`
);
if (request.data) {
const data = request.data.articles;
if (data) {
for (let i = 0; i < 20; i++) {
response.push({
source: {
name: data[i].source.name,
url: data[i].url,
},
content: {
title: data[i].title,
text: data[i].content,
},
});
}
setPosts(response);
} else {
console.log("No articles in response data");
}
} else {
console.log("Empty response");
}
} catch (err) {
console.log(`Error: ${err}`);
}
loading.classList.add("none");
});
return (
<section className="news-container">
<div className="news-posts">
<div className="loading">
<SyncIcon />
</div>{" "}
{posts.map((post) => (
<Post
name={post.source.name}
url={post.source.url}
text={post.content.text}
title={post.content.title}
/>
))}
</div>{" "}
</section>
);
}
export default News;
This is one of the proper ways to fetch data and display them:
import React, { useEffect } from "react";
import SyncIcon from "#mui/icons-material/Sync";
import axios from "axios";
import "../css/news.css";
import Post from "./Post";
function News() {
const [posts, setPosts] = useState([]) // init state as empty array. We will store posts here
const [loading, setLoading] = useState(true) //Start with loading = true
const [error, setError] = useState(false) //Start with error = false
useEffect(() => {
async function loadPosts() {
const date = new Date();
const day = date.getDate();
const month = date.getMonth();
const year = date.getFullYear();
const fullDate = year + "-0" + month + "-0" + day;
try {
const request = await axios.get(
`https://newsapi.org/v2/everything?qInTitle=Ukraine&from=${fullDate}&sortBy=publishedAt&apiKey=363858d3a88f49ffad9b467282270c8a`
);
const data = request.data.articles;
for (let i = 0; i < 20; i++) {
response.push({
source: {
name: data[i].source.name,
url: data[i].url,
},
content: {
title: data[i].title,
text: data[i].content,
},
});
}
setPosts(response); //set state
setLoading(false) //end loading
} catch {
console.log("error");
setError(true)
setLoading(false)
}
}
loadPosts();
}, []);
return (
<section className="news-container">
<div className="news-posts">
{loading?
(<div className="loading">
<SyncIcon />
</div>)
: null }
{posts?.length? posts.map((post) => (
<Post
name={post.source.name}
url={post.source.url}
text={post.content.text}
title={post.content.title}
/>
))} : <p>No data found</p>
</div>
</section>
);
}
export default News;
Basically, the error is that your posts are undefined. And trying to map it breaks your app. You need to check if it exists and if it is an array, then map trough it. Also, the React way to render loading or error components is to use conditional rendering in the return function (check loading)
Also, you must use state otherwise React wont know if it needs to rerender anything.
You do not need to use query selectors
Related
I'm making an interactive graph which re-renders when you select a different date. It used to re-render fine before I made some fundamental changes (moved state responsibilites to the components instead of app.js. When I change the date, it does seem to actually change the url in the component responsible for making the api call. However, it's not re-rendering even though it's in a useEffect, and it did re-render before.
I'm new to React so it might be something very obvious. I'm also not sure if this is the right way to do things so I'm open for any pointers to improve my application or best practices.
App.js: Preferably I'd remove the useGetData and useURLSwitcher in the app.js, but without them it's not able to load the url on start up.
import "./App.css";
import React, { useEffect, useState } from "react";
import { Button } from "./components/button/Button";
import useGetData from "./apiData/useGetData";
import BarChart from "./components/BarChart";
import { SpinnerDiamond } from "spinners-react";
import GraphChart from "./components/GraphChart";
import { DatePicker } from "./components/datePicker/DatePicker";
import useURLSwitcher from "./apiData/useURLSwitcher";
import { DatePickerWithButton } from "./components/datePickerWithButton/DatePickerWithButton";
const App = () => {
const {url} = useURLSwitcher()
const { dataNew, isLoading, error } = useGetData(url);
console.log(url)
return isLoading ? (
<div className="spinner">
<SpinnerDiamond
size={400}
speed={100}
secondaryColor="#354A54"
color={"#00BAEC"}
/>
</div>
) : error ? (
console.log(error)
) : (
<div className="app">
<div className="example">
<div className="graph-box">
<div id="wrapper">
<GraphChart graphData={dataNew} />
</div>
<div className="side-bar">
<DatePicker
nameOne={"Start Date"}
nameTwo={"End Date"}
/>
<Button name={"Apply"}/>
</div>
</div>
<div className="graph-box">
<div id="wrapper">
<BarChart graphData={dataNew} />
</div>
<div className="side-bar">
<DatePickerWithButton
nameOne={"Start Date"}
nameTwo={"End Date"}
name={"Apply"}
/>
</div>
</div>
DatePickerWithButton: These used to be two separate components which I combined to make it easier to set the start,- and end dates.
import React, { useState } from "react";
import "./DatePickerWithButton.css";
import useURLSwitcher from "../../apiData/useURLSwitcher";
import useGetData from "../../apiData/useGetData";
import { format, parseISO } from "date-fns";
export const DatePickerWithButton = ({
nameOne,
nameTwo,
name,
}) => {
let tempStartDate ="";
let tempEndDate= "";
const [startDate, setStartDate] = useState();
const [endDate, setEndDate] = useState();
const {url} = useURLSwitcher(startDate, endDate)
const onClick = () => {
setStartDate(tempStartDate);
setEndDate(tempEndDate);
}
return (
<>
<div className="date-box">
<label className="styleDate">{nameOne}</label>
<input
className="datePicker"
type="date"
onChange={(e) => tempStartDate = e.target.value}
></input>
</div>
<div className="date-box">
<label className="styleDate">{nameTwo}</label>
<input
className="datePicker"
type="date"
onChange={(e) => tempEndDate= e.target.value}
></input>
</div>
<button className="button-28" onClick={()=> onClick()}>
{name}
</button>
{console.log("start date " + startDate, " end date "+ endDate)}
</>
);
};
The custom hook I made to fetch the data:
import { useState, useEffect } from "react";
import axios from "axios";
const config = {
"Content-Type": "application/json",
Accept: "application/json",
};
const useGetData = (url) => {
const [dataNew, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
const { data } = await axios({
url: url,
method: "GET",
data: null,
withCredentials: true,
headers: config,
}).catch((err) => setError(err));
console.log(url)
const newData = data.value.map((d) => {
const personId = d._personId;
const startDate =
d._startDate;
const endDate =
d._endDate;
return {
personId,
startDate,
endDate,
};
});
setData(newData);
setIsLoading(false);
}, [url]);
return {
dataNew,
isLoading,
error,
};
};
export default useGetData;
The second custom hook that should set the right url once selected with the DatePickerWithButton. Links have been pruned:
import { format, parseISO } from "date-fns";
import { isEmpty } from "lodash";
import useGetData from "./useGetData";
const useURLSwitcher = (setStartDate, setEndDate) => {
const startDate = setStartDate;
const endDate = setEndDate;
console.log(setStartDate);
console.log(setEndDate);
let url = "";
const RawThisYear = new Date();
const RawLastYear = new Date();
RawLastYear.setFullYear(RawLastYear.getFullYear() - 5);
const ThisYear = format(RawThisYear, "yyyy-dd-MM");
const LastYear = format(RawLastYear, "yyyy-dd-MM");
if (startDate === undefined && endDate === undefined) {
url = `http://192.168/{LastYear} lt ${ThisYear}`;
} else {
// if (startDate > endDate) {
// alert("Start date can't be greater than the end date!");
// } else {
if (startDate !== undefined && endDate !== undefined) {
url = `http://192.168/$filter={startDate}{endDate}`;
console.log(url)
}
}
console.log(url)
return {
url,
};
};
export default useURLSwitcher;
In general a component will re-render if its state or incoming props changes. The App.js is also a component.
At first glance, when you change a date in DatePickerWithButton, it does not seems updating any state/props that the parent App.js is aware of, hence no re-rendering.
The useURLSwitcher() just returns an object with url string.
The onClick function below only updates the DatePickerWithButton's local states.
const [startDate, setStartDate] = useState();
const [endDate, setEndDate] = useState();
const {url} = useURLSwitcher(startDate, endDate)
const onClick = () => {
setStartDate(tempStartDate);
setEndDate(tempEndDate);
}
The GraphChart component is a child component of App.js, but no state/props changes in App.js, hence, GraphChart does not re-render when only the DatePickerWithButton's local states changes.
This component displays calendar to the patients so that they can select the appointment day from the appointment days of doctor. Doctor appointment days are fetched from api. What i am trying to achieve is to disable all other weekdays days in the calendar except the doctor appointment days so that patients can only press one of the appointment days. i am using react-native-calendars library and date-fns-library for dates. However my app is freezing once while loop is being defined. What am i doing wrong here ? Also is there a better way of doing what i am trying to achieve?
import { View } from "react-native";
import React, { useEffect, useState } from "react";
import { Calendar, CalendarProps } from "react-native-calendars";
import startOfMonth from "date-fns/startOfMonth";
import endOfMonth from "date-fns/endOfMonth";
import isBefore from "date-fns/isBefore";
import addDays from "date-fns/addDays";
import format from "date-fns/format";
import setDay from "date-fns/setDay";
import api from "../../config/api";
import Layout from "../UI/Layout";
import RegularText from "../UI/Text/RegularText";
import { useAppSelector } from "../../store/hooks";
import { useRoute } from "#react-navigation/native";
import { AppointmentDaysScreenRouteProp } from "../../#types/navigation";
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
type weekday = typeof weekdays[number];
const CalendarComponent = () => {
const language = useAppSelector((state) => state.language.selected);
const [markedDates, setMarkedDates] = useState<CalendarProps["markedDates"]>(
{}
);
const [disabledledWeekdays, setDisbaledWeekdays] = useState<number[]>([]);
const route = useRoute<AppointmentDaysScreenRouteProp>();
const { doctorId } = route.params;
const [loading, setLoading] = useState(true);
let text = {
loading: "...Please wait",
};
if (language === "اردو") {
text = {
loading: "...لوڈ ہو رہا ہے",
};
}
useEffect(() => {
(async () => {
try {
const res = await api.get<{ appointmentDays: weekday[] }>(
`/appointments/appointmentDays/doctorId/${doctorId}`
);
const { appointmentDays } = res.data;
const disabledDays = weekdays
.filter((item) => !appointmentDays.includes(item))
.map((item) => weekdays.indexOf(item));
const now = new Date();
getDisabledDays(now.getMonth(), now.getFullYear(), disabledDays);
setDisbaledWeekdays(disabledDays);
} finally {
setLoading(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const getDisabledDays = (
month: number,
year: number,
daysIndexes: number[]
) => {
const now = new Date();
now.setFullYear(year, month);
const pivot = startOfMonth(now);
const end = endOfMonth(now);
const dates: CalendarProps["markedDates"] = {};
const disabled = { disabled: true, disableTouchEvent: true };
//THIS WHILE LOOP IS FREEZING MY APP
//IF I REMOVE THIS LOOP APP WORKS FINE
while (isBefore(pivot, end)) {
daysIndexes.forEach((day) => {
const copy = setDay(new Date(pivot), day);
dates[format(copy, "yyyy-MM-dd")] = disabled;
});
addDays(pivot, 7);
}
setMarkedDates(dates);
return dates;
};
return (
<Layout>
<View>
{loading ? (
<RegularText>{text.loading}</RegularText>
) : (
<Calendar
theme={{
textSectionTitleDisabledColor: "#d9e1e8",
}}
markedDates={markedDates}
onDayPress={(day) => {
console.log("dosomething with ", day);
}}
firstDay={1}
enableSwipeMonths={true}
disabledDaysIndexes={disabledledWeekdays}
onMonthChange={(date) => {
getDisabledDays(date.month - 1, date.year, disabledledWeekdays);
}}
/>
)}
</View>
</Layout>
);
};
export default CalendarComponent;
Looking at the documentation, I'm it seems that addDays(date, amount) is returning a new date and not modifying the value of pivot. Try doing pivot = addDays(pivot, 7)
I am using React's Context API to share data that most of my components need.
The Context is initially defined, but shortly receives data from the Firebase database (please see IdeaContext.tsx). I define the context in a functional component and the display component, which returns a small card based on the information received.
However, the component doesn't render when I start the development server with Yarn. Instead, in order to get it to render, I have to write console.log('something') inside the display component and then it suddenly re-renders. However, when I refresh the server, it again doesn't render.
How can I make my component render immediately (or at least after the context updates with the data from the database?)
Code:
Context Definition:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
function getIdeas() {
var arr: Array<Idea> = [];
ideasRef.on('value', (snapshot) => {
let items = snapshot.val()
snapshot.forEach( (idea) => {
const obj = idea.val()
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID
})
console.log(arr)
})
})
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect( ()=> {
console.log('getting info')
setIdeas(getIdeas())
}, [])
useEffect( () => {
console.log('idea change: ', ideas)
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
Displayer and Card Component
import React, { FC, ReactElement, useContext } from "react";
import IdeaCreator from "./IdeaCreator";
import { IdeaContext } from "./IdeaContext";
import { Idea } from "../t";
import { Link } from "react-router-dom";
const IdeaPost:React.FC<Idea> = ({title, keyID, description}):ReactElement => {
console.log('Received',title,description,keyID)
return (
<div className="max-w-sm rounded overflow-hidden shadow-lg">
<img
className="w-full"
src="#"
alt="Oopsy daisy"
/>
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2"> <Link to={"ideas/" + keyID} key= {keyID}> {title}</Link> </div>
<p className="text-gray-700 text-base">{description}</p>
</div>
</div>
);
};
const IdeaDisplay:FC<any> = (props:any):ReactElement => {
const { ideas, setIdeas } = useContext(IdeaContext)
console.log('Ideas in display: ', ideas)
console.log('test') //This is what I comment and uncommend to get it to show
return (
<div className="flex flex-wrap ">
{ideas.map((idea) => {
console.log(idea)
console.log('Sending',idea.title,idea.description,idea.keyID)
console.log(typeof idea.keyID)
return (
<IdeaPost
title={idea.title}
description={idea.description}
keyID = {idea.keyID}
key = {idea.keyID * 100}
/>
);
})}
</div>
);
};
export default IdeaDisplay;
Solution Code:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect(() => {
console.log("getting info");
const setup = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
setup()
}, []);
useEffect( () => {
console.log('idea change: ', ideas)
const updateDatabase = async () => {
await ideasRef.update(ideas)
console.log('updated database')
}
updateDatabase()
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
export {IdeaContext, IdeaContextProvider}
First of all you would need to use once and not on if you want to get the data only once. If you want to use a realtime listener you could send the setIdeas to your function. Also try to be carefull with async/away calls to the Firebase sdk. Your code could look like this:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import { ideasRef } from "./firebase";
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
let items = snapshot.val();
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr;
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext < IdeaContextType > IdeaContextDefaultValues;
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] =
useState < Array < Idea >> IdeaContextDefaultValues.ideas;
useEffect(() => {
console.log("getting info");
const getData = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
}, []);
useEffect(() => {
console.log("idea change: ", ideas);
}, [ideas]);
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { useGlobalContext } from "../context";
const SingleTvShow = () => {
const { id } = useParams();
const [details, setDetails] = useState({});
const { imgUrl } = useGlobalContext();
const getDetails = async (showId) => {
const resp = await fetch(
`https://api.themoviedb.org/3/tv/${showId}?api_key=API_KEY&language=en-US`
);
const data = await resp.json();
setDetails(data);
};
useEffect(() => {
getDetails(id);
}, [id]);
return (
<div>
{console.log(details)}
<img src={imgUrl + details.backdrop_path} alt="show" />
</div>
);
};
export default SingleTvShow;
https://drive.google.com/file/d/1FhYcJSqZiko0lJBoQdB2Gx5KyMWGR3dj/view?usp=sharing
I just started learning react, please help
Because the initial value of state details is empty {}. details only update when you call api success. Before that, details kept the value {}
You can check like this:
{details.backdrop_path && <img src={imgUrl + details.backdrop_path} alt="show" />}
As #Viet said - the initial value is an empty object {} that is being populated when you have fetched the data.
If you want to show the data to your UI only when the fetching process has been completed then i would suggest changing your return to this:
return (
<div>
{console.log(details)}
{details && <img src={imgUrl + details.backdrop_path} alt="show" /> }
</div>
);
This tells your function to only show the the img element if your state is not an empty object.
this is my react code here I am getting react poll using API but when I start working on handalchange For POST API request I need (PollId,userId and answer) I am getting userId through { const userId = isAutheticated() && isAutheticated().user._id; } but I do not understand how can I get PollId from my all polls, please help...!
import React, { useState, useEffect } from "react";
import Poll from "react-polls";
import "../../styles.css";
import { isAutheticated } from "../../auth/helper/index";
import { getPolls, postPoll } from "../helper/coreapicalls";
import { useParams } from "react-router-dom";
const MainPoll = () => {
const userId = isAutheticated() && isAutheticated().user._id;
const pollId = useParams();
const id = pollId._Id;
console.log(id);
const [polls, setPoll] = useState([]);
const [error, seterror] = useState(false);
// Setting answers to state to reload the component with each vote
const [pollAnswers, setPollAnswers] = useState([]);
useEffect(() => {
loadPoll();
}, []);
const loadPoll = () => {
getPolls().then((data) => {
if (data.error) {
seterror(data.error);
} else {
setPoll(data);
console.log(data);
}
});
};
// Handling user vote
// Increments the votes count of answer when the user votes
const handalchange = () => {
postPoll();
console.log("hello");
};
return (
<div className="">
<div className="container my-5">
<h1 className="blog_heading my-3">Poll's of the Day</h1>
<div className="row">
{polls.reverse().map((poll, index) => (
<div className="col-lg-4 col-12 poll_border" key={index}>
<Poll
noStorage
question={poll.question}
answers={Object.keys(poll.options).map((key) => {
return {
option: key,
votes: poll.options[key].length,
};
})}
onVote={handalchange}
className="mb-2"
/>
</div>
))}
</div>
</div>
</div>
);
};
export default MainPoll;
my frontend image -
Here I have 5 polls , so I can not get PollId from useParams ...! so how can I get..?
Your component seems to represent list of polls, not any specific poll. So if you have an array of polls instead of one poll, than you have multiple ids instead of the single one.
You can get them by mapping your polls array like that:
const pollIds = polls.map((poll) => poll.id); // or any other prop that stores id