Evaluation failed: ReferenceError: _MainBody is not defined - Puppeteer - javascript

I am super, super new to this subject (Today it is my first day): end 2 end unit tests and I discovered puppeteer.
I have my page where I launch a function to fetch some info from an API and than I display the info on the page.
What I want to do is the following.
I want to make this end 2 end test to check if the page has a header and a footer + if the function gets called and gives a response if called.
Bellow I will attach my code.
The question is: Why does it says that _MainBody is not defined since that is the name of the function and the file name where everything happens: fetch the data and display it.
I will attach it below so you can understand what I did and where is the problem.
Thank you in advance to everyone that is willing to help.
e2e.test.tsx
import getRandomBeer from "./MainBody";
import puppeteer from "puppeteer";
describe("myTest", () => {
let browser: puppeteer.Browser;
let page: puppeteer.Page;
beforeAll(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
});
it('The function gets called', async () => {
await page.goto('LINK');
console.log(await page.evaluate(() => typeof getRandomBeer === 'function'));
})
afterAll(() => browser.close());
});
file where everything happens and where the function gets called to fetch the data
import { render } from '#testing-library/react';
import React, { useState, useEffect } from 'react';
import './App.css';
import axios, { AxiosResponse } from 'axios';
import Beer from './BeerClass';
//Function that gets called in order to fetch the beers one by one
async function getRandomBeer() {
const req = await fetch('https://api.punkapi.com/v2/beers/random');
const data = await req.json();
console.log(data[0]);
return data[0] as Beer;
}
const nBeers = 30;
function MainBody() {
const [beerData, setBeerData] = useState<Beer[]>([]);
console.log(beerData);
//-----------------------------------------------------------------------------------------------------------------
//NOTE: Some of the beers come with NULL for the image link so some of the beers don't have a photo unfortunatelly.
//-----------------------------------------------------------------------------------------------------------------
//Saving all the beers inside an array that initally gets filled with zeros and than I map the beers inside it and than I se the beerData so I can display it below
//It waits until it does not fetch all the beers
useEffect(() => {
Promise.all(new Array(nBeers).fill(0).map(getRandomBeer).reverse()).then(setBeerData);
}, [])
//Display the beer data, beer after beer
return (
<div id="beers">
{beerData && beerData.map((beerData) => {
return (
<div className="container">
<div className="image"> <img src={beerData.image_url} width={30} height={100}></img>
<div className='text'>
<h4>{beerData.name} </h4>
<p>{beerData.tagline}</p>
</div>
</div>
</div>
);
}
)}
</div>
);
};
export default { MainBody , getRandomBeer};

You need to use this after defining page
now you can use your function in the callback you use in evaluate.
evaluate returns the element you define
await page.exposeFunction("getRandomBeer", getRandomBeer);

Related

Update Server Component after data has changed by Client Component in Next.js

I am still trying to wrap my head around this scenario. Can anyone please suggest what is the correct way to do this in Next.js 13?
I diplay a list of users in a Server Component, for example, like this (using MongoDB):
// UsersList.jsx
const UsersList = () => {
const users = await usersCollection.getUsers()
return (
<div>
{users.map(user) => <div>{user}</div>}
</div>
)
}
And on the same page, I have also defined client component for adding users:
// UsersEdit.jsx
'use client'
const UsersEdit = () => {
const handleAdd() => // calls POST to /api/users
return // render input + button
}
Both are displayed together like this in a Server Component Page:
// page.jsx
const Users = () => {
return (
<div>
<UsersList />
<UsersEdit />
</div>
)
}
How should I "reload" or "notify" UsersList that a new user has been added to the collection to force it to display a new user/updated user?
For now, the only way to have the updated data by your Client Component reflected on the Server Component is to call router.refresh(), where router is the returned value by useRouter, after your request to the API. As you can read on the official Next.js doc:
The Next.js team is working on a new RFC for mutating data in Next.js. This RFC has not been published yet. For now, we recommend the following pattern:
You can mutate data inside the app directory with router.refresh().
And they gave a wonderful example, working with a Todo List application. I added it below to have a more complete thread.
Let's consider a list view. Inside your Server Component, you fetch the list of items:
// app/page.tsx
import Todo from "./todo";
async function getTodos() {
const res = await fetch("/api/todos");
const todos = await res.json();
return todos;
}
export default async function Page() {
const todos = await getTodos();
return (
<ul>
{todos.map((todo) => (
<Todo key={todo.id} {...todo} />
))}
</ul>
);
}
Each item has its own Client Component. This allows the component to use event handlers (like onClick or onSubmit) to trigger a mutation.
// app/todo.tsx
"use client";
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
export default function Todo(todo) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [isFetching, setIsFetching] = useState(false);
// Create inline loading UI
const isMutating = isFetching || isPending;
async function handleChange() {
setIsFetching(true);
// Mutate external data source
await fetch(`https://api.example.com/todo/${todo.id}`, {
method: 'PUT',
body: JSON.stringify({ completed: !todo.completed }),
});
setIsFetching(false);
startTransition(() => {
// Refresh the current route and fetch new data from the server without
// losing client-side browser or React state.
router.refresh();
});
}
return (
<li style={{ opacity: !isMutating ? 1 : 0.7 }}>
<input
type="checkbox"
checked={todo.completed}
onChange={handleChange}
disabled={isPending}
/>
{todo.title}
</li>
);
}

