Gatsby works fine during development but throws error during build - javascript

I am passing data from one page to another in gatsby. First page code:
let state = {book: book, src: src}
return (
<div className="book__holder">
<Link to="/pdf/" state={state}>
<Card
hoverable
style={{ width: 240 }}
cover={<img alt={book}
src={url}
/>}
>
<Meta
title={book}
description={faculty+" "+year+"-"+part}
/>
</Card>
</Link>
</div>
This data is used in pdf page as:
const PDFPage = props =>{
return (
<React.Fragment>
<SEO title={props.location.state.book} />
<NavBar></NavBar>
<Embed src={props.location.state.src} type="application/pdf">
</Embed>
</React.Fragment>
)}
export default PDFPage
Everything is fine when using gatsby develop but when i use gatsby build it throws following error:
error Building static HTML for pages failed
See our docs page on debugging HTML builds for help https://gatsby.app
/debug-html
11 | return (
12 | <React.Fragment>
> 13 | <SEO title={props.location.state.book} keywords={[`gatsby`,
`application`, `react`]} />
| ^
14 | <NavBar></NavBar>
15 | <Embed src={props.location.state.src} type="application/pdf">
</Embed>
16 |
WebpackError: TypeError: Cannot read property 'book' of undefined
- pdf.js:13 PDFPage
lib/src/pages/pdf.js:13:38
Can anyone help me please?

Gatsby will throw error during production build, since location is not available during server-side rendering.
One way to make sure the build doesn't throw an error is to:
Check for the window in componentDidMount
Map the location prop to state
Render the value from your state instead of directly from props
In componentDidMount()
componentDidMount() {
if (typeof window === 'undefined') {
return;
}
this.setState(() => ({ playerName: this.props.location.state.playerName }));
}
In render()
render() {
return <div>{this.state.playerName}</div>;
}
credit to this thread, How to get previous url in react gatsby

Sometimes you’ll want to pass data from the source page to the linked page. You can do this by passing a state prop to the Link component... The linked page will have a location prop containing a nested state object structure containing the passed data.
Passing Props to Link targets
While the following demo is not actually using gatsby, it is using reach router (and gatsby uses reach router under the hood).
import React from "react";
import { render } from "react-dom";
import { Router, Link } from "#reach/router";
const App = () => {
let state = {
name: 'Ron',
book: {
title: 'Harry Potter and the Deathly Hallows',
author: 'J. K. Rowling',
progress: '80%'
}
}
return (
<div>
<h1>App</h1>
<nav>
<Link to="/" state={state}>Home</Link>{" "}
<Link to="dashboard" state={state} >Dashboard</Link>
</nav>
<Router>
<Home path="/" />
<Dashboard path="/dashboard" />
</Router>
</div>
)};
const Home = ({location}) => (
<div>
<h2>Welcome { location.state.name }</h2>
<p></p>
</div>
);
const Dashboard = ({location}) => (
<div>
<h2>Dashboard</h2>
<p>Hi { location.state.name }.</p>
<p>You have read { location.state.book.progress } of { location.state.book.title } by { location.state.book.author }.</p>
</div>
);
render(<App />, document.getElementById("root"));
Stackblitz

Related

How do I fix the Remix Error: useFetcher must be used within a data router?

I'm new to Remix (and backend programming in general) and feeling lost troubleshooting this. I'm trying to UseFetcher to allow for non-navigational data mutations in a "todo-like" application. Remix docs doesn't explicitly say I need to be using it within a data router, and the examples don't clear up my confusion at all.
Here's what my App component looks like in root.tsx:
export default function App() {
return (
<html lang="en" className="h-full">
<head>
<Meta />
<Links />
</head>
<body className="h-full">
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
And my routes/goods.tsx for displaying a list of items (much of this is adapted from the default Indie Stack):
export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const title = formData.get("title");
const id = formData.get("id");
if (typeof title !== "string" || title.length === 0) {
return json(
{ errors: { title: "Title is required" } },
{ status: 400 }
);
}
const good = await updateGood({ title, id });
return null;
}
export default function GoodsPage() {
const data = useLoaderData<typeof loader>();
const user = useUser();
return (
<div className="flex h-full min-h-screen flex-col">
<Outlet />
<main className="flex h-full bg-white">
<div className="h-full w-80 border-r bg-gray-50">
{data.completedGoodListItems.length === 0 ? (
<p className="p-4">No goods yet</p>
) : (
<>
<h2>Incomplete</h2>
<ol>
{data.completedGoodListItems.map((good) => (
<GoodItem key={good.id} good={good}></GoodItem>
))}
</ol>
</>
)}
<>
<h2>Completed</h2>
<ol>
{data.incompleteGoodListItems.map((good) => (
<GoodItem key={good.id} good={good}></GoodItem>
))}
</ol>
</>
</div>
<Form action="/logout" method="post">
<button
type="submit"
className="rounded bg-slate-600 py-2 px-4 text-blue-100 hover:bg-blue-500 active:bg-blue-600"
>
Logout
</button>
</Form>
</main>
</div>
);
}
function GoodItem ({ good }) {
const fetcher = useFetcher();
return (
<li>
<fetcher.Form method="post">
<input type="text" defaultValue={good.title}></input>
</fetcher.Form>
</li>
)}
This results in Error: useFetcher must be used within a data router.
So then I try to follow the instructions for encapsulating the App within a data router using createBrowserRouter which leads me to writing this code in my root.tsx:
async function loader({ request }: LoaderArgs) {
return json({
user: await getUser(request),
});
}
const router = createBrowserRouter([
{
path: "/",
element: <App />,
// loader: rootLoader,
children: [
{
path: "/goods",
element: <GoodsPage />,
// loader: loaderName,
},
],
},
]);
// #ts-ignore
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
export default function App() {
return (
<html lang="en" className="h-full">
<head>
<Meta />
<Links />
</head>
<body className="h-full">
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
I didn't know what to add for the loaders for each element. I tried assigning the async loader to a const and adding it into the constructor for router, but I received the error: The expected type comes from property 'loader' which is declared here on type 'RouteObject' so I just left it blank.
This code results in ReferenceError: document is not defined certainly because I don't have the syntax or structure correct for this router. Can someone provide some guidance on how I should be using createBrowserRouter in this context? I know I need to use the RouterProvider component in some way, but I don't have enough experience to see the path forward. What am I missing here?
You are most probably importing useFetcher from an incorrect package. Make sure that you are importing it from #remix-run/react:
import { useFetcher } from "#remix-run/react";

React Component keeps rerendering causing video to restart

I am trying to render a video on a page with the following code. The name of the video is sent to the component from the previous page with match props and react router dynamic routing.
router.js
<LayoutRoute
path="/videos/:id"
render={matchprops => (
<Suspense>
<AccessControl>
<LazyVideoPage {...matchprops} />
</AccessControl>
</Suspense>
)}
/>
videoList.js snippet
history.push(`/videos/${id}`, {
name
})
Currently the video is not able to play properly as it keeps restarting on every mouse move or page scroll. Using the componentDidUpdate lifecycle method it can be seen that the page rerendering is triggered by the match property, however this property is not changing at all.
I have investigated the middleware and can't see anything that may be tracking the mouse coordinates and causing the behaviour, nor have such features been purposely implemented. This behaviour is also only observed when logged into the app, even though there are no details about user log in used in the component.
videoPage.js
const VideoDisplay = ({videoId, location:{state:{name}}}) => {
return (
<Card className="main-card mb-3">
<CardBody className="top-card-yellow">
<CardTitle><h1>{name}</h1></CardTitle>
<ReactHlsPlayer
src={`https://hello-world.com/${videoId}/${videoId}.m3u8`}
autoPlay={false}
controls={true}
width="100%"
height="auto"
/>
</CardBody>
</Card>
)
}
class VideoPage extends Component {
constructor(props) {
super(props);
this.state = {
videoId: this.props.match.params.id,
}
}
componentDidUpdate(prevProps, prevState) {
Object.entries(this.props).forEach(([key, val]) =>
prevProps[key] !== val && console.log(`Prop '${key}' changed`)
);
if (this.state) {
Object.entries(this.state).forEach(([key, val]) =>
prevState[key] !== val && console.log(`State '${key}' changed`)
);
}
}
render() {
const { videoId } = this.state;
const { location } = this.props;
return (
<Fragment>
<Row>
<Col md="12">
<VideoDisplay videoId={videoId} location={location} />
</Col>
</Row>
</Fragment>
);
}
}
export default VideoPage;
react-router-dom is not currently on the latest version, so the useParams hook is not an option but this will be updated and could be used in the next sprint.
How do I go about fixing this behaviour so that the video can be viewed without being constantly restarted? Is there something I may have overlooked?

Next js error when linking to a dynamic page

I have the following structure:
- blog (directory)
-- index.js (list of all blog articles)
-- [slug].js (single article)
when I am inside index.js I have:
const Blog = props => {
const { pageProps: { articles } } = props
const blogArticles = objectToArray(articles)
return (
<Layout bio={props.bio}>
<Title>
I am excited by the idea of sharing my knowledge and perhaps inspiring others.
</Title>
{blogArticles.map(
(article, i) => <Card key={`id-${i}`} data={article} />
)}
</Layout>
)
}
Blog.getInitialProps = async () => {
const articles = await getBlogArticles()
return { articles }
}
and the card component has the link:
...
<Link href={`/blog/${slug}`}>
<Wrapper>
<ImgWrapper>
<Img src={BASE_URL + url} />
</ImgWrapper>
<TextWrapper>
<Title>{title}</Title>
<ArtcilePreview>{intro}</ArtcilePreview>
</TextWrapper>
</Wrapper>
</Link>
...
and inside [slug].js I have:
const BlogArticle = (props) => {
return (
<Layout bio={props.bio}>
<Article title={props.pageProps.article[0].title} content={props.pageProps.article[0].content} />
<ArticleShare url={'process.env.API_URL + asPath'} />
</Layout>
)
}
BlogArticle.getInitialProps = async ({ query }) => {
const article = await getArticleBySlug(query.slug)
return { article }
}
when I click within the card component to go to the dynamically generated page, it works correctly. However, during the transition from url/blog to url/blog/my-slug I can see an error message appearing and disappearing quickly in the console.
It looks like it cannot find the page that is dynamically generated throwing what I think to be a 500 error.
I cannot figure out why it appears and disappears so quickly.
ERROR BELOW I HAVE RECORDED A SCREEN VIDEO
According to the nextjs documentation you should not add the actual slug in href
If you check the docs of Link I linked above correct usage is
<Link href="/blog/[slug]" as={`/blog/${slug}`}>

cannot read property 'map' of undefined in React jsx

I am learning react by going through a react tutorial and i am getting a map undefind error. What should i do?
I am trying to iterate over the array to display the data of the array in the player component but i am still getting this error. i tried searching online and looking through at other map undefined error on stack overflow but none is like my problem that i am having.
const players = [
{
name: "Guil",
score: 50
},
{
name: "Treasure",
score: 85
},
{
name: "Ashley",
score: 95
},
{
name: "James",
score: 80
}
];
const Player = (props) => {
return (
<div className="player">
<span className="player-name">
{props.name}
</span>
<Counter score={props.score} />
</div>
);
}
const App = (props) => {
return (
<div className="scoreboard">
<Header
title="Scoreboard"
totalPlayers={4}
/>
{/* Players list */}
{props.initialPlayers.map(player =>
<Player
name={props.name}
score={props.score}
/>
)}
</div>
);
}
ReactDOM.render(
<App initialPlayers={ players}/>,
document.getElementById('root')
);
export default App;
Considering you didn't give us the error message, I can't be sure of what is actually undefined, although I doubt that map is undefined.
In the snippet below, you're trying to access props.name and props.score, which don't exist in the context. You've called the player player within the map callback and need to access it as such.
i.e. change props.name and props.score to player.name and player.score.
{props.initialPlayers.map(player =>
<Player
name={props.name} // player.name
score={props.score} // player.score
/>
)}
Looks like cubrr's comment might have identify the issue that you are running into. You are probably getting the error here:
const App = (props) => {
return (
<div className="scoreboard">
<Header
title="Scoreboard"
totalPlayers={4}
/>
{/* Players list */}
{props.initialPlayers.map(player =>
<Player
name={props.name}
score={props.score}
/>
)}
</div>
);
}
since props.name = undefined, you are trying to render something that does not exist. You will need to change it to:
{props.initialPlayers.map(player =>
<Player
name={player.name}
score={player.score}
/>
)}
Hope that helps.
React is relatively good at providing error logs for you, please be sure to take a look at error logs and it will tell you which line the error is occurring on.

How to Retrieve and Render Data from Firebase using ID/Key (with React)?

I’m trying to access data from the Firebase server and then proceed to open a view page of the object. My routing is working for sure I think.
The index component I want the clicked post to link from is this:
renderPosts() {
return _.map(this.state.posts, (post, key) => {
return (
<div key={key} className="thumbnail">
<h2>
<Link to="/view/posts/{post}">
{post.title}
</Link>
</h2>
<p>{post.body}</p>
</div>
);
});
}
I tried to link to the post cause I figured it work based on the componentDidMount used to build that function. And I imported the view component into it as well.
The app.js page is as this:
<BrowserRouter>
<div>
<div>
<Nav/>
</div>
<Switch>
<Route path='/' exact component={List}/>
<Route path='/new' component={NewPost}/>
<Route path='/view' component={View}/>
</Switch>
</div>
</BrowserRouter>
</div>
);
}
}
And the view.js page is as follows:
componentWillMount(){
let postRef = database().ref('posts').orderByKey().limitToLast(100);
postRef.on('value', snapshot => {
let post = { title: snapshot.title, id: snapshot.key };
this.setState({ posts: [post].concat(this.state.posts) });
});
console.log(postRef);
}
}
render() {
return (
<div >
</div>
);
}
}
Sorry. It’s a bit bare now cause I removed everything I have tried. I used a couple articles and videos to try to figure it out but nothing worked. The original lifecycle method and constructor I used for the index page however is this:
constructor(props){
super(props);
this.state = {
title: '',
body: '',
posts:{}
};
this.onInputChange = this.onInputChange.bind(this);
this.onHandleSubmit = this.onHandleSubmit.bind(this);
}
and
componentDidMount(){
database.on('value', snapshot => {
this.setState({
posts: snapshot.val()
});
});
}
I’m sorry, I know it is a lot to get through but I thought I should cover everything that might be useful. But to sum up: My issues are how to retrieve the post from the database and then add the key that Firebase automatically makes to the link and thus render it in a new page.
Ps. My database is exported as this
export const database = firebase.database().ref('/posts’);
I don't understand the question but it sounds like the post key contains the value you want for the link. If that's the case then try...
renderPosts(){
return Object.keys(this.state.posts).map((post, index)=>(
<div key={index} className="thumbnail">
<h2>
<Link to={`/view/posts/${post}`}>
{this.state.posts[post].title}
</Link>
</h2>
<p>{this.state.posts[post].body}</p>
</div>
)}
);
then you should just be able to call your renderPosts function in your render method.
render() {
return (
<div >
{renderPosts()}
</div>
);
}
It might take a little while to get the post data if you're calling firebase so only call the render method when the post data shows up.
render() {
return (
<div >
{this.state.posts && renderPosts()}
</div>
);
}

Categories