How can I modify this code so that render waits for Promise Items? - node.js

I am new to React and I load all the data from my database initially on page load but there is info I need to find in an array and apparently it isn't instant. What do I need to do to make sure the render method only renders the objects when the object promises have resolved?
I haven't tried much... I'm really stuck here.
This seems different than the other problems I've read here because I load a bunch on info in the beginning just fine but I need to call some team information every time a function is called so it isn't as simple as loading it once because the object i need is always different.
This code is the main issue. I also included the full file below:
I did some modification to the code in a edit: I realized that I just need to call the opponent team because I have the player team already.
if (team.id === game.team_1) {
var redTeam = team;
// set blueTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_2}')
} else {
var blueTeam = team;
// set redTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_1}')
}
Full file:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Async from 'react-promise'
import { withFirebase } from '../Firebase';
// import * as ROUTES from '../../constants/routes';
import { Container, Image, Spinner, Col, Row, Card, Accordion, Button } from 'react-bootstrap'
class PlayerGameList extends Component {
constructor(props) {
super(props);
this.state = {
loadingTeams: false,
loadingSchedule: false,
teams: [],
schedule: []
};
}
componentDidMount() {
this.setState({
loadingTeams: true,
loadingSchedule: true,
});
this.unsubscribe = this.props.firebase
.teams()
.where('players', 'array-contains', '-LXkkB7GNvYrU4UkUMle')
.onSnapshot(snapshot => {
let teams = [];
snapshot.forEach(doc =>
teams.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
teams,
loadingTeams: false,
});
});
this.unsubscribe2 = this.props.firebase
.schedule()
.onSnapshot(snap => {
let schedule = [];
snap.forEach(doc =>
schedule.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
schedule,
loadingSchedule: false,
});
});
}
componentWillUnmount() {
this.unsubscribe();
this.unsubscribe2();
}
render() {
const { teams, schedule, loadingTeams, loadingSchedule } = this.state;
return (
<div>
<h2>Games</h2>
{loadingTeams && loadingSchedule && <div colSpan="12"><Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner></div>}
{/* CONTENT */}
<Container fluid>
<Row>
{getTeams({ teams, schedule })}
</Row>
</Container>
</div >
);
}
}
function getTeams({ teams, schedule }) {
if (!teams) {
return null;
}
if (!teams.length) {
return null;
} else {
return teams.map(team => getGames({ team, schedule }))
}
}
function getGames({ team, schedule }) {
schedule.sort((a, b) => (a.time > b.time) ? -1 : 1)
if (!schedule) {
return null;
}
if (!schedule.length) {
return null;
} else {
return schedule.map(game => guts({ team, game }));
}
}
function guts({ team, game }) {
const image = {
height: '25px',
width: '25px'
}
if (team.id === game.team_1) {
var redTeam = team;
// set blueTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_2}')
} else {
var blueTeam = team;
// set redTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_1}')
}
if (game.team_1 === team.id || game.team_2 === team.id) {
var time = new Date(game.time.seconds * 1000);
var dateFormat = require('dateformat');
var finalTime = dateFormat(time, 'ddd mmm dd, h:MM tt')
return (
<Col lg='4' md='6' sm='12' key={game.uid} style={{ marginBottom: '15px' }}>
<Card>
<Card.Body>
<Row>
<Image src={team.logo} style={image} roundedCircle />
<p>{team.name}</p>
<div style={{ height: '25px', width: '25px', backgroundColor: 'red' }}></div>
</Row>
<Row>
<Image src={team.logo} style={image} roundedCircle />
<p>{team.name}</p>
<div style={{ height: '25px', width: '25px', backgroundColor: 'blue' }}></div>
</Row>
<Row>
<div>
{finalTime}
</div>
</Row>
</Card.Body>
<Accordion>
<Card style={{ margin: '0', padding: '0' }}>
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="0">
Show Match IDs
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>{game.match_id}</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</Card>
</Col>
);
}
}
export default withFirebase(PlayerGameList);
The items all load blank then a few seconds later all the console logs come through with the array objects. When I tell it to await the program just throws an error.

Related

Data not reloading until refresh reactJS