How to pass props from async functions triggered by an OnClick event to a component React js

I am working on an app which is a NY Times Search Engine (I am using this API: https://developer.nytimes.com/docs/most-popular-product/1/overview). I am showing the news most viewed in the last 7 days ordered by categories. Well, the user flow is the following one:
The user will click on a section and it will render the most viewed news in the last 7 days of that sections (for instance, "well"). My problem is I am having problems passing the props to the child component, I dont know why the News component is not receiving the props. Here the code of my Section component:
import React from 'react';
import { getNewsAttributes } from '../helpers/helpers';
import getNewsFilteredBySection from '../services/getNewsFilteredBySection';
import News from './News';
export default function Section({ section }) {
function showEvent(e) {
console.log("pasa por aquí");
const sectionSearched = e.target.innerText;
getNewsFilteredBySection(sectionSearched)
.then((response) => {
const filteredResults = response;
const newsAttributes = getNewsAttributes(filteredResults);
return (
<News newsAttributes={newsAttributes} />
)
})
}
return (
<div className="animate__animated animate__fadeIn animate__slower">
<h3 className="section-container mr-4 mb-4 pointer"
onClick={showEvent}
>{section}</h3>
</div>
)
};
Here the code of my getNewsAttributes() method:
export function getNewsAttributes(filteredResults) {
const newsAttributes = filteredResults.map(filteredResults => {
const { title, abstract, url } = filteredResults;
console.log(title, abstract, url);
return { title, abstract, url };
});
return newsAttributes;
};
And here the code of my News component:
export default function News({ newsAttributes }) {
const { title, abstract, url } = newsAttributes;
console.log(`title: ${title}`);
console.log(`abstract: ${abstract}`);
console.log(`url: ${url}`);
return (
<div>
<h3 className="section-container mr-4 mb-4 pointer">{title}</h3>
</div>
)
};
The console.log(title, abstract, url); of my getNewsAttributes(filteredResults) is currently showing this:
It seems like it is getting the required information (title, abstract and url) and mixing it together into a string. The console.logs of the News component doesnt get executed. Any idea where am I wrong? Thanks a lot :)
Your component has to return everything you want to render. You can't return another component from an onclick function - where would it be returned to?
Store the data in a state.
Use that data in your render logic.
const ExampleComponent = () => {
const [data, setData] = useState(null);
const show = async () => {
const sectionSearched = e.target.innerText;
const result = await getNewsFilteredBySection(sectionSearched);
setData(result)
};
if (data === null) {
return <button onClick={show}>Click me</button>
} else {
return <News newsAttributes={data} />
}
}

Issue to show a like counter in the front end in React JS (but it works in the API)

