My react-native-app freezes when i use while loop - javascript

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)

Related

Can't add some data from API request to React component

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

Render Item in Agenda using database data - React Native

I'm trying to show the items I get from my database in the calendar, everything is working fine (maybe not), but in short I got the data from the database with an array and then I converted it to an object (because the calendar only accepts objects), but it doesn't show anything and it doesn't give an error either
import React, { useEffect, useState } from 'react'
import { StyleSheet, View, Text } from 'react-native'
import { LocaleConfig, Agenda } from 'react-native-calendars'
import DateTimePicker from 'react-native-modal-datetime-picker';
import { getAuth } from 'firebase/auth';
import { getDatabase, ref, onValue, set, push, get, child } from 'firebase/database';
const Calendario = () => {
const dbRef = ref(getDatabase());
const data = []
var obj = {}
// getting data from the database
useEffect(() => {
getInDB()
} ,[])
const getInDB = () => {
get(child(dbRef, 'users/' + app.currentUser.uid)).then((snapshot) => {
snapshot.forEach(childsnap => {
let dateD = childsnap.child("date").val()
let titleD = childsnap.child("title").val()
let dtsD = childsnap.child("details").val()
// "yyyy-MM-dd": [{any: "whatever", any2: "whatever"}],
data.push({
[dateD] : [{ title: titleD, details: dtsD }],
});
})
obj = Object.assign({}, ...data)
console.log(obj)
})
}
const renderItem = (item) => {
return(
<View style={styles.itemContainer}>
<Text style={styles.textInf}>{item.title}</Text>
<Text style={styles.textInf}>{item.details}</Text>
</View>
)
}
return (
<>
<Agenda
items={obj}
renderEmptyDate={() => {
return <View />;
}}
renderEmptyData={() => {
return <View />;
}}
selected={new Date()}
minDate={null}
renderItem={renderItem}
markingType="custom"
/>
</>
}
You need to use state and set it or otherwise your component will not be rerendered with the new data.
Furthermore, the Agenda component expects an object. By using data as an array and the spread operator, we won't get the desired result.
You can implement this correctly as follows.
...
const [obj, setObj] = useState({});
...
const getInDB = () => {
get(child(dbRef, 'users/' + app.currentUser.uid)).then((snapshot) => {
const temp = {}
snapshot.forEach(childsnap => {
let dateD = childsnap.child("date").val()
let titleD = childsnap.child("title").val()
let dtsD = childsnap.child("details").val()
Object.assign(temp, {dateD: [{ title: titleD, details: dtsD }]})
})
setObj(temp)
})
}
I have implemented a little snack.

React graph component doesn't re-render on url change present in useEffect

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.

airbnb/react-dates DayPickerRangeController set visible month on some function call

Is there way to set DayPickerRangeController custom visible month after component rendered?
I have 'handleMonthChange' function which I want to change visible month when this function is called. I try to set 'initialVisible' month but it is not working.
import { useState } from "react";
import moment from "moment";
import { DayPickerRangeController } from "react-dates";
import "react-dates/initialize";
import "react-dates/lib/css/_datepicker.css";
const Date = (props) => {
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const [focusedInput, setFocusedInput] = useState("startDate");
const [initialMonth, setInitialMonth] = useState(moment("01-01-2021"));
const handleDatesChange = ({ startDate, endDate }) => {
setStartDate(moment(startDate, "MMM DD, YYYY"));
setEndDate(moment(endDate, "MMM DD, YYYY"));
};
const handleMonthChange = () => {
console.log("month change");
setInitialMonth(moment("01-06-2021"));
};
return (
<div>
<DayPickerRangeController
onDatesChange={handleDatesChange}
focusedInput={focusedInput}
startDate={startDate}
endDate={endDate}
numberOfMonths={2}
initialVisibleMonth={() => initialMonth}
/>
<button onClick={handleMonthChange}>Change Visible Month</button>
</div>
);
};
export default Date;
I found workaround solution. Unmounting component and then render with new initialMonth
import { useState } from "react";
import moment from "moment";
import { DayPickerRangeController } from "react-dates";
import "react-dates/initialize";
import "react-dates/lib/css/_datepicker.css";
const Date = (props) => {
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const [focusedInput, setFocusedInput] = useState("startDate");
const [initialMonth, setInitialMonth] = useState(moment("01-01-2021"));
const handleDatesChange = ({ startDate, endDate }) => {
setStartDate(moment(startDate, "MMM DD, YYYY"));
setEndDate(moment(endDate, "MMM DD, YYYY"));
};
const handleMonthChange = () => {
setInitialMonth(null)
setTimeout(() => setInitialMonth(moment("01-06-2021")), 0);
};
if(!initialMonth) return <div>Loading...</div>
return (
<div>
<DayPickerRangeController
onDatesChange={handleDatesChange}
focusedInput={focusedInput}
startDate={startDate}
endDate={endDate}
numberOfMonths={2}
initialVisibleMonth={() => initialMonth}
/>
<button onClick={handleMonthChange}>Change Visible Month</button>
</div>
);
};
export default Date;

change style of calendar component in react js

I wanted to change my calendar component , from an old one to new one , they both exist on the website , but the new one isn't working, I want to make it work , when the user choose a date, it reacts with the website
this is my old one :
This is the Code :
import 'd3-transition';
import React, { Component } from 'react';
import { connect } from "react-redux";
import { setDatePrecision, nextDate, previousDate, loadWords, loadArticles } from "../redux/actions";
class DaySelector extends Component {
state = {
datePrecision: "day",
selectedDate: new Date()
};
render() {
const rthis = this.props;
const prev = () => {
rthis.previousDate();
this.props.loadWords();
this.props.loadArticles();
};
const next = () => {
rthis.nextDate();
this.props.loadWords();
this.props.loadArticles();
}
const dayPrecision = () => {
rthis.setDatePrecision("day");
this.props.loadWords();
this.props.loadArticles();
}
const monthPrecision = () => {
rthis.setDatePrecision("month");
this.props.loadWords();
this.props.loadArticles();
}
const current_date = this.props.selectedDate;
const datePrecision = this.props.datePrecision;
const year = current_date.getFullYear();
const month = current_date.getMonth() + 1;
const day = current_date.getDate();
return (
<div>
<a href="#day" onClick={dayPrecision}>day </a>
<a href="#month" onClick={monthPrecision}>month </a>
<a href="#prev" onClick={prev}><<<</a>
{datePrecision === "day" ? String(day).padStart(2, "0") + "/" : ""}{String(month).padStart(2, "0")}/{year}
<a href="#next" onClick={next}>>>></a>
</div>
);
}
}
const mapStateToProps = state => {
return {
selectedDate: state.wordsReducer.selectedDate,
datePrecision: state.wordsReducer.datePrecision,
}
};
export default connect(mapStateToProps, { setDatePrecision, nextDate, previousDate, loadWords, loadArticles })(DaySelector);
I want to replace it with this new Calendar :
this is the code of this component :
import React, { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';
const MyCalendar = () => {
const [date, setDate] = useState(new Date());
const onChange = (date) => setDate(date);
return (
<div>
<h5 className="card-title mb-0">Calendar</h5>
<Calendar onChange={onChange} value={date} />
</div>
);
};
export default MyCalendar;
Those components are both on the website but I could not make it dynamically work,
I've install it from Here
Thank you !
Looks like you are missing to pass the prop from onChange, try:
<Calendar onChange={(value, event) => onChange(value)} value={date} />
or try:
<Calendar onChange={(value, event) => setDate(value)} value={date} />

Categories