I am currently building a website with two views in which a user can toggle between both views (images seen below) On both views I am allowing a user to update a database and both views display data regarding the state of that database. However, when I update the data on one toggle view of the data and switch to the other view, I can't see the updated data until I refresh.
I am wrapping my react application around this user context provider shown below
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState();
const [courseList, setCourseList] = useState();
const [semesterList, setSemesterList] = useState();
const [userMajor, setMajor] = useState();
const checkContext = useCallback(() => {
axios
.get("/auth/check", { withCredentials: true })
.then(({ data }) => {
if (data.auth) {
setIsAuthenticated(true);
setUser(data.user);
setCourseList(data.courseList);
setSemesterList(data.semesterList);
setMajor(data.major);
setIsLoading(false);
} else {
setIsAuthenticated(false);
setMajor(undefined);
setCourseList(undefined);
setSemesterList(undefined);
setUser(undefined);
setIsLoading(false);
}
})
.catch(() =>
console.log(
"Something went wrong while trying to fetch your auth status."
)
);
}, []);
useEffect(() => {
checkContext();
}, [checkContext]);
return (
<UserContext.Provider
value={{
isLoading,
isAuthenticated,
user,
courseList,
semesterList,
userMajor,
checkContext,
}}
>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
This is where I toggle between views
const Home = () => {
const [selected, setSelected] = useState(false);
const handleChange = () => {
setSelected(!selected);
setSelected(!selected);
};
let view;
if (!selected) {
view = <CardView />;
} else {
view = <SemesterView />;
}
return (
<>
<ToggleButton
className="toggler"
value="toggle"
selected={selected}
onChange={handleChange}
>
<ViewModuleIcon />
</ToggleButton>
{view}
</>
);
};
export default Home;
For keeping this shorter, here is one of views in which I update the database using a checkbox form.
const Home = () => {
const { courseList } = useContext(UserContext);
const initialMajorState = {
majorName: "",
majorCode: "",
majorComponentFamilies: [],
};
const [majorData, setMajorData] = useState(initialMajorState);
const [checkedCourses, setCheckedCourses] = useState(courseList);
const [componentLimit, setComponentLimit] = useState([]);
const [componentFamilyLimit, setComponentFamilyLimit] = useState([]);
const [isLoadingData, setIsLoadingData] = useState(true);
/**
* updates the checked courses, the database, as well as the limits for both components and componentfamilys
* #param checkedCourse is the course that is being checkedd
* #param checkedComponentName is the course family name that is being updated
* #param componentIndex is the course component to which the course belongs to
* #param componentFamilyIndex is which componentFamily the course belongs to
*/
const handleOnChange = (
checkedCourse,
checkedComponentName,
componentIndex,
componentFamilyIndex
) => {
// copy the componentLimit array
const updatedComponentLimit = [...componentLimit];
// copy the componentFamilyLimit array
const updatedComponentFamilyLimit = [...componentFamilyLimit];
const updatedCheckedCourses = checkedCourses?.includes(checkedCourse)
? checkedCourses?.filter((name) => name !== checkedCourse)
: [...(checkedCourses ?? []), checkedCourse];
let difference = updatedCheckedCourses.filter(
(x) => !checkedCourses.includes(x)
);
// This means that we added the course
if (difference.length > 0) {
// Add the course to the databas
UserCourseService.add(checkedCourse);
// Add the course to the checked list for that component
updatedComponentLimit[componentIndex].checked.push(checkedCourse);
// if that component is now satisfied then we want to add that to the component family's satisfied components
if (
updatedComponentLimit[componentIndex].checked.length >=
updatedComponentLimit[componentIndex].limit
) {
updatedComponentFamilyLimit[
componentFamilyIndex
].checkedComponentFamily.push(checkedComponentName);
}
}
// if the difference length is none then that means we unchecked a box
else {
// Delete the course from our database
UserCourseService.delete(checkedCourse);
// Add the course to the updatedComponentLimit array at the component index where course lies
updatedComponentLimit[componentIndex].checked.pop(checkedCourse);
// Pop that component family (pop will return null if not found anyways)
updatedComponentFamilyLimit[
componentFamilyIndex
].checkedComponentFamily.pop(checkedComponentName);
}
// set the componentFamilyLimit array and componentLimit array and checked courses
setComponentLimit(updatedComponentLimit);
setComponentFamilyLimit(updatedComponentFamilyLimit);
setCheckedCourses(updatedCheckedCourses);
};
/**
* returns a boolean determining if a checkbox is disabled or not
* #param course deals with which course we are checking should be disabled or not
* #param componentIndex deals with which component we are looking at
* #param componentFamilyIndex deals with which compoentFamily we are looking at
*/
const isDisabled = (course, componentIndex, componentFamilyIndex) => {
// if the component family limit has been reached then just return if the checkbox has already been checked
if (
componentFamilyLimit[componentFamilyIndex].checkedComponentFamily
.length >= componentFamilyLimit[componentFamilyIndex].limit
)
return componentLimit[componentIndex].checked.indexOf(course) === -1;
// if the component family limit has not been reached then check if the component itself has been meet and
// if the element has been checked already
else {
return (
componentLimit[componentIndex].checked.length >=
componentLimit[componentIndex].limit &&
// If not already checked
componentLimit[componentIndex].checked.indexOf(course) === -1
);
}
};
/**
* gets the data after searching for a course in the searchbar from the backend. Initializes the limit arrays and checked courses
* #param query is the name of the major we are getting data from in the backend
*/
const getData = async (query) => {
// Initializing the state for our global hooks in this function
var newCheckedComponentList = [];
var newCheckedComponentFamilyList = [];
// gets the response data in regards to the major we are interested in from the backend
MajorDataService.find(query)
.then((response) => {
// sets the major data to the response data
setMajorData(response.data);
// if the data exists then we want to initialize the other stuff
if (response.data.majorComponentFamilies) {
// Looping through the component families
response.data.majorComponentFamilies.forEach((componentFamily) => {
// gets the limit from the component family
var componentFamilyLimit = parseInt(
componentFamily.required_num_components
);
var newComponentFamilyList = [];
// Loops through each of the components in each component family
componentFamily.component_list.forEach((component) => {
// courselimit on each component
var courseLimit = parseInt(component.required_num_courses);
// the course list for a component
var newUserCourseList = [];
// Loops through all of the courses in each component
component.course_list.forEach((course) => {
// if the courselist includes the course then add that to the course list
if (courseList.includes(course)) {
newUserCourseList.push(course);
}
});
// Add to the checked array for components
newCheckedComponentList.push({
checked: newUserCourseList,
limit: courseLimit,
});
if (newUserCourseList.length === courseLimit) {
newComponentFamilyList.push(component.component_name);
}
});
newCheckedComponentFamilyList.push({
checkedComponentFamily: newComponentFamilyList,
limit: componentFamilyLimit,
});
});
setComponentLimit(newCheckedComponentList);
setComponentFamilyLimit(newCheckedComponentFamilyList);
setIsLoadingData(false);
}
})
.catch((e) => {
console.log(e);
});
};
const renderCourseRequirements = (
requiredCourses,
componentIndex,
componentFamilyIndex,
componentFamilyName
) => {
return (
<Grid container spacing={1} key={[uuid()]}>
{requiredCourses.map((course, index) => {
return (
<Grid key={[uuid()]} item xs={6} lg={4} xl={4}>
<Card
key={[uuid()]}
className={
checkedCourses.includes(course)
? "CourseCardCompleted"
: "CourseCard"
}
variant="outlined"
>
<CardHeader
key={[uuid()]}
action={
<Checkbox
name={course}
value={course}
className="header"
disabled={isDisabled(
course,
componentIndex,
componentFamilyIndex
)}
checked={checkedCourses.includes(course)}
onChange={() =>
handleOnChange(
course,
componentFamilyName,
componentIndex,
componentFamilyIndex
)
}
/>
}
title={
<Typography
key={[uuid()]}
className="CourseTitle"
color="textSecondary"
gutterBottom
>
{course}
</Typography>
}
></CardHeader>
<CardContent key={[uuid()]} className="content"></CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
var componentCount = 0;
const renderComponents = (components, componentFamilyIndex) => {
return (
<Grid
container
spacing={1}
direction="column"
alignItems="center"
justifyContent="center"
key={[uuid()]}
>
{components.map((component, index) => {
var temp_index = componentCount;
componentCount = componentCount + 1;
return (
<Grid key={[uuid()]} item className="grid" xs={12} lg={4} xl={2}>
<Card
key={[uuid()]}
className={
componentLimit[temp_index].checked.length >=
componentLimit[temp_index].limit
? "ComponentCardCompleted"
: "ComponentCard"
}
variant="outlined"
>
<CardContent key={[uuid()]}>
<Typography
key={[uuid()]}
className="ComponentTitle"
color="textSecondary"
gutterBottom
>
{component.component_name}
</Typography>
<Typography
key={[uuid()]}
className="pos"
color="textSecondary"
>
Required # of Courses: {component.required_num_courses}
</Typography>
{renderCourseRequirements(
component.course_list,
temp_index,
componentFamilyIndex,
component.component_name
)}
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
const renderComponentFamilies = (majorComponentFamilies) => {
return (
<Masonry className="masonry" columns={3} spacing={2} key={[uuid()]}>
{majorComponentFamilies.map((componentFamily, index) => {
if (componentFamily.component_list.length > 1)
return (
<Card
key={[uuid()]}
className={
componentFamilyLimit[index].checkedComponentFamily.length >=
componentFamilyLimit[index].limit
? "ComponentFamilyCardCompleted"
: "ComponentFamilyCard"
}
variant="outlined"
>
<CardContent key={[uuid()]}>
<Typography
key={[uuid()]}
className="ComponentFamilyTitle"
color="textSecondary"
gutterBottom
>
{componentFamily.component_family_name}
</Typography>
<Typography
key={[uuid()]}
className="pos"
color="textSecondary"
>
Required # of Components :{" "}
{componentFamily.required_num_components}
</Typography>
{renderComponents(componentFamily.component_list, index)}
</CardContent>
</Card>
);
return (
<div key={[uuid()]}>
{renderComponents(componentFamily.component_list, index)}
</div>
);
})}
</Masonry>
);
};
return (
<div className="container home">
<SearchBar
key={[uuid()]}
placeholder="Enter Major Name ..."
data={MajorNames}
onChange={(value) => getData(value)}
/>
<div className="mui-grid">
{Array.isArray(majorData.majorComponentFamilies) &&
majorData.majorComponentFamilies.length > 0 &&
!isLoadingData ? (
<>
<h1>{majorData.majorName} </h1>
{renderComponentFamilies(majorData.majorComponentFamilies)}
</>
) : (
<></>
)}
</div>
</div>
);
};

React Typescript project .map function working only one time after compilation

I want to create a frontend for my app using React and TypeScript.
I am gathering data from my backend (simple DRF app) and fetching it using Axios and pushing it into separate arrays.
const myApi = axios.create({
baseURL: 'http://127.0.0.1:8000/app/',
headers: {
"Content-type": "application/json"
}
})
let idArr:number [] = new Array()
let titleArr:string [] = new Array()
let contentArr:string [] = new Array()
myApi.get('/getallarticles').then( res => {
for(let elem in res.data)
{
idArr.push(res.data[elem].id)
titleArr.push(res.data[elem].title)
contentArr.push(res.data[elem].content)
}
})
The app contains a page named feed into which I parse the id Array.
function App() {
console.log(idArr)
return (
<Box>
<Navbar/>
<Grid alignItems={"center"} justifyContent={"center"} marginLeft={'15%'} marginRight={'15%'}>
<Stack spacing={2} margin={2}>
<Box id='res_viewer'>
<Feed content={"CONTENT FROM MAIN"} id={10} title={"Sherlock Holmes"} total={5} idArr={idArr}/>
</Box>
</Stack>
</Grid>
</Box>
);
}
Then in my page Feed I copy the ActionCard (from Mui5) and the problem is that all the Cards show only if I recompile the project. It only works one time, and I have no idea why. Moreover if I put the console.log and alert into the Feed the console.log would show an array every time, while Alert only the first time. Anyone have any idea what the issue might be?
const Feed = ({ id, title, content, total, idArr }: { id: number; title: string; content: string; total: number; idArr:Array<number>}) => {
console.log(idArr)
alert(idArr)
return(
<div>
{idArr.map(customId =>
<Card key={customId} variant="outlined" sx={{ margin: 0 }}>
<CardActionArea>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{customId}
</Typography>
<Typography variant="body2" color="text.secondary">
{content}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button style={{
color: "#ff0000",
}} size="large" startIcon={<DeleteForever />}>
DELETE
</Button>
<Button style={{
color: "#68ee06",
}} size="large" startIcon={<Edit />}>
EDIT
</Button>
</CardActions>
</Card>
)}
</div>
)
}
export default Feed
I suspect the asynchronous nature of the call to your API is what's giving this behavior. When making calls to an API for data, it's common to use a useEffect hook that updates a state. This ensures that the UI is in sync with the data. Try rewriting as follows:
const myApi = axios.create({
baseURL: 'http://127.0.0.1:8000/app/',
headers: {
"Content-type": "application/json"
}
})
const [idArr, setIdArr] = useState([] as number[]);
const [titleArr, setTitleArr] = useState([] as string[]);
const [contentArr, setContentArr] = useState([] as string[]);
useEffect(() => {
myApi.get('/getallarticles').then(res => {
const newIds: number[] = [];
const newTitles: string[] = [];
const newContents: string[] = [];
for (let elem in res.data) {
newIds.push(res.data[elem].id);
newTitles.push(res.data[elem].title);
newContents.push(res.data[elem].content);
}
setIdArr(newIds);
setTitleArr(newTitles);
setContentArr(newContents);
});
}, []);

delete from firebase real time database

it makes a flat list of rooms and inside those rooms are messages. So I want to long press on a specific room in the list, and when i do this, I want to delete a room.
onLongPress={() => this.roomsRef.remove({item})}
I thought the above would work, but it fails. I put it in the render row part. It comes up saying Reference.remove failed. first argument must be a valid function. Like inside these rooms it has messages and I think thats where im going wrong but im not sure how to go about it. thank you
'use strict'
import React, { Component, useEffect } from 'react';
import { Text, Image, TextInput, TouchableHighlight, StatusBar, FlatList, View, TouchableOpacity, Dimensions, LogBox, Alert } from 'react-native';
// import firebaseApp from './firebaseConfig.js';
import firebaseApp from '../config/firebasekeys';
import {SafeAreaView} from 'react-native-safe-area-context'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
import {Avatar} from 'react-native-elements'
import { color } from 'react-native-reanimated';
const DeviceHeight = Dimensions.get('screen').height
const DeviceWidth = Dimensions.get('screen').width
class Rooms extends Component {
static navigationOptions = {
title: 'Rooms',
};
constructor(props) {
super(props);
var firebaseDB = firebaseApp.database();
this.roomsRef = firebaseDB.ref('rooms'); // rooms collection
this.state = {
rooms: [],
newRoom: ''
}
}
// maybe its this.roomsRef.remove(item.name) or maybe its name idk hmmm
componentDidMount() {
LogBox.ignoreLogs(['VirtualizedLists should never be nested']); // doing this to ignore the log box error for scroll view
this.listenForRooms(this.roomsRef);
}
listenForRooms(roomsRef) {
roomsRef.on('value', (dataSnapshot) => {
var roomsFB = [];
dataSnapshot.forEach((child) => {
roomsFB.push({
name: child.val().name,
key: child.key
});
});
this.setState({ rooms: roomsFB });
});
}
addRoom() {
if (this.state.newRoom === '') {
return
}
this.roomsRef.push({ name: this.state.newRoom });
this.setState({ newRoom: '' });
}
openMessages(room) {
this.props.navigation.navigate("Messages", {roomKey: room.key, roomName: room.name});
}
renderRow(item) {
return (
<TouchableOpacity
onPress={() => this.openMessages(item)}
onLongPress={() => this.roomsRef.remove({name:item})}
>
<Text style={{
color: 'orange',
fontSize: 22,
}}>{item.name}</Text>
</TouchableOpacity>
)
}
render() {
return (
<SafeAreaView style={styles.container}>
<StatusBar style="auto"/>
<KeyboardAwareScrollView>
<View>
<View >
<TextInput>
}}
placeholder={"enter chat name"}
onChangeText={(text) => this.setState({newRoom: text})}
value={this.state.newRoom}
/>
<TouchableHighlight
style={{
marginRight: 20
}}
onPress={() => this.addRoom()}
>
>
</TouchableHighlight>
</View>
<View>
<FlatList
data={this.state.rooms}
renderItem={({item}) => (this.renderRow(item)
)}
/>
</View>
{/* <View>
<FlatList
data={this.state.rooms}
renderItem={({item}) => (this.renderRow(item)
)}
/>
</View> */}
</View>
</KeyboardAwareScrollView>
</SafeAreaView>
);
}
}
export default Rooms;
I recommend keeping the reference docs for the API handy as you're developing, as there are quite a few problems you can catch that way. For example, the Reference.remove() method doesn't take an item as its argument (in fact: it typically is called without arguments).
To delete a specific node, you should build a reference to that node, and then call remove() on the reference:
roomsRef.child("keyOfRoomToRemove").remove()

How to make GridList face vertically in One Row with React and materialUI on Mobile

How do I make Materia-UI GridList Face vertically in One Row on Mobile?
My GridList is two in a row which is what I wanted but when I inspect it on mobile it still shows two in a row instead of One. that means it's not responsive, How do I make it responsive?
this is my Events Component.
import React from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import GridList from "#material-ui/core/GridList";
import GridListTile from "#material-ui/core/GridListTile";
import GridListTileBar from "#material-ui/core/GridListTileBar";
import ListSubheader from "#material-ui/core/ListSubheader";
mport IconButton from "#material-ui/core/IconButton";
import Button from "#material-ui/core/Button";
import { withStyles } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
const useStyles = (theme) => ({
root: {
display: "flex",
flexWrap: "wrap",
justifyContent: "space-around",
overflow: "hidden",
backgroundColor: theme.palette.background.paper,
},
gridList: {
width: 1000,
height: 950,
},
icon: {
color: "rgba(255, 255, 255, 0.54)",
},
});
class EventsList extends React.Component {
constructor(props) {
super(props);
this.state = { events: [] };
}
componentDidMount() {
axios
.get("http://localhost:9000/events/")
.then((response) => {
this.setState({ events: response.data });
})
.catch(function (error) {
console.log(error);
});
}
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<GridList cellHeight={330} className={classes.gridList} spacing={8}>
<GridListTile key="Subheader" cols={2} style={{ height: "auto" }}>
<ListSubheader component="div">December</ListSubheader>
</GridListTile>
{this.state.events.map((tile) => (
<GridListTile key={tile.eventImage}>
<img src={tile.eventImage} alt={tile.title} />
<GridListTileBar title={tile.title} titlePosition="top" />
<GridListTileBar
paragraph={tile.description}
actionIcon={
<IconButton
aria-label={`info about ${tile.title}`}
className={classes.icon}
></IconButton>
}
/>
</GridListTile>
))}
</GridList>
</div>
);
}
}
export default withStyles(useStyles)(EventsList);
this is the List of my events displayed by GridList on Mobile
but something like this is what I want to see when I am on Mobile.
Since you are using class based component, you can use withWidth to conditionally set cols value to GridList.
See working example here in codesandbox
Example code snippet
class TitlebarGridList extends Component {
render() {
const { classes, width } = this.props; // <---- see here
let columns = width === "xs" || width === "sm" ? 1 : 2;
console.log("=========>", width);
return (
<div className={classes.root}>
<GridList cellHeight={200} className={classes.gridList} cols={columns}> {/* //<---- see here */}
<GridListTile key="Subheader" cols={3} style={{ height: "auto" }}>
<ListSubheader component="div">December</ListSubheader>
</GridListTile>
{tileData.map(tile => (
<GridListTile key={tile.title}>
<img className={classes.image} src={tile.img} alt={tile.title} />
</GridListTile>
))}
</GridList>
</div>
);
}
}
TitlebarGridList.propTypes = {
classes: PropTypes.object.isRequired
};
export default compose(
withStyles(styles, {
name: "TitlebarGridList"
}),
withWidth() //<---- see here
)(TitlebarGridList);
Note: withWidth will be deprecated sometime in the future, so consider to use useMediaQuery hook (in that case you have to switch to functional component).

How to paginate a list of posts belonging to a specific category in GatsbyJS

I have developed a blog with Gatsby JS and I managed to add categories to each markdown file so that I can create pages by querying a specific category and list all the posts related to that category.
Now, I'm trying to add pagination to avoid an infinite list of posts inside each category page.
I have been following the official guide here: https://www.gatsbyjs.org/docs/adding-pagination/
And this is the code I came up with:
gatsby-node.js
const path = require('path')
const _ = require("lodash")
const { createFilePath } = require("gatsby-source-filesystem")
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions
const articleTemplate = path.resolve(`src/templates/article.js`)
const categoryTemplate = path.resolve("src/templates/category.js")
return new Promise((resolve, reject) => {
resolve(
graphql(
`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 2000
) {
edges {
node {
html
id
frontmatter {
path
title
categories
}
}
}
}
}
`).then(result => {
if (result.errors) {
reject(result.errors)
}
const articles = result.data.allMarkdownRemark.edges
const articlesPerPage = 6
const numPages = Math.ceil(articles.length / articlesPerPage)
//Creating a page for each article
articles.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: articleTemplate,
//context: {}, // additional data can be passed via context
})
})
// Categories pages:
let categories = []
// Iterate through each article, putting all found categories into `categories`
_.each(articles, edge => {
if (_.get(edge, "node.frontmatter.categories")) {
categories = categories.concat(edge.node.frontmatter.categories)
}
})
Array.from({ length: numPages }).forEach((category, _, i) => {
createPage({
path: i === 0 ? `/${_.kebabCase(category)}/` : `/${_.kebabCase(category)}/${i + 1}`,
component: categoryTemplate,
context: {
limit: articlesPerPage,
skip: i * articlesPerPage,
category,
},
})
})
})
)
})
/templates/categories.js
import React from "react"
import PropTypes from "prop-types"
import Layout from '../layouts/layout'
import ArticleCard from '../components/articles/articlecard'
// Components
import { Link, graphql } from "gatsby"
const _ = require("lodash")
const Categories = ({ pageContext, data }) => {
const { category } = pageContext
const { edges } = data.allMarkdownRemark
return (
<Layout>
<section class="hero is-info is-medium has-text-centered">
<div class="hero-body">
<div class="container">
<h1 class="title is-top">
{category}
</h1>
</div>
</div>
</section>
<div class="section">
<div class="container">
<div class="columns is-multiline">
{edges.map(({ node }) => {
const { path, title, date } = node.frontmatter
return (
<div class="column is-half">
<div class="card">
<div class="card-header">
<p class="card-header-content">{date}</p>
</div>
<div class="card-content">
<Link to={_.kebabCase(category)}><span class="tag is-success has-padding">{category}</span></Link>
<Link to={path}>
<h2 class="title is-4">{title}</h2>
</Link>
</div>
<div class="card-footer">
<div class="card-footer-item"><Link to={path}><div class="button is-success is-inverted is-fullwidth">Read</div></Link></div>
<div class="card-footer-item"><Link to={path}><div class="button is-info is-inverted is-fullwidth">Share on Linkedin</div></Link></div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</Layout>
)
}
Categories.propTypes = {
pageContext: PropTypes.shape({
category: PropTypes.string.isRequired,
}),
data: PropTypes.shape({
allMarkdownRemark: PropTypes.shape({
totalCount: PropTypes.number.isRequired,
edges: PropTypes.arrayOf(
PropTypes.shape({
node: PropTypes.shape({
frontmatter: PropTypes.shape({
path: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
}),
}),
}).isRequired
),
}),
}),
}
export default Categories
export const pageQuery = graphql`
query($skip: Int!, $limit: Int!, $category: String) {
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { categories: { in: [$category] } } }
limit: $limit
skip: $skip
) {
totalCount
edges {
node {
frontmatter {
title
path
date(formatString: "MMMM DD, YYYY")
}
}
}
}
}
`
This does not work and it is now throwing the error: error gatsby-node.js returned an error, TypeError: _.kebabCase is not a function
However kebabCase was used smoothly before modifying the query to add pagination, so I don't think the problem is actually there.
Does anyone have any clue?
Thank you!
You are declaring the variable "underscore" twice:
1- from the lodash library
2- from the forEach function:
Array.from({ length: numPages }).forEach((category, _, i)
just change the the second variable to another arbitrary name like this:
Array.from({ length: numPages }).forEach((category, otherName, i)

Resources