I am trying to do getServerSideProps but I am getting the following error what is the error I am doing
TypeError: Cannot read properties of undefined (reading 'map')
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
import { FormControl, Button } from "react-bootstrap";
import Card from "react-bootstrap/Card";
export default function Answershooks(props, { posts }) {
return (
<div className="answerhook">
{posts.map((personData, index) => {
return (
<Card key={index} className="cardmobile">
<Card.Body>
<p className="answersize">{personData.Answers} </p>
</Card.Body>
</Card>
);
})}
</div>
);
}
export async function getServerSideProps(ctx) {
const res = await fetch("https://askover.wixten.com/answersapi/" + props.id);
console.log(res);
console.log("dada");
const posts = await res.json();
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
};
}
i have added added a file stucture screenshot so u undersand how my files are placed
Your main problem is you're trying to call getServerSideProps in Answerhooks but it's not a page component, so you cannot get data on the server as expected
Instead of having getServerSideProps in that, you can move your API call to getServerSideProps in [itmid].jsx (which is an actual page component) like below
export async function getServerSideProps(ctx) {
var id = ctx.query.itmid;
const queryRequest = fetch("https://ask-over.herokuapp.com/questone/" + id).then(async (res) => await res.json());
const answerRequest = fetch("https://askover.wixten.com/answersapi/" + id).then(async (res) => await res.json());
const [posts, answerPosts] = await Promise.all([queryRequest, answerRequest]);
return {
props: {
posts,
answerPosts
}
};
}
After that, you can get answerPosts from props for Query
function Query({ posts, answerPosts }) {
return <Answerhooks answerPosts={answerPosts} />
}
Finally, you can have the data on props inside Answerhooks component
function Answershooks({ answerPosts }) {
//TODO: Apply your logic with `answerPosts`
console.log(answerPosts)
return <div></div>
}
Lets start with the fetch error and work out why that is failing so make a new component.
fetchHandler.js
export async function fetchHandler(url){
try{
const res = await fetch(url);
return res
} catch(err){
console.log(err); //this will tell us why it failed.
return false //this gives us a condition we can use on the front end
}
}
Then your Answerhooks.
import {fetchHandler} from '../yourpath'
export default function Answershooks({ posts }) {
return (
<div className="answerhook">
{posts.map((personData, index) => {
return (
<Card key={index} className="cardmobile">
<Card.Body>
<p className="answersize">{personData.Answers} </p>
</Card.Body>
</Card>
);
})}
</div>
);
}
export async function getServerSideProps(ctx) {
const url = `https://askover.wixten.com/answersapi/${ctx.query.id}`
const res = await fetchHandler(url)
console.log(res);
const posts = await res.json();
return {
props: {
posts: posts === false ? [] : posts //Just to stop your app crashing
},
};
}
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({req}) => {
const result = await store.dispatch(fetchHome());
return {
props: {
list : result
},
};
}
);
Related
I have a website where if I press a button it fetches a post object from a database and adds it into an array. I need to somehow display all the objects in the array as react components and also to update the list every time when a new post is added.
I've been trying to use the map() method but I can't get it to display the new posts that are added when I click the button.
Main component:
import Post from './Post'
import { useState, useEffect, createElement } from 'react'
import { useCookies } from 'react-cookie';
import Axios from 'axios'
const Content = () => {
const [postArr, setPostArr] = useState([])
const getPosts = ()=>{
Axios.defaults.withCredentials = true
Axios({
method: 'get',
url: 'http://localhost:3010/api/getpost/',
headers: {'Content-Type': 'multipart/formdata' }
})
.then((response) => {
addPostToPostArray(response)
})
.catch((response) => {
console.log(response);
});
}
const addPostToPostArray = (response) => {
let imgName = response.data.imgurl.slice(68, 999999)
let sendObj = {
id:response.data.id,
posterid:response.data.posterid,
imgurl:`http://localhost:3010/images/${imgName}`,
title:response.data.title,
likes:response.data.likes,
date:response.data.date
}
postArr.push(sendObj)
/*
A fetched post will look like this:
{
id:123, posterid:321, imgurl:`http://localhost:3010/images/333.png`,
title:'best title', likes:444, date:111111
}
*/
}
return (
<div>
{postArr.map((e) => {
return <Post post={e}/>
})}
<button onClick={getPosts}>load post</button>
</div>
);
}
export default Content;
Post component:
const Post = (props) => {
const post = props.post
return (
<div className='post-frame'>
<h1>{post.title}</h1>
<div className="image-frame">
<img src={post.imgurl}></img>
</div>
<p>{post.likes}</p>
<p>{post.posterid}</p>
</div>
);
}
export default Post;
To update the state of the component you need to call that setPostArr function with the updated array. Without that the state of the component never get's updated.
Here's an example
const Content = () => {
const [postArr, setPostArr] = useState([])
const getPosts = () => {
...
}
const addPostToPostArray = (response) => {
let sendObj = {
...
}
// ~ This part here
setPostArr([...postArr, sendObj])
// ~ Instead of
// postArr.push(sendObj)
}
return ...
}
I'm building a simple venue review app using react/redux toolkit/firebase.
The feature VenueList.js renders a list of venues. When the user clicks on a venue, it routes them to Venue.js page which renders information about the specific venue clicked on.
Here's the problem: Venue.js renders on the first page load, but crashes when I try to refresh the page.
After some investigating I found that in Venues.js, the useSelector hook returned the correct state on first load, and then an empty array upon refresh:
Intial page load:
On page refresh
Why is this happeing and how can I fix this so that the page renders in all circumstances?
Here's Venue.js
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import AddReview from "../../components/AddReview";
import Reviews from "../../components/Reviews";
const Venue = () => {
const { id } = useParams();
const venues = useSelector((state) => state.venues);
const venue = venues.venues.filter((item) => item.id === id);
console.log(venues)
const content = venue.map((item) => (
<div className="venue-page-main" key = {item.name}>
<h2>{item.name}</h2>
<img src={item.photo} alt = "venue"/>
</div>
));
return (
<>
{content}
<AddReview id = {id}/>
{/* <Reviews venue = {venue}/> */}
</>
);
};
export default Venue;
The list of venues in VenueList.js
import { Link } from "react-router-dom";
import { useEffect } from "react";
import { fetchVenues } from "./venueSlice";
import { useSelector,useDispatch } from "react-redux";
const VenueList = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchVenues());
}, [dispatch]);
const venues = useSelector((state) => state.venues);
const content = venues.venues.map((venue) => (
<Link to={`/venue/${venue.id}`} style = {{textDecoration: "none"}} key = {venue.name}>
<div className="venue-item">
<h2>{venue.name}</h2>
<img src={venue.photo} />
</div>
</Link>
));
return (
<div className="venue-list">
{content}
</div>
);
};
export default VenueList;
And here's the slice venueSlice.js controlling all the API calls
import { createSlice,createAsyncThunk } from "#reduxjs/toolkit";
import { collection,query,getDocs,doc,updateDoc,arrayUnion, arrayRemove, FieldValue } from "firebase/firestore";
import { db } from "../../firebaseConfig";
const initialState = {
venues: []
}
export const fetchVenues = createAsyncThunk("venues/fetchVenues", async () => {
try {
const venueArray = [];
const q = query(collection(db, "venues"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) =>
venueArray.push({ id: doc.id, ...doc.data() })
);
return venueArray;
} catch (err) {
console.log("Error: ", err);
}
});
export const postReview = createAsyncThunk("venues/postReview", async (review) => {
try {
const venueRef = doc(db,"venues",review.id)
await updateDoc(venueRef, {
reviews: arrayUnion({
title:review.title,
blurb:review.blurb,
reviewId:review.reviewId })
})
} catch (err) {
console.log('Error :', err)
}
})
export const deleteReview = createAsyncThunk("venues/deleteReview", async (review) => {
const newReview = {blurb:review.blurb, title: review.title, reviewId: review.reviewId}
try {
const venueRef = doc(db,"venues",review.id)
await updateDoc(venueRef, {
reviews: arrayRemove(newReview)
})
} catch (err) {
console.log('Error: ', err)
}
})
const venueSlice = createSlice({
name: "venues",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchVenues.fulfilled, (state, action) => {
state.venues = action.payload;
})
},
});
export default venueSlice.reducer
I think this is what is going on:
First time you load this page, you first visit the list of venues so the call to fetch them is made and the venues are stored to redux. Then when you visit a specific venue, the list exists so the selector always returns data.
dispatch(fetchVenues());
When you refetch the page you are in the /venue/${venue.id} route.
The dispatch to fetch the list hasn't been called and so you get the errors you mention.
There are a couple of ways to fix your issue
Fetch the venues if the data are not available. In Venue.js do something like:
const Venue = () => {
const { id } = useParams();
const venues = useSelector((state) => state.venues) || [];
const venue = venues.venues.filter((item) => item.id === id);
useEffect(() => {
if(venues?.length === 0) {
dispatch(fetchVenues());
}
}, [dispatch, venues, id]);
console.log(venues)
// You need to check if the venue exists, otherwise your code will throw errors
if(!venue) {
return <div>Some loader or error message<div/>
}
const content = venue.map((item) => (
<div className="venue-page-main" key = {item.name}>
<h2>{item.name}</h2>
<img src={item.photo} alt = "venue"/>
</div>
));
return (
<>
{content}
<AddReview id = {id}/>
{/* <Reviews venue = {venue}/> */}
</>
);
};
export default Venue;
Second option would be to use something like redux-persist so your data remains when the reload happens
const venues = useSelector((state) => state.venues)
render(){
<React.Fragment>
{
(venues && venues.venues && venues.venues instanceof Array && venues.venues.length>0) && venues.venues.map((elem,index)=>{
return(
<div className="venue-page-main" key={index}>
<h2>{elem.name}</h2>
<img src={elem.photo} alt="venue" />
</div>
);
})
}
</React.Fragment>
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I have been trying to get the images from this API to append to the page by mapping through them, but I keep getting one of two error messages saying 'undefined.map is not a function' or 'getBirds.map is not a function.'I've tried leaving the array of objects as it is and setting state to an object and an array(at separate times) but that didn't work. I've also tried using Object.key, Object.values, and Object.entries(each at separate times) to turn the array of objects into an array and then map through my variable and through getBirds(again separately) but those attempts also failed. I have attached three of my attempts below. Can someone help me understand where I've gone wrong?
// Attempt 1
import {useState, useEffect} from 'react'
import axios from 'axios'
function Birds(props) {
const [getBirds, setGetBirds] = useState({})
const {image} = props
useEffect(() => {
async function fetchBirds() {
const URL = `https://audubon-society-api.herokuapp.com/birds`
try {
const res = await axios.get(URL)
console.log(res.data)
setGetBirds(res.data)
} catch (error) {
console.log(error)
}
}
fetchBirds()
}, [])
if (!getBirds) return <h3>Loading...</h3>
return (
<div>
<img src={getBirds.map(image)} alt={getBirds.map(image)}></img>
</div>
)
}
export default Birds
// Attempt 2
import {useState, useEffect} from 'react'
import axios from 'axios'
function Birds(props) {
const [getBirds, setGetBirds] = useState([])
const {image} = props
useEffect(() => {
async function fetchBirds() {
const URL = `https://audubon-society-api.herokuapp.com/birds`
try {
const res = await axios.get(URL)
console.log(res.data)
setGetBirds(res.data)
} catch (error) {
console.log(error)
}
}
fetchBirds()
}, [])
if (!getBirds) return <h3>Loading...</h3>
return (
<div>
<img src={getBirds.map(image)} alt={getBirds.map(image)}></img>
</div>
)
}
export default Birds
// Attempt 3
import {useState, useEffect} from 'react'
import axios from 'axios'
function Birds(props) {
const [getBirds, setGetBirds] = useState({})
const {image} = props
useEffect(() => {
async function fetchBirds() {
const URL = `https://audubon-society-api.herokuapp.com/birds`
try {
const res = await axios.get(URL)
console.log(res.data)
setGetBirds(res.data)
} catch (error) {
console.log(error)
}
}
fetchBirds()
}, [])
const birds = Object.entries(getBirds)
birds.forEach(([key, value]) => {
console.log(key, value)
})
if (!getBirds) return <h3>Loading...</h3>
return (
<div>
<img src={birds.map(image)} alt={birds.map(image)}></img>
</div>
)
}
export default Birds
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You would need to initialize your state with an array, so the map function won't get errors, and correct the way you map it:
Initialize state with an array:
const [getBirds, setGetBirds] = useState([]);
Map it:
return (
<div>
{getBirds.map((bird) => (
<img src={bird.image} alt={bird.image}></img>
))}
</div>
);
Also, check your array with length, because [] or {} both equal to true.
if (!getBirds.length) return <h3>Loading...</h3>;
console.log(!![]);
console.log(!!{});
console.log(!![].length)
The completed solution:
import { useState, useEffect } from "react";
import axios from "axios";
function Birds(props) {
const [getBirds, setGetBirds] = useState([]);
useEffect(() => {
async function fetchBirds() {
const URL = 'https://audubon-society-api.herokuapp.com/birds';
try {
const res = await axios.get(URL);
console.log(res.data);
setGetBirds(res.data);
} catch (error) {
console.log(error);
}
}
fetchBirds();
}, []);
if (!getBirds.length) return <h3>Loading...</h3>;
return (
<div>
{getBirds.map((bird) => (
<img src={bird.image} alt={bird.image}></img>
))}
</div>
);
}
export default Birds;
Working Example:
Your init state of birds and setBirds should be an array [] not an object {}, also you don't need:
const birds = Object.entries(getBirds). fetch return array of birds already.
<img src={birds.map(image)} alt={birds.map(image)}></img> is wrong, the array loop map should render an image for each bird.
Below code will run for your need:
import React, {useState, useEffect} from "react";
import axios from 'axios';
function Birds(props) {
//- const [getBirds, setGetBirds] = useState([])
//- const {image} = props
// +
const [birds, setGetBirds] = useState([])
useEffect(() => {
async function fetchBirds() {
const URL = `https://audubon-society-api.herokuapp.com/birds`
try {
const res = await axios.get(URL)
console.log(res.data)
setGetBirds(res.data)
} catch (error) {
console.log(error)
}
}
fetchBirds()
}, [])
// - const birds = Object.entries(getBirds)
// - birds.forEach(([key, value]) => {
// - console.log(key, value)
// - })
// - if (!getBirds) return <h3>Loading...</h3>
if (!birds) return <h3>Loading...</h3>
return (
<div>
{/* <img src={birds.map(image)} alt={birds.map(image)}></img> */}
{birds.map((item, index) =>
<img src={item.image} alt={index}></img>
)}
</div>
)
}
export default Birds
I'm creating a page that will call my API route to return the value from my collection using MongoDB. But I'm having this error of Objects are not valid as a React child. I don't know why this happening. Can you please help me?
pages/index.js
export const getServerSideProps = async () => {
const res = await fetch('http://localhost:3000/api/listings');
const data = await res.json();
if (!data) {
return {
notFound: true,
};
}
return { props: { data } };
};
const index = async ({ data }) => {
return (
<>
<section className='w-screen h-screen bg-hero-pattern bg-cover bg-no-repeat bg-bottom'></section>
{data.map((prop) => (
<div key={prop._id}>
<h1>{prop.name}</h1>
<h2 className='text-2xl truncate'>{prop.summary}</h2>
<p>{prop.description}</p>
</div>
))}
</>
);
};
pages/api/listings
import { connectToDatabase } from '../../middlewares/mongodb';
export const fetchDbData = async () => {
const { db } = await connectToDatabase();
const data = await db
.collection('listingsAndReviews')
.find({})
.limit(1)
.toArray();
return JSON.parse(JSON.stringify(data));
};
export default async (req, res) => {
const data = await fetchDbData();
res.status(200).json(data);
};
For this project, I am attempting to transfer the data I have received from the API (a list of albums) and send them to another module in order to display the results based on whichever user has been selected. I'm using "useEffect()" in tandem with the "setAlbums()" function to set and send the album list data through a prop labeled "album". The problem is I am not receiving the data in the other module "AlbumList.js", so I can't display the user's album list. Can anyone find a solution to this? Please forgive all the logs, I'm new to working with React and have been trying to sort this out. Thanks so much for taking the time.
Here is the App.js file:
// App.js
import "./App.css";
import AlbumList from "./AlbumList";
import UserList from "./UserList";
function App() {
const controller = new AbortController();
const [users, setUsers] = useState([]);
const [user, setCurrentUser] = useState({});
const [albums, setAlbums] = useState([]);
document.title = 'Awesome Album App';
const userUrl = "https://jsonplaceholder.typicode.com/users";
// Loading Albums
useEffect(() => {
const albumUrl = `https://jsonplaceholder.typicode.com/albums?userId=${user.id}`;
async function loadAlbums() {
try {
const response = await fetch(albumUrl, { signal: controller.signal });
const json = await response.json();
console.log("Logging json: ", json)
setAlbums(json)
} catch (err) {
if (err.name === 'AbortError') {
console.log('Aborted', err)
} else {
throw err;
}
}
}
loadAlbums();
console.log("After loadAlbums: ", albums)
return () => {
controller.abort();
}
},[user])
// Loading Users
useEffect(() => {
async function loadUser() {
try {
const response = await fetch(userUrl, { signal: controller.signal });
const data = await response.json();
setUsers(...users, data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Aborted', err)
setUsers([])
} else {
throw err;
}
}
}
loadUser();
return () => {
controller.abort();
}
},[])
// Return JSX
return (
<div className="App">
<div className="left column">
<UserList users={users} setCurrentUser={setCurrentUser} />
</div>
<div className="right column">
<AlbumList user={user} album={album} />
</div>
</div>
);
}
export default App;
Here is the component that displays the albums:
// AlbumList.js
import React from "react";
function AlbumList({ user = {} }, albums) {
console.log("Logging inside of albumsList", albums)
if (albums.length) {
return albums.map((album, index) => {
return <li key={index}>{album.id}{album.title}</li>;
});
}
return (
<div>
<p>Please click on a user name to the left</p>
</div>
);
}
export default AlbumList;```
Is it because you are passing album={album} to the <AlbumList /> component when it should be albums={albums}? I'm not sure if this was just an error when you were transferring your code to Stack Overflow, but <AlbumList/> expects a prop of albums whereas you passed an album prop. Though, I'm curious as to why the compiler didn't throw an error for album not being defined - from what I can see, you only have the albums variable defined. Also, I believe you need to destructure the props in AlbumList.js like so
function AlbumList({ user = {} , albums}) {
(i.e. } should appear after the albums prop).