How to call client-server side render and static generation render - javascript

I want to make static generation for top products with getStaticProps.
now a section of my rendering is not needed to static generation. for example: comments, related products.
full code:
export default function Gift(props) {
let [relatedProducts, setRelatedProducts] = useState([]);
const getRelatedProducts = () => {
api.get(`gift/id/${props.id}/relateds/count/10`).then(res => {
console.log(res.data.data);
setRelatedProducts(res.data.data)
})
}
//called n times. looping !!!
getRelatedProducts();
return (
<GiftProvider value={props}>
<ProductPage/>
<RelatedProducts title="related products" products={relatedProducts}/>
<ProductGeneralProperties/>
<ProductComment/>
</GiftProvider>
);
}
export async function getStaticPaths() {
const gifts = await getTopGifts()
const paths = gifts.map((gift) => ({
params: {slug: gift.slug}
}))
return {paths, fallback: 'blocking'}
}
export async function getStaticProps(context) {
const slug = context.params.slug
const gift = await getGiftWithSlug(slug)
return {
props: gift,
}
}
but with below code my codes renders multi times:
export default function Gift(props) {
let [relatedProducts, setRelatedProducts] = useState([]);
const getRelatedProducts = () => {
api.get(`gift/id/${props.id}/relateds/count/10`).then(res => {
console.log(res.data.data);
setRelatedProducts(res.data.data)
})
}
getRelatedProducts();

You can use useEffect hook to call the api
useEffect(() => {
const getRelatedProducts = () => {
api.get(`gift/id/${props.id}/relateds/count/10`).then(res => {
console.log(res.data.data);
setRelatedProducts(res.data.data)
})
}
getRelatedProducts();
},[])

Related

React recoil, overlay with callback and try finally

I want to render overlay on the long running operations.
Consider I have the following code
let spinnerState = useRecoilValue(overlayState);
return <BrowserRouter>
<Spin indicator={<LoadingOutlined />} spinning={spinnerState.shown} tip={spinnerState.content}>.........</BrowserRouter>
What I do in different components
const [, setOverlayState] = useRecoilState(overlayState);
const onButtonWithLongRunningOpClick = async () => {
Modal.destroyAll();
setOverlayState({
shown: true,
content: text
});
try {
await myApi.post({something});
} finally {
setOverlayState(overlayStateDefault);
}
}
How can I refactor this to use such construction that I have in this onbuttonclick callback? I tried to move it to the separate function, but you cannot use hooks outside of react component. It's frustrating for me to write these try ... finally every time. What I basically want is something like
await withOverlay(async () => await myApi.post({something}), 'Text to show during overlay');
Solution
Write a custom hook that includes both UI and API. This pattern is widely used in a large app but I couldn't find the name yet.
// lib/buttonUi.js
const useOverlay = () => {
const [loading, setLoading] = useState(false);
return {loading, setLoading, spinnerShow: loading };
}
export const useButton = () => {
const overlay = useOverlay();
const someOp = async () => {
overlay.setLoading(true);
await doSomeOp();
/* ... */
overlay.setLoading(false);
}
return {someOp, ...overlay}
}
// components/ButtonComponent.jsx
import { useButton } from 'lib/buttonUi';
const ButtonComponent = () => {
const {spinnerShow, someOp} = useButton();
return <button onClick={someOp}>
<Spinner show={spinnerShow} />
</button>
}
export default ButtonComponent;
create a custom hook that handles the logic for showing and hiding the overlay.
import { useRecoilState } from 'recoil';
const useOverlay = () => {
const [, setOverlayState] = useRecoilState(overlayState);
const withOverlay = async (fn: () => Promise<void>, content: string) => {
setOverlayState({ shown: true, content });
try {
await fn();
} finally {
setOverlayState(overlayStateDefault);
}
};
return withOverlay;
};
You can then use the useOverlay hook in your components
import { useOverlay } from './useOverlay';
const Component = () => {
const withOverlay = useOverlay();
const onButtonWithLongRunningOpClick = async () => {
await withOverlay(async () => await myApi.post({ something }), 'Text to show during overlay');
};
return <button onClick={onButtonWithLongRunningOpClick}>Click me</button>;
};

Not able to mock a function inside useEffect

I have a custom hook as below
export const useUserSearch = () => {
const [options, setOptions] = useState([]);
const [searchString, setSearchString] = useState("");
const [userSearch] = useUserSearchMutation();
useEffect(() => {
if (searchString.trim().length > 3) {
const searchParams = {
orgId: "1",
userId: "1",
searchQuery: searchString.trim(),
};
userSearch(searchParams)
.then((data) => {
setOptions(data);
})
.catch((err) => {
setOptions([]);
console.log("error", err);
});
}
}, [searchString, userSearch]);
return {
options,
setSearchString,
};
};
and I want to test this hook but am not able to mock userSearch function which is being called inside useEffect.
can anybody help?
this is my test
it('should set state and test function', async () => {
const wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
)
const { result } = renderHook(
() => useUserSearch(),
{ wrapper }
)
await act(async () => {
result.current.setSearchString('abc5')
})
expect(result.current.options).toEqual(expected)
})
useUserSearchMutation
import {createApi, fetchBaseQuery} from '#reduxjs/toolkit/query/react';
export const userSearchAPI = createApi({
reducerPath: 'userSearchResult',
baseQuery: fetchBaseQuery({baseUrl: process.env.REACT_APP_BASE_URL}),
tagTypes: ['Users'],
endpoints: build => ({
userSearch: build.mutation({
query: body => ({url: '/org/patient/search', method: 'POST', body}),
invalidatesTags: ['Users'],
}),
}),
});
export const {useUserSearchMutation} = userSearchAPI;
Because it's a named export you should return an object in the mock
it("should set state and test function", async () => {
jest.mock("./useUserSearchMutation", () => ({
useUserSearchMutation: () => [jest.fn().mockResolvedValue(expected)],
}));
const wrapper = ({ children }) => (
...
});
I have created a smaller example based on your code, where I am mocking a hook inside another hook.
hooks/useUserSearch.js
import { useEffect, useState } from "react";
import useUserSearchMutation from "./useUserSearchMutation.js";
const useUserSearch = () => {
const [text, setText] = useState();
const userSearch = useUserSearchMutation();
useEffect(() => {
const newText = userSearch();
setText(newText);
}, [userSearch]);
return text;
};
export default useUserSearch;
hooks/useUSerSearchMutation.js
I had to move this to its own file to be able to mock it when it was called
inside of the other hook.
const useUserSearchMutation = () => {
return () => "Im not mocked";
};
export default useUserSearchMutation;
App.test.js
import { render } from "react-dom";
import useUserSearch from "./hooks/useUserSearch";
import * as useUserSearchMutation from "./hooks/useUserSearchMutation";
import { act } from "react-dom/test-utils";
let container;
beforeEach(() => {
// set up a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
document.body.removeChild(container);
container = null;
});
function TestComponent() {
const text = useUserSearch();
return <div>{text}</div>;
}
test("should mock userSearch", async () => {
const mockValue = "Im being mocked";
jest
.spyOn(useUserSearchMutation, "default")
.mockImplementation(() => () => mockValue);
act(() => {
render(<TestComponent />, container);
});
expect(container.textContent).toBe(mockValue);
});

Export a function which has an imported axios response data

Currently I have two files, one is being used to fetch and get the data response. The other js file is to import these results in a function. I would like to export this last function so I could use it in other files.
api.js
import axios from 'axios';
const url = 'data/data.json';
const cars = 'data/cars.json';
export const fetchData = () => {
return axios.get(url)
.then(response => {
return response.data
})
}
export const fetchCars = () => {
return axios.get(cars)
.then(response => {
return response.data
})
}
import.js
See: export const details
import { fetchData, fetchCars } from './api';
let fetchingData = fetchData();
let fetchingCars = fetchCars();
// I want to export the below functionality:
fetchingData.then((result) => {
// I will be doing stuff here
console.log(result);
})
export const details = () => {
fetchingCars.then((result) => {
// I will be doing stuff here
console.log(result);
})
}
// And be able to console.log the results out of that exported function. Since I will need to update DOM values based on the API results.
console.log(details);
importCars.js
import { details } from './import'
function example() {
// do something
details();
}
Just change the fetchData in api.js as below,
export const fetchData = () => {
return axios.get(url)
}
And in import.js use result.data instead of result while logging like below,
fetchingData.then((result) => {
// I will be doing stuff here
console.log(result.data);
})

How can I run multiple queries on load of functional component using UseEffect and get result in render method?

I have the following functional component where, on load of the component, it needs to loop through an array and run some async queries to populdate a new array I want to display in render method.
import React, { useEffect, useState, useContext } from 'react';
import { AccountContext } from '../../../../providers/AccountProvider';
import { GetRelationTableCount } from '../../../../api/GetData';
import { getTableAPI } from '../../../../api/tables';
const RelatedRecordsPanel = (props) => {
const { userTokenResult } = useContext(AccountContext);
const { dataItem } = props;
const [relatedTableItems, setRelatedTableItems] = useState([]);
useEffect(() => {
const tempArray = [];
const schema = `schema_${userTokenResult.zoneId}`;
const fetchData = async () => {
return Promise.all(
dataItem.tableinfo.columns
.filter((el) => el.uitype === 1)
.map(async (itm) => {
const tblinfo = await getTableAPI(itm.source.lookuptableid);
const tblCount = await GetRelationTableCount(
dataItem.tableid,
itm.source.jointable,
schema,
);
const TableIconInfo = { name: tblinfo.name, icon: tblinfo.icon, count: tblCount };
tempArray.push(TableIconInfo);
})
);
};
fetchData();
setRelatedTableItems(tempArray)
}, []);
return (
<div>
{relatedTableItems.length > 0 ? <div>{relatedTableItems.name}</div> : null}
</div>
);
};
In the above code, the queries run correctly and if I do a console.log in the loop, I can see if fetches the data fine, however, the array is always [] and no data renders. How do I write this async code such that it completes the queries to populate the array, so that I can render properly?
Thx!
You aren't using the return value of the Promise.all and since all your APIs are async, the tempArray is not populated by the time you want to set it into state
You can update it like below by waiting on the Promise.all result and then using the response
useEffect(() => {
const schema = `schema_${userTokenResult.zoneId}`;
const fetchData = async () => {
return Promise.all(
dataItem.tableinfo.columns
.filter((el) => el.uitype === 1)
.map(async (itm) => {
const tblinfo = await getTableAPI(itm.source.lookuptableid);
const tblCount = await GetRelationTableCount(
dataItem.tableid,
itm.source.jointable,
schema,
);
const TableIconInfo = { name: tblinfo.name, icon: tblinfo.icon, count: tblCount };
return TableIconInfo;
})
);
};
fetchData().then((res) => {
setRelatedTableItems(res);
});
}, []);

Curried function causing error but works if not curried

export const postMoviePopular = url = dispatch => {
const data = axios.get(url);
dispatch(saveMoviePopular(data));
}
const saveMoviePopular = payload => {
return {
type: POST_MOVIE_POPULAR,
payload
}
}
This is my code which does not work because it is curried, but if it IS NOT curried like below, it works, why is this??
export const postMoviePopular = url => {
return dispatch => {
const data = axios.get(url);
dispatch(saveMoviePopular(data));
}
}
I'm wondering if it has anything to do with the way I am calling mapDispatchToProps???
componentDidMount() {
this.props.postMDBConfig(`https://api.themoviedb.org/3/configuration?api_key=${this.props.apiKey}`);
this.props.postMoviePopular(`https://api.themoviedb.org/3/movie/popular?api_key=${this.props.apiKey}&language=en-US&page=1&region=US`)
}
const mapDispatchToProps = (dispatch) => {
return {
postMDBConfig: url => dispatch(postMDBConfig(url)),
postMoviePopular: url => dispatch(postMoviePopular(url))
}
}
You need a return statement. And another arrow.
export const postMoviePopular = url => dispatch => {
const data = axios.get(url);
return dispatch(saveMoviePopular(data));
}

Categories

Resources