const Page: FC<PageProps> = () => {
const dispatch = useAppDispatch();
const querystring = window.location.search;
const urlparameter = new URLSearchParams(querystring);
const postid = Number(urlparameter.get("id"));
const [COM] = GET_COMMENT(4); //retrieve Comments with axios
const [data, loading, error] = RETRIEVE_POST("qna", 4); //retrieve Post Detail with axios
useEffect(() => {
dispatch(changeCurrentPage({ type: "/page/:uri", data: data }));
return () => {
dispatch(changeCurrentPage({ type: "/", data: {} }));
};
}, []);
return (
<>
<div>
{/* error */}
<PostContent data={data} />
</div>
</>
);
};
export default Page;
const PostContent: FC<ContentProps> = ({ data }) => {
const { user, content } = data;
return (
<div>
<div>
{/* Post Content */}
{content}
</div>
<div>
{/* Post Writer */}
<Link to={user.profileImageUrl}>
<Writer
imgUrl={user.profileImageUrl}
userName={user.nickname}
/>
</Link>
</div>
</div>
);
};
I want to send data from Page to PostContent.
But in ContentProps(PostContent) an error occur.
Post Writer Part has trouble.
user information is undefined and all page is break.
How can I send data to child props without error?
Check if data arrived first since you are passing undefined to the child. That is why it crashes.
{data?.user?
<PostContent data={data} />
: <p>No data here</p>}
Related
When I click on the search button the first time API response is "undefined" (based on what console.log says) but the second time it has the response from API.
Why does this happen?
xport default function Home() {
const [searchTerm, setSearchTerm] = useState('');
const fetcher = (url) => fetch(url).then((res) => res.json());
const [shouldFetch, setShouldFetch] = useState(false);
const { data, error } = useSWR(
() =>
shouldFetch
? `https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=${searchTerm}`
: null,
fetcher
);
if (error) return 'An error has occurred.';
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<button
onClick={() => {
setShouldFetch(true);
console.log(searchTerm);
console.log(data);
}}
>
Search!
</button>
</div>
</main>
</>
);
}
I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());
I'm fetching two things. An item by id and then the item comments by item id. I when npm start I get
TypeError: data.comments is undefined
But if I comment out
<Comment data={itemComments} />
And then run npm start, the item data loads and if I uncomment the comment tag after the item data has already loaded comments shows until I refresh or reload again, it's only when I try to load them simultaneously I get the error.
Item.js
import React, { useEffect, useState } from "react";
import Comment from "./Comment";
import axios from "axios";
const Item = () => {
const itemId = "6019afbce548e33e7c2f4e56";
const [item, setItem] = useState([]);
const [itemComments, setItemComments] = useState([]);
const fetchData = () => {
const item = `http://localhost:3000/api/v1/items/${itemId}`;
const itemComments = `http://localhost:3000/api/v1/items/${itemId}/comments`;
const getItem = axios.get(item);
const getItemComments = axios.get(itemComments);
axios.all([getItem, getItemComments]).then(
axios.spread((...allData) => {
const allItemData = allData[0].data;
const allItemCommentsData = allData[1].data;
setItem(allItemData);
setItemComments(allItemCommentsData);
})
);
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
<div>
<div>
<h3>{item.title}</h3>
</div>
<div>
<p>Price</p>
<p>${item.price}</p>
</div>
<div>
<h3>Offers & Comments</h3>
<Comment data={itemComments} />
</div>
</div>
</div>
);
};
export default Item;
ItemComments.js
import React from "react";
const Message = (props) => {
const { data } = props;
console.log(data);
return (
<>
{data &&
data.comments.map((comment, i) => (
<div key={i}>
<div>
<div>
<p>{comment.comment}</p>
</div>
</div>
</div>
))}
</>
);
};
export default Message;
After first render react try to access comments inside itemComments when its just an empty array, and you just check if its not undefined in your children component:
{data && data.comments.map((comment, i) => (
<div key={i}>
<div>
<div>
<p>{comment.comment}</p>
</div>
</div>
</div>
))}
so change your initial state to this:
const [itemComments, setItemComments] = useState({comments:[]});
name your comment state by its initial name like this "useState({comments:[]})" and make sure that data.comment is not empty and also try to make fetch data asynchronously and let me know the result, please
const fetchData = async () => {
const item = `http://localhost:3000/api/v1/items/${itemId}`;
const itemComments = `http://localhost:3000/api/v1/items/${itemId}/comments`;
const getItem = await axios.get(item);
const getItemComments = await axios.get(itemComments);
const allData=await axios.all([getItem, getItemComments])
const allItemData= await axios.spread((...allData) => allData[0].data)
const allItemCommentsData= await axios.spread((...allData) => allData[1].data)
setItem(allItemData);
setItemComments(allItemCommentsData);
};
Is there a way to destructure a current state that has a specified shape? I have a current state in personJob but I want to able to specify which object to look at (when I click on a button that will slice that certain object and render only that data).
I get an error in TypeScript const {company, dates, duties, title} = personJob[value]; when I try to slice by that index
The error is:
Cannot destructure property 'company' of 'personJob[value]' as it is undefined.
Component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const url = 'https://course-api.com/react-tabs-project';
interface IPerson {
id: string;
company: string;
dates: string;
duties: string[];
title: string;
}
function App() {
const [personJob, setPersonJob] = useState<IPerson[]>([]);
const [value, setValue] = useState<number>(0);
const fetchData = async () => {
const response = await axios(url);
setPersonJob(response.data);
};
useEffect(() => {
fetchData();
}, []);
const { company, dates, duties, title, id } = personJob[value];
return (
<main>
<h1>Jobs</h1>
{personJob.map((job, index) => {
return (
<button key={job.id} onClick={() => setValue(index)}>
{job.company}
</button>
);
})}
<section>
<article className="border-2 px-5 py-5">
<div key={id}>
<h2>{title}</h2>
<h3>{company}</h3>
<p>{dates}</p>
{duties.map((duty) => {
return <div>*** {duty}</div>;
})}
<button type="button">More Info</button>
</div>
</article>
</section>
</main>
);
}
export default App;
Issue
On the initial render personJob is still an empty array and personJob[0] is undefined, so values can't be destructured from it.
Solution
Provide a fallback object to destructure from, personJob[value] || {}.
Conditionally render the section if personJob[value] is truthy and exists.
Code:
function App() {
const [personJob, setPersonJob] = useState<IPerson[]>([]);
const [value, setValue] = useState<number>(0);
const fetchData = async () => {
const response = await axios(url);
setPersonJob(response.data);
};
useEffect(() => {
fetchData();
}, []);
const { company, dates, duties, title, id } = personJob[value] || {}; // <-- fallback for destructuring
return (
<main>
<h1>Jobs</h1>
{personJob.map((job, index) => {
return (
<button key={job.id} onClick={() => setValue(index)}>
{job.company}
</button>
);
})}
{personJob[value] && <section> // <-- conditionally render section if data available
<article className="border-2 px-5 py-5">
<div key={id}>
<h2>{title}</h2>
<h3>{company}</h3>
<p>{dates}</p>
{duties.map((duty) => {
return <div>*** {duty}</div>;
})}
<button type="button">More Info</button>
</div>
</article>
</section>}
</main>
);
}
Demo
I've created my backend and it works. I tested different Axios requests in order to create a form.
In my React front project, I created a POST axios request, I console.log(response.data) and I got an object with the id, the title and questions.
I am stuck because I don't know how I could display the data of the object in my front.
Here is my front React code:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back-formnest-lereacteur.herokuapp.com/form/create",
{
title: "Your event",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
I am completely stuck here to display the data of my backend in my front
This is my backend code:
const express = require("express");
const router = express.Router();
const Form = require("../models/Form");
router.post("/form/create", async (req, res) => {
try {
if (req.fields.title) {
const newForm = new Form({
title: req.fields.title,
});
await newForm.save();
return res.json(newForm);
} else {
return res.status(400).json({ error: "Missing parameters" });
}
} catch (e) {
return res.status(400).json({ error: e.message });
}
});
This is my console.log(response.data) I want to display in my front React page:
I edited my code and I got an error:
import React, { useState, useEffect } from "react";
/* import { Link } from "react-router-dom"; */
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back.herokuapp.com/form/create",
{
title: "Nouveau formulaire",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.map((question, index) => (
<div> {question} </div>
))}
</>
)}
</div>
</>
);
};
export default NewForm;
Hi Guys,
I updated my code but I have still an error code (TypeError: Cannot read property 'length' of undefined)
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again my code, I succeeded only to display the title of my form but I did not succeed to display the data included in my question array. I have a "0" which appears instead of my data. Please help
return (
<>
<div>My forms </div>
<div>
{data && data.questions && (
<>
<div>{data.title} </div>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again, same error appears:
return (
<>
<div>My forms </div>
<div>
{data &&
data.questions &&
data.questions.length(
<>
<div>{data.title} </div>
{data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
you've done the hard part!
now just .map over the question array if you want to display them out?
<div>
{data.questions.map((question => (
<div> {question.title} </div>
))}
</div>
I've only done a simple example but of course you can display as much or as little as you want
of course anything in state you can render. so if you want to display title do:
{data.title} wherever pleases you
It looks like your backend responds with an object, so here is how you could go about it.
1) Change your initinal state to undefined like this.
const [data, setData] = useState([]);
to
const [data, setData] = useState(undefined);
Then you can use it in the display like this
return (
<div>
{data && (
<>
<p>{data._id}</p>
<p>{data.title}</p>
{data.question.length && data.question.map((question,idx) => {
// this is assuming that each question is just a string and not an object
return (<p key={idx}>{question}</p>)
})}
</>
)}
</div>
)