getStaticProps did't return data - reactjs

I am trying to fetch data from Storyblok API, but in getStaticProps I get the data from the storybook but it can't return data to the page.
pages/docs/[slug].js
import React from 'react'
import Storyblok from "../../lib/storyblok"
export default function DocsArticle( props ) {
console.log("PROPS: ", props)
return (
<>
<div className="page">
{props.story.name}
</div>
</>
);
}
export async function getStaticProps({ params, preview = false }) {
let slug = params.slug ? params.slug : "home";
let sbParams = {
version: "draft", // or 'published' / ' draft
};
if (preview) {
sbParams.version = "draft";
sbParams.cv = Date.now();
}
// load the stories insides the pages folder
let { data } = await Storyblok.get(`cdn/stories/${slug}`, sbParams);
console.log("STORY DATA:", data);
return {
props: {
story: data ? data.story : null,
preview,
},
revalidate: 10, // revalidate every hour
};
}
export async function getStaticPaths() {
let { data } = await Storyblok.get('cdn/links/', {
starts_with: 'article'
})
let paths = [];
Object.keys(data.links).forEach((linkKey) => {
// don't create routes for folders and the index page
if (data.links[linkKey].is_folder || data.links[linkKey].slug === "home") {
return;
}
// get array for slug because of catch all
const slug = data.links[linkKey].slug;
// remove the pages part from the slug
let newSlug = slug.replace('docs', '')
let splittedSlug = newSlug.split("/");
paths.push({ params: { slug: splittedSlug.toString() } });
});
return {
paths: paths,
fallback: false,
};
}
pages/_app.js
...
export default withRedux(initStore)(
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
return {
pageProps: Component.getInitialProps
? await Component.getInitialProps(ctx)
: {},
}
}
render() {
const { Component, pageProps, store } = this.props
return (
<>
<DefaultSeo
title="My page"
description="My test page"
openGraph={{
type: 'website',
locale: 'en_IE',
}}
/>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
</Head>
<Provider store={store}>
<ThemeProvider theme={theme}>
<GetCurrentUser />
<Component {...pageProps} />
</ThemeProvider>
</Provider>
</>
);
}
}
)
If I display the data using console.log(), I get the data, but it doesn't return it.
STORY DATA: {
story: {
name: 'article1',
created_at: '2021-08-13T00:36:04.648Z',
published_at: '2021-08-15T16:24:54.810Z',
id: 66059334,
uuid: '900a311a-2ad4-461c-9304-e2f36fd25b07',
content: {
_uid: 'd21214b5-1e80-4c6a-aa74-259f082a8242',
content: [Array],
component: 'page',
_editable: '<!--#storyblok#{"name": "page", "space": "122749", "uid": "d21214b5-1e80-4c6a-aa74-259f082a8242", "id": "66059334"}-->'
},
slug: 'article1',
full_slug: 'article1',
sort_by_date: null,
position: 10,
tag_list: [],
is_startpage: false,
parent_id: 0,
meta_data: null,
group_id: 'bdf123cc-0044-4d02-b4b3-28034ee457d0',
first_published_at: '2021-08-14T00:02:05.000Z',
release_id: null,
lang: 'default',
path: 'docs',
alternates: [],
default_full_slug: null,
translated_slugs: null
},
cv: 1629044699,
rels: [],
links: []
}
PROPS: {}
TypeError: Cannot read property 'title' of undefined
I'd appreciate it if you could let me know what I did wrong.

Related

Implementing Uppy.io StatusBar in react

I am trying to implement an Uppy.io statusbar upload as a react component. I tried using the wrapper but it does not work. If I use Dashboard or DragDrop then it does. Are there any tutorials or examples I could refer to ? My code looks like:
const UppyUpload = ({ uploadPayload }) => {
const uppy = useUppy(() => {
return new Uppy({
meta: { CaseId: '29670', CustomerId: '107', projectTitle: 'Trial' },
restrictions: { maxNumberOfFiles: 1 },
autoProceed: false,
})
.use(XHRUpload, {
endpoint: 'api/FormController/UploadVideo',
formData: true,
fieldName: 'Files',
})
.on('complete', (result) => {
const url = result.successful[0].uploadURL;
store.dispatch({
type: 'SET_USER_AVATAR_URL',
payload: { url },
});
});
});
return (
<div>
<StatusBar
uppy={uppy}
hideAfterFinish={false}
showProgressDetails
/>
</div>
);
};