first time I post here !
We're working on a group project and we have an issue in the incrementation of an item in our front-end. At the moment we can check in our console.log that the number is rising when click on the button.
Althought, we have to refresh the page to see the new "like".
So here's the code in our compenent.
import "./Post.css";
import { useEffect, useState } from "react";
import { addLike, createPost } from '../../lib/social-network-library-master';
function Post({post}) {
const handleLikes = async () => {
let result = await addLike(post._id)
};
return (
<div id="general-container">
<h2>{post.title}</h2><br/>
<p>{post.content}</p><br/>
<p>Posté par {post.firstname} {post.lastname}</p><br/>
<div className="boutton-like-container">
<button className="boutton-like" onClick={() =>handleLikes()}>
like
</button>
<p>{post.likes.length} likes</p>
</div>
</div>
)
};
export default Post;
You need a state here to manage the likes.
Something like
const [counter, setCounter] = useState(0)
const handleLikes = async () => {
let result = await addLike(post._id);
//setCounter(prev => prev + 1);
// if the result is coming from api then instead just set the response that you get
setCounter(result)
};
then in the UI you can render it like so
<p>{counter}</p>

When does the callback function inside the useCallback() hook runs exactly?

Here is the code for infinite scroll load
Mainly two components MainComponent and a custom hook component
everytime i entered something on search item it sends the request and display the data to screen and inside main component i am using lastELementRef to set observer Api on that to send the request again when i scrolled at the end .
Not able to understand when does function passed inside useCallBack(()=>{}) runs
to check how many times it runs i did console.log at line no 21 inside MainComponent.
It will be very nice of folks on this community if anybody can explain me when does it runs.
I have googled and watched some Youtube videos on useCallback and all I can come up with is that it gives the function object only when the dependency inside its dependency array changes else on it memoizes the function on each re-render if dependency does not change.?
i am sharing the code here
have used axios to send request.
//MainComponent
import React,{useState,useRef,useCallback} from 'react'
import useBookSearch from './useBookSearch';
export default function MainComponent() {
//these 2 stataes are here because
//we want them to be used in this component only
//meaning we dont want them to be part
//of any custom logic
const[query,setQuery] = useState('');
const[pageNumber,setPageNumber] = useState(1);
const observer = useRef();
const {books,loading,hasMore,error} = useBookSearch(query,pageNumber);
const lastElementRef = useCallback(node=>{
console.log("How many times did i run ?");
if(loading) return ;
if(observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries=>{
if(entries[0].isIntersecting && hasMore){
setPageNumber(prevPage => prevPage + 1);
}
})
if(node) observer.current.observe(node);
console.log(node);
},[loading,hasMore])
const handleSearch=(e)=>{
setQuery(e.target.value);
setPageNumber(1);
}
return (
<div>
<input value={query} type="text" onChange={handleSearch} ></input>
{books.map((book,index) =>{
if(books.length === index + 1)
{
return <div ref={lastElementRef}key={book}>{book}</div>
}
else{
return <div key={book}>{book}</div>
}
})}
{loading && 'Loading.....................'}
{error && 'Error........'}
</div>
)
}
//custom hook component-useBookSearch
import { useEffect,useState } from 'react';
import axios from 'axios'
export default function useBookSearch(query,pageNumber) {
const[loading,setLoading] = useState('true');
const[error,setError] = useState('false');
const[books,setBooks] = useState([]);
const[hasMore,setHasMore] = useState(false);
//second useEffect which clear the books first
//and then make an api request
useEffect(()=>{
setBooks([]);
},[query])
useEffect(()=>{
setLoading(true);
setError(false);
let cancel ;
axios({
method:'GET',
url:'http://openlibrary.org/search.json',
params:{q:query,page:pageNumber},
cancelToken:new axios.CancelToken(c=>cancel=c)
}).then(res=>{
setBooks(prevBooks=>{
return [...new Set([...prevBooks,...res.data.docs.map(b=>b.title)])]
})
setHasMore(res.data.docs.length > 0);
setLoading(false);
console.log(res.data);
}).catch(e=>{
if(axios.isCancel(e)) return
setError(true);
})
return ()=> cancel();
},[query,pageNumber])
return {loading,error,books,hasMore};
}
screenshot of how the code looks when i entered the string test to fetch data
Screenshot of the console window when entering test into input box

React custom hook: can't get an async function