Gatsby Dynamic Routing URL for Generating Layouts

So I generate conditional layouts with this code:
exports.onCreatePage = ({ page, actions }) => {
const { createPage } = actions;
if (page.path.match(/about/)) {
page.context.layout = "special";
createPage(page);
}
if (page.path.match(/projects/)) {
page.context.layout = "projectsPage";
createPage(page);
}
};
I want to change the page.path.mathch(/projects/TO ALL PROJECT SLUGS/) but I can't write the correct syntax for the path.
Does anyone know how to get all the paths after the /projects/ ?
This is the full gatsby.node.js
const path = require("path");
const { createFilePath } = require(`gatsby-source-filesystem`);
exports.onCreatePage = ({ page, actions }) => {
const { createPage } = actions;
if (page.path.match(/about/)) {
page.context.layout = "special";
createPage(page);
}
if (page.path.match(/projects\/([^\/]+$)/)) {
page.context.layout = "projectsPage";
createPage(page);
}
};
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` });
createNodeField({
node,
name: `slug`,
value: slug,
});
}
};
exports.createPages = async function ({ graphql, actions }) {
const { data } = await graphql(`
query Projects {
allMarkdownRemark {
nodes {
frontmatter {
slug
}
}
}
}
`);
data.allMarkdownRemark.nodes.forEach((node) => {
const slug = node.frontmatter.slug;
actions.createPage({
path: "/projects/" + slug,
component: path.resolve("./src/templates/project-details.js"),
context: { slug: slug },
});
});
};
And this is my template:
import React from "react";
import { AnimatePresence, motion } from "framer-motion";
import Navbar from "../components/Navbar/Navbar";
import Footer from "../components/Footer/Footer";
import FooterAbout from "../components/Footer/FooterAbout";
const duration = 0.5;
const variants = {
initial: {
opacity: 0,
},
enter: {
opacity: 1,
transition: {
duration: duration,
delay: duration,
when: "beforeChildren",
},
},
exit: {
opacity: 0,
transition: { duration: duration },
},
};
export const Layout = ({ children, location, pageContext }) => {
if (pageContext.layout === "projectsPage") {
return (
<main className="bg-black ">
<AnimatePresence>
<motion.main
key={location.pathname}
variants={variants}
initial="initial"
animate="enter"
exit="exit"
className="opacity-loader"
>
{children}
</motion.main>
</AnimatePresence>
</main>
);
}
if (pageContext.layout === "special") {
return (
<main className="bg-black ">
<Navbar />
<AnimatePresence>
<motion.main
key={location.pathname}
variants={variants}
initial="initial"
animate="enter"
exit="exit"
className="opacity-loader"
>
{children}
</motion.main>
</AnimatePresence>
<FooterAbout />
</main>
);
}
return (
<main className="bg-black ">
<Navbar />
<AnimatePresence>
<motion.main
key={location.pathname}
variants={variants}
initial="initial"
animate="enter"
exit="exit"
className="opacity-loader"
>
{children}
</motion.main>
</AnimatePresence>
<Footer />
</main>
);
};
export default Layout;
It seems like I am missing something, it accepts the (/projects/) path but not (/projects/([^/]+$)/).
To make it more clear I only want to disable the layout in the subdirectory pages of projects not in the /projects/ page.
You can use the following regular expression:
projects\/([^\/]+$)
This will match everything after /projects/. So:
if (page.path.match(/projects\/([^\/]+$)/)) {
page.context.layout = "projectsPage";
createPage(page);
}
I've added a sandbox to test all scenarios: https://regex101.com/r/eQRJb4/1
Alternatively, you can try gatsby-plugin-create-client-paths what makes exactly the same job automatically. In your case:
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/projects/*`] },
},
You can achieve the same result more easily like:
data.allMarkdownRemark.nodes.forEach((node) => {
const slug = node.frontmatter.slug;
actions.createPage({
path: "/projects/" + slug,
component: path.resolve("./src/templates/project-details.js"),
context: { slug: slug, layout: "projectsPage" },
});
});