I've got a button that calls an async function, that is returned by a call to a custom React hook, alongside with a reactive prop that I need to keep track of.
CodeSandbox here.
// useEmail.js
import { useState } from "react";
export default function useEmail(message) {
const [returnedMessage, setReturnedMessage] = useState("old");
const send = async () => {
// fake fetch
const whatever = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
setReturnedMessage("new");
};
return {
returnedMessage,
send
};
}
And this is the app
// app.js
import React from "react";
import useEmail from "./useEmail";
export default function App() {
const { returnedMessage, send } = useEmail();
const run = async () => {
console.log("returnMessage PRE", returnedMessage);
await send();
console.log("returnMessage POST", returnedMessage);
};
return (
<div className="App">
<h2>Click and wait for 1 second</h2>
<button onClick={run}>Click me</button>
<h2>Returned message:</h2>
<p>{returnedMessage}</p>
<button onClick={() => window.location.reload()}>
Reload to test again
</button>
<p>
It prints "new", but logs "old"
<br />
even if I await send()...?
</p>
</div>
);
}
useEmail returns both a returnMessage string, that is initialized as "old", and an async function send that fetches something, then flips the returnMessage and sets it to "new".
How is it possible that in the <p>{returnedMessage}</p> the value correctly turns from "old" to "new", while the Console logs always "old", even if I await when calling send()?
It seems like send() is not really treated as an asynchronous function – I've tried in different ways but I always have a correctly updated rendering but a wrong value when I need it in the function for further processing.
Thank you for your help
You can do the job using useRef.
It seems you can't access the updated value without running the hook again.
With useRef you'll get a reference and you can access the data at any time, without running the hook again.
// useEmail.js
export default function useEmail(message) {
const messageRef = React.useRef("old");
const send = async () => {
// fake fetch
const whatever = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
messageRef.current = "new";
};
return {
messageRef,
send
};
}
// app.js
export default function App() {
const { messageRef, send } = useEmail();
const run = async () => {
console.log("returnMessage PRE", messageRef.current);
await send();
console.log("returnMessage POST", messageRef.current);
};
return (
<div className="App">
<h2>Click and wait for 1 second</h2>
<button onClick={run}>Click me</button>
<h2>Returned message:</h2>
<p>{returnedMessage}</p>
<button onClick={() => window.location.reload()}>
Reload to test again
</button>
<p>
It prints "new", but logs "old"
<br />
even if I await send()...?
</p>
</div>
);
}
You have 2 async functions in your custom hook.
Your fetch (which one you await)
setState
So even if you await for the fetch, your setState is still asynchronous:
console.log("returnMessage PRE", returnedMessage); //old
Fetch
Await fetch to complete
Fetch complete
trigger setState
function send() returns undefined (because no return is defined)
console.log("returnMessage POST", returnedMessage); //old
State is updated (async setState is complete)
returnedMessage is updated
Component re-renders
If you want to have actions depending on when returnedMessage is changed, you'll have to use useEffect in your component
useEffect(() => {
if (returnedMessage === "old") return; // Do nothing here
// returnedMessage !== "old" so assume it's "new"
// Do something...
}, [returnedMessage]);
It is a normal behaviour setState will produce only a single re-render at the end of the event even if you used await, try to add a console.log inside your component you will see returnedMessage moved to 'new'
// app.js
import React from "react";
import useEmail from "./useEmail";
export default function App() {
const { returnedMessage, send } = useEmail();
console.log("returnMessage POST", returnedMessage); // in last render it will be new so it will change the view
const run = async () => {
console.log("returnMessage PRE", returnedMessage);
await send();
};
return (
<div className="App">
<h2>Click and wait for 1 second</h2>
<button onClick={run}>Click me</button>
<h2>Returned message:</h2>
<p>{returnedMessage}</p>
<button onClick={() => window.location.reload()}>
Reload to test again
</button>
<p>
It prints "new", but logs "old"
<br />
even if I await send()...?
</p>
</div>
);
}
One thing that I noted, from your custom React Hook, you are returning an async function.
which is this:
async () => {
// fake fetch
const whatever = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
setReturnedMessage("new");
};
And within your App Component, you are accessing the custom hook where send is pointing to this async function. Right?
Now when you are calling your async function you are trying to do:
await send();
Why await here again, since we already have an await inside of our function.
When you do this you are basically waiting for a promise() here, since every async function returns a promise even when nothing is returned.
I feel the implementation of custom hook should change or calling the hook has to be different.
On top of this setState() is itself an asynchronous action. That is not in our control to tell when the state will update :)

Categories

Resources