React.js error: The service worker navigation preload request was cancelled before 'preloadResponse' settled

I have an issue with my React application (with Redux Saga), I'm getting the console error:
The service worker navigation preload request was cancelled before 'preloadResponse' settled. If you intend to use 'preloadResponse', use waitUntil() or respondWith() to wait for the promise to settle.
I see this error on the console only on Chrome, not in Firefox or Edge.
This error does not affect my application.
The following steps reproduce the error:
1. Main page upload.
2. Go to movie details page.
3. Go back to main page.
Main.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { mainActions } from '../../store/actions/actions';
import './Main.scss';
import { MoviesList, SearchPanel } from '../../components';
const propTypes = {};
const defaultProps = {};
class Main extends Component {
constructor(props) {
super(props);
this.handleSearchTextChange = this.handleSearchTextChange.bind(this);
this.handleLoadMoreButtonClick = this.handleLoadMoreButtonClick.bind(this);
this.handleMovieClick = this.handleMovieClick.bind(this);
this.handleFavoriteMovieClick = this.handleFavoriteMovieClick.bind(this);
}
componentDidMount() {
this.handleComponentDidMount();
}
handleComponentDidMount() {
const { moviesList } = this.props;
if (!moviesList || moviesList.length <= 0) {
this.getMovies(null, false);
}
}
handleLoadMoreButtonClick() {
this.getMovies(null, false);
}
handleMovieClick(e) {
if (e.target.className === 'movie') {
this.props.history.push(`/details/${e.currentTarget.dataset.id}`);
}
}
handleSearchTextChange(e) {
const { pageNumber, favoriteMoviesList } = this.props;
this.props.onSearchTextChange({
searchText: e.target.value,
pageNumber: pageNumber,
favoriteMoviesList: favoriteMoviesList
});
}
handleFavoriteMovieClick(e) {
const { id, name, posterId } = e.currentTarget.dataset;
const { moviesList, favoriteMoviesList } = this.props;
this.props.onUpdateFavoriteMovies({
updatedMovie: { id: id, name: name, posterId: posterId },
favoriteMoviesList: favoriteMoviesList,
moviesList: moviesList
});
}
getMovies(updatedSearchText, isSearchChange) {
const { searchText, pageNumber, favoriteMoviesList } = this.props;
this.props.onLoadMovies({
pageNumber: pageNumber,
favoriteMoviesList: favoriteMoviesList,
updatedSearchText: isSearchChange ? updatedSearchText : searchText,
isSearchChange: isSearchChange
});
}
render() {
const { searchText, isLoadingMoreMovies, isPager, moviesList } = this.props;
return (
<div className="main-area">
<SearchPanel
searchText={searchText}
onSearchTextChange={this.handleSearchTextChange}
/>
<MoviesList
pageName='movies'
moviesList={moviesList}
isLoadingMoreMovies={isLoadingMoreMovies}
isPager={isPager}
onLoadMoreClick={this.handleLoadMoreButtonClick}
onMovieClick={this.handleMovieClick}
onFavoriteMovieClick={this.handleFavoriteMovieClick}
/>
</div>
);
}
}
Main.propTypes = propTypes;
Main.defaultProps = defaultProps;
const mapStateToProps = (state) => {
return {
searchText: state.main.searchText,
pageNumber: state.main.pageNumber,
isLoadingMoreMovies: state.main.isLoadingMoreMovies,
isPager: state.main.isPager,
moviesList: state.main.moviesList,
favoriteMoviesList: state.main.favoriteMoviesList
};
};
const mapDispatchToProps = (dispatch) => {
return {
onLoadMovies: (request) => dispatch(mainActions.loadMovies(request)),
onSearchTextChange: (request) => dispatch(mainActions.searchTextChange(request)),
onUpdateFavoriteMovies: (request) => dispatch(mainActions.updateFavoriteMovies(request))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Main);
Details.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { detailsActions, mainActions } from '../../store/actions/actions';
import './Details.scss';
import { ActorsList, ButtonClick, CrewsList, FeaturesList, PageTitle, ProductionsList, Rating, Trailer } from '../../components';
import movieUtils from '../../utils/movie.utils';
const propTypes = {};
const defaultProps = {};
class Details extends Component {
constructor(props) {
super(props);
this.handleBackClick = this.handleBackClick.bind(this);
this.handleFavoriteMovieClick = this.handleFavoriteMovieClick.bind(this);
this.isFavorite = false;
}
componentDidMount() {
this.handleComponentDidMount();
}
handleComponentDidMount() {
if (this.props.moviesList.length <= 0) {
this.handleBackClick();
return;
}
const movieId = this.props.match.params.id;
if (!movieId) {
this.handleBackClick();
return;
}
this.props.onLoadMovieDetails(movieId);
this.updateIsFavorite(movieId);
}
numberWithCommas(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
updateIsFavorite(movieId) {
this.isFavorite = this.props.favoriteMoviesList.findIndex(movie => parseInt(movie.id) === parseInt(movieId)) > -1;
}
handleBackClick() {
this.props.history.push(`/`);
}
handleFavoriteMovieClick() {
const { movie, moviesList, favoriteMoviesList } = this.props;
this.props.onUpdateFavoriteMovies({
updatedMovie: { id: movie.id, name: movie.title, posterId: movie.poster_path },
favoriteMoviesList: favoriteMoviesList,
moviesList: moviesList
});
this.updateIsFavorite(movie.id);
}
render() {
const { movie, youtubeKey, credits } = this.props;
if (!movie) {
return null;
}
const { adult, poster_path, budget, genres, homepage, imdb_id, original_language, original_title,
overview, popularity, production_companies, production_countries, release_date, revenue, runtime, spoken_languages,
status, tagline, title, video, vote_average, vote_count } = movie;
const genresText = genres.map(genre => genre.name).join(', ');
const countriesText = production_countries.map(country => country.name).join(', ');
const languagesText = spoken_languages.map(language => language.name).join(', ');
const featuresList = [
{ item: 'Release Date', value: release_date },
{ item: 'Budget', value: `$${this.numberWithCommas(budget)}` },
{ item: 'Revenue', value: `$${this.numberWithCommas(revenue)}` },
{ item: 'Length', value: `${runtime} minutes` },
{ item: 'Popularity', value: popularity },
{ item: 'Original Title', value: original_title },
{ item: 'For Adults', value: adult ? 'Yes' : 'No' },
{ item: 'Original Language', value: original_language },
{ item: 'Spoken Languages', value: languagesText },
{ item: 'Countries', value: countriesText },
{ item: 'Status', value: status },
{ item: 'Is Video', value: video ? 'Yes' : 'No' }
];
const linksList = [];
if (homepage) {
linksList.push({ id: 1, name: 'Homepage', url: homepage });
}
if (imdb_id) {
linksList.push({ id: 2, name: 'IMDB', url: `https://www.imdb.com/title/${imdb_id}` });
}
const actorsList = movieUtils.removeDuplicates(credits ? credits.cast ? credits.cast : null : null, 'name');
const crewsList = movieUtils.removeDuplicates(credits ? credits.crew ? credits.crew : null : null, 'name');
return (
<div>
<section className="details-area">
<PageTitle
pageName='details'
pageTitle='Details'
/>
<ul className="details-content">
<li className="details-left" style={{ backgroundImage: `url('https://image.tmdb.org/t/p/original${poster_path}')` }}></li>
<li className="details-right">
<h2>{title} ({release_date.substr(0, 4)})</h2>
<p className="genres">{genresText}</p>
<p className="description short">{tagline}</p>
<Rating
rating={vote_average}
votesCount={this.numberWithCommas(vote_count)}
/>
<p className="description full">{overview}</p>
<div className="extra">
<FeaturesList
featuresList={featuresList.slice(0, 5)}
linksList={null}
isFavorite={this.isFavorite}
onFavoriteMovieClick={this.handleFavoriteMovieClick}
/>
{youtubeKey && <Trailer
youtubeKey={youtubeKey}
/>}
</div>
</li>
<div className="extra-features">
<FeaturesList
featuresList={featuresList.slice(5, featuresList.length)}
linksList={linksList}
isFavorite={null}
onFavoriteMovieClick={null}
/>
<ProductionsList
productionsList={production_companies}
/>
</div>
</ul>
</section>
<section className="actors-area">
<PageTitle
pageName='actors'
pageTitle='Cast'
/>
<ActorsList
actorsList={actorsList}
/>
</section>
<section className="crew-area">
<PageTitle
pageName='crew'
pageTitle='Crew'
/>
<CrewsList
crewsList={crewsList}
/>
</section>
<ButtonClick
buttonText={'Back'}
buttonTitle={'Back'}
isLoading={false}
onClick={this.handleBackClick}
/>
</div>
);
}
}
Details.propTypes = propTypes;
Details.defaultProps = defaultProps;
const mapStateToProps = (state) => {
return {
movie: state.details.movie,
youtubeKey: state.details.youtubeKey,
credits: state.details.credits,
moviesList: state.main.moviesList,
favoriteMoviesList: state.main.favoriteMoviesList
};
};
const mapDispatchToProps = (dispatch) => {
return {
onLoadMovieDetails: (movieId) => dispatch(detailsActions.loadDetails(movieId)),
onUpdateFavoriteMovies: (request) => dispatch(mainActions.updateFavoriteMovies(request))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Details);
What I already looked in:
Getting The service worker navigation preload request was cancelled before 'preloadResponse' settled
https://learn.microsoft.com/en-us/answers/questions/108004/getting-the-service-worker-navigation-preload-requ.html
https://support.google.com/mail/thread/4055804?hl=en
https://love2dev.com/pwa/service-worker-preload/
I tried to put this on Details.jsx page, but it didn't work:
self.addEventListener('fetch', event => {
event.respondWith(async function () {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse; // Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response; // Else try the network.
return fetch(event.request);
}());
});
self.addEventListener('activate', event => {
event.waitUntil(async function () {
// Feature-detect
if (self.registration.navigationPreload) { // Enable navigation preloads!
console.log('Enable navigation preloads!');
await self.registration.navigationPreload.enable();
} return;
})();
});
How can I solve this issue? Thanks.
Had same error, even my iframe wasn't loading..whatever video you are using from youtube use nocookie/embed in url. It's working for me.
Try changing https://www.youtube.com/watch?v=i8eBBG46H8A to
https://www.youtube-nocookie.com/embed/i8eBBG46H8A
Hope nocookie & embed helps..!!

How does the method menuDataRender() get its input for menuList?

Looking at the following snips from the Ant Design Pro work work, how does the method menuDataRender get its parameters? The reason I ask this, is because I want to modify the signature, and given the current calling method, there does not appear to be any parameters being passed.
The method:
const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
menuList.map(item => {
const localItem = {
...item,
children: item.children ? menuDataRender(item.children) : [],
};
return Authorized.check(item.authority, localItem, null) as MenuDataItem;
});
The caller:
//
// ... code removed for brevity ...
//
return (
<>
<ProLayout
logo={logo}
menuHeaderRender={(logoDom, titleDom) => (
<Link to="/">
{logoDom}
{titleDom}
</Link>
)}
onCollapse={handleMenuCollapse}
menuItemRender={(menuItemProps, defaultDom) => {
if (menuItemProps.isUrl || menuItemProps.children) {
return defaultDom;
}
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
}}
breadcrumbRender={(routers = []) => [
{
path: '/',
breadcrumbName: formatMessage({
id: 'menu.home',
defaultMessage: 'Home',
}),
},
...routers,
]}
itemRender={(route, params, routes, paths) => {
const first = routes.indexOf(route) === 0;
return first ? (
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
) : (
<span>{route.breadcrumbName}</span>
);
}}
footerRender={footerRender}
menuDataRender={menuDataRender} // <--- called here!
formatMessage={formatMessage}
rightContentRender={rightProps => <RightContent {...rightProps} />}
{...props}
{...settings}
>
<Authorized authority={authorized!.authority} noMatch={noMatch}>
{children}
</Authorized>
</ProLayout>
<SettingDrawer
settings={settings}
onSettingChange={config =>
dispatch({
type: 'settings/changeSetting',
payload: config,
})
}
/>
</>
);
};
The ProLayout is the BasicLayout component that comes from the #ant-design/pro-layout package and this component can receive a route prop, which defaults to (GitHub):
route = {
routes: [],
}
The value of the routes key (an empty array by default) is used to call the menuDataRender function (GitHub):
const { routes = [] } = route;
if (menuDataRender) {
renderMenuInfoData = getMenuData(
routes,
menu,
formatMessage,
menuDataRender,
);
}
The expected schema for the elements in the routes array is an array of Route type (GitHub):
export interface MenuDataItem {
authority?: string[] | string;
children?: MenuDataItem[];
hideChildrenInMenu?: boolean;
hideInMenu?: boolean;
icon?: string;
locale?: string;
name?: string;
key?: string;
path?: string;
[key: string]: any;
parentKeys?: string[];
}
export interface Route extends MenuDataItem {
routes?: Route[];
}
Example:
// routes.ts
import { MenuDataItem } from "#ant-design/pro-layout/lib/typings";
const routes : MenuDataItem[] = [
{
path: "/",
name: "Home",
authority: []
},
{
path: "/users",
name: "Users",
authority: ["admin"]
}
];
export default routes;
// app.ts
import React from "react";
import ProLayout, {
MenuDataItem,
BasicLayoutProps as ProLayoutProps,
} from "#ant-design/pro-layout";
import routeList from "./routes";
const Application: React.FC<ProLayoutProps> = (props) => {
const { children } = props;
// or get your route list from where you have it (ex. from a store ...)
// const routeList = useStoreState(state => state.app.routeList);
const route = { routes: routeList };
const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
menuList.map(item => {
const localItem = {
...item,
children: item.children ? menuDataRender(item.children) : [],
};
return Authorized.check(item.authority, localItem, null) as MenuDataItem;
});
return (
<ProLayout
// more props
route={route}
menuDataRender={menuDataRender}
>
{ children }
</ProLayout>
);
}

How to generate snapshot after all life cycle methods have called in React Jest

snapshot file has created before componentDidMount() is being called. In my situation, I fetch data from server inside the componentDidMount(). Based on the results, I draw the table. But in my test case, it doesn't show those received mock results.
Test file
import React from 'react';
import renderer from 'react-test-renderer';
import { fakeRequestLibrary } from '../../../__mocks__/fakeRequestLibrary';
import ReportAsTableView from '../../../components/reports/common/ReportAsTableView';
const FAKE_RESPONSE = {
dataSets: [
{
metadata: {
columns: [
{
name: "username",
label: "username"
},
{
name: "date_created",
label: "date_created"
}
]
},
rows: [
{
date_created: "2010-04-26T13:25:00.000+0530",
username: "daemon"
},
{
date_created: "2017-06-08T21:37:18.000+0530",
username: "clerk"
},
{
date_created: "2017-07-08T21:37:18.000+0530",
username: "nurse"
},
{
date_created: "2017-07-08T21:37:19.000+0530",
username: "doctor"
},
{
date_created: "2017-07-08T21:37:18.000+0530",
username: "sysadmin"
}
]
}
]
};
describe('<ReportAsTableView /> ', () => {
it('renders correctly with success data received from server', () => {
const params = {
"startDate": "2017-05-05",
"endDate": "2017-10-05"
};
var rendered = renderer.create(
<ReportAsTableView reportUUID="e451ae04-4881-11e7-a919-92ebcb67fe33"
reportParameters={params}
fetchData={fakeRequestLibrary('openmrs-fake-server.org', {}, true, FAKE_RESPONSE)} />
);
expect(rendered.toJSON()).toMatchSnapshot();
});
});
Targeted component class
import React, { Component } from 'react';
import { ApiHelper } from '../../../helpers/apiHelper';
import * as ReportConstants from '../../../helpers/ReportConstants';
import ReactDataGrid from 'react-data-grid';
import DataNotFound from './DataNotFound';
import moment from 'moment';
import './ReportAsTableView.css';
class ReportAsTableView extends Component {
constructor(props) {
super();
this.state = {
report: {
definition: {
name: ''
}
},
reportColumnNames: Array(),
reportRowData: Array()
};
this.resolveResponse = this.resolveResponse.bind(this);
this.rowGetter = this.rowGetter.bind(this);
this.init = this.init.bind(this);
}
componentDidMount() {
this.init(this.props.reportParameters);
}
componentWillReceiveProps(nextProps) {
this.init(nextProps.reportParameters);
}
init(params) {
if(this.props.fetchData != null){
//Test Path
this.props.fetchData
.then((response) => {
console.log('>>>>>'+JSON.stringify(response.body));
this.resolveResponse(response.body);
});
}else{
new ApiHelper().post(ReportConstants.REPORT_REQUEST + this.props.reportUUID, params)
.then((response) => {
this.resolveResponse(response);
});
}
}
resolveResponse(data) {
this.setState({ report: data });
this.setState({ reportColumnNames: data.dataSets[0].metadata.columns });
this.setState({ reportRowData: data.dataSets[0].rows });
}
// ... there are some other methods as well
render() {
return (
<div style={{ border: '1px solid black' }}>
{this.getColumns().length > 0 ? (
<ReactDataGrid
columns={this.getColumns()}
rowGetter={this.rowGetter}
rowsCount={this.state.reportRowData.length} />
) : (
<DataNotFound componentName="Report Table"/>
)}
</div>
);
}
}
export default ReportAsTableView;
Snapshot file
// Jest Snapshot v1,
exports[`<ReportAsTableView /> renders correctly with success data received from server 1`] = `
<div
style={
Object {
"border": "1px solid black",
}
}
>
<div
className="NotFoundWrapper"
>
<div
className="attentionSign"
>
<img
src="./warning.png"
width="300"
/>
</div>
<div>
No Data found
<span>
for
Report Table
</span>
</div>
</div>
</div>
`;
Update:
fakeRequestLibrary
import Response from 'http-response-object';
export const fakeRequestLibrary = (requestUrl, requestOptions, shouldPass = true, responseData = null) => {
return new Promise((resolve, reject) => {
if (shouldPass) {
resolve(new Response(200, {}, responseData || { message: `You called ${requestUrl}` }, requestUrl));
} else {
reject(new Response(404, {}, responseData || { message: `The page at ${requestUrl} was not found` }, requestUrl));
}
});
};
Instead of passing an http end point what you can do for fix your problem is changing your init method and passing the data if no data are passed fetch them. Like this
init(params) {
if(this.props.fetchData != null){
this.resolveResponse(this.props.fetchData);
}else{
new ApiHelper().post(ReportConstants.REPORT_REQUEST + this.props.reportUUID, params)
.then((response) => {
this.resolveResponse(response);
});
}
}
Then in your test you will have
var rendered = renderer.create(
<ReportAsTableView reportUUID="e451ae04-4881-11e7-a919-92ebcb67fe33"
reportParameters={params}
fetchData={FAKE_RESPONSE} />
);
expect(rendered.toJSON()).toMatchSnapshot();
This solution works for my own project. It might also work for this question as well, but I haven't tested it. Add an await wait(); statement to wait for the async function in componentDidMount to complete.
const wait = async () => 'foo'; // a dummy function to simulate waiting
describe('<ReportAsTableView /> ', async () => {
it('renders correctly with success data received from server', async () => {
const params = {
startDate: '2017-05-05',
endDate: '2017-10-05',
};
var rendered = renderer.create(
<ReportAsTableView
reportUUID="e451ae04-4881-11e7-a919-92ebcb67fe33"
reportParameters={params}
fetchData={fakeRequestLibrary(
'openmrs-fake-server.org',
{},
true,
FAKE_RESPONSE,
)}
/>,
);
await wait(); // wait for <ReportAsTableView> to finish async data fetch in componentDidMount()
expect(rendered.toJSON()).toMatchSnapshot(); // shall render the component AFTER componentDidMount() is called
});
});

Resources