remix passing args to async server side functions - remix

I am actually a really beginner with this stuff so I beg your pardon for my (silly) questions.
I want to use async functions inside tsx pages, specifically those functions are fetching calls from shopify to get data and ioredis calls to write and read some data.
I know that remix uses action loader functions, so to manage shopify calls I figured out this
export const loader: LoaderFunction = async ({ params }) => {
return json(await GetProductById(params.id as string));
};
async function GetProductById(id: string) {
const ops = ...;
const endpoint = ...;
const response = await fetch(endpoint, ops);
const json = await response.json();
return json;
};
export function FetchGetProductById(id: number) {
const fetcher = useFetcher();
useEffect(() => {
fetcher.load(`/query/getproductid/${id}`);
}, []);
return fetcher.data;
}
with this solution I can get the data whenever I want just calling FetchGetProductById, but my problem is that I need to send more complex data to the loader (like objects)
How may I do that?

In Remix, the loader only handles GET requests, so data must be in the URL, either via params or searchParams (query string).
If you want to pass data in the body of the request, then you'll need to use POST and create an action.
NOTE: Remix uses FormData and not JSON to send data. You will need to convert your JSON into a string.
export const action = async ({ request }: ActionArgs) => {
const formData = await request.formData();
const object = JSON.parse(formData.get("json") as string);
return json(object);
};
export default function Route() {
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.state !== 'idle' || fetcher.data) return;
fetcher.submit(
{
json: JSON.stringify({ a: 1, message: "hello", b: true }),
},
{ method: "post" }
);
}, [fetcher]);
return <pre>{JSON.stringify(fetcher.data, null, 2)}</pre>
}

Related

Jest mockResolvedValue return undefined with multiple args

I am trying to write a unit test for my NodeJs code. I am mocking my API call by using mockResolvedValue.
This is my unit test:
const { search } = require("../src/utils");
jest.mock("axios");
test("Should find an user", async () => {
axios.get.mockResolvedValue({
data: [
{
userId: 1,
name: "Mike",
},
],
});
const phone = "123456789";
const token = "ItIsAFakeToken"
const name = await search(phone, token);
expect(name).toEqual("Mike");
});
And this is my search function
const searchContact = async (phone, token) => {
const config = {
method: "get",
url: "https://userdatabase/api/search",
token,
params: {
phone
},
};
const response = await axios(config);
return response.name;
}
It returned me "undefined" of response, However, if I change my API call to the below code without using config parameter, I can get the expected data. The thing is I need to pass several args in the real code.
const response = await axios.get("https://userdatabase/api/search");
Please help. Thank you.
I figured it out the reason.
In my unit test file, I use axios.get.mockResolvedValue, it should be axios.mockResolvedValue (remove get). Since I don't use axios.get in the method which are tested.

Download files before build in gatsby wordpress

I have a client that im working with who needs his pdfs to be readable in browser and the user doesn't need to download them first and it turned out to not be an option to do it through Wordpress so I thought I can download them in gatsby before build everytime if they don't already exist and I was wondering if this is possible.
I found this repo: https://github.com/jamstack-cms/jamstack-ecommerce
that shows a way to do it with this code:
function getImageKey(url) {
const split = url.split('/')
const key = split[split.length - 1]
const keyItems = key.split('?')
const imageKey = keyItems[0]
return imageKey
}
function getPathName(url, pathName = 'downloads') {
let reqPath = path.join(__dirname, '..')
let key = getImageKey(url)
key = key.replace(/%/g, "")
const rawPath = `${reqPath}/public/${pathName}/${key}`
return rawPath
}
async function downloadImage (url) {
return new Promise(async (resolve, reject) => {
const path = getPathName(url)
const writer = fs.createWriteStream(path)
const response = await axios({
url,
method: 'GET',
responseType: 'stream'
})
response.data.pipe(writer)
writer.on('finish', resolve)
writer.on('error', reject)
})
}
but It doesn't seem to work if i put it in my createPages and i cant use it outside it either because i don't have access to graphql to query the data first.
any idea how to do this?
WordPress source example is defined as async:
exports.createPages = async ({ graphql, actions }) => {
... so you can already use await to download your file(-s) just after querying data (and before createQuery() call). It should (NOT TESTED) be as easy as:
// Check for any errors
if (result.errors) {
console.error(result.errors)
}
// Access query results via object destructuring
const { allWordpressPage, allWordpressPost } = result.data
const pageTemplate = path.resolve(`./src/templates/page.js`)
allWordpressPage.edges.forEach(edge => {
// for one file per edge
// url taken/constructed from some edge property
await downloadImage (url);
createPage({
Of course for multiple files you should use Promise.all to wait for [resolving] all [returned promise] downloads before creating page:
allWordpressPage.edges.forEach(edge => {
// for multiple files per edge(page)
// url taken/constructed from some edge properties in a loop
// adapth 'paths' of iterable (edge.xxx.yyy...)
// and/or downloadImage(image) argument, f.e. 'image.someUrl'
await Promise.all(
edge.node.someImageArrayNode.map( image => { return downloadImage(image); }
);
createPage({
If you need to pass/update image nodes (for components usage) you should be able to mutate nodes, f.e.:
await Promise.all(
edge.node.someImageArrayNode.map( image => {
image["fullUrl"] = `/publicPath/${image.url}`;
return downloadImage(image.url); // return Promise at the end
}
);
createPage({
path: slugify(item.name),
component: ItemView,
context: {
content: item,
title: item.name,
firstImageUrl: edge.node.someImageArrayNode[0].fullUrl,
images: edge.node.someImageArrayNode

Pass query from Link to server, first time load query value undefined, after reload get correct query

I try to create some API to external adobe stock.
Like in the title, first time i get query from Link router of undefined, but after reload page it work correctly. My
main page
<Link
href={{
pathname: "/kategoria-zdjec",
query: images.zdjecia_kategoria
}}
as={`/kategoria-zdjec?temat=${images.zdjecia_kategoria}`}
className={classes.button}>
</Link>
and my server
app
.prepare()
.then(() => {
server.get("/kategoria-zdjec", async (req, res) => {
const temat = await req.query.temat;
console.log(temat)
const url = `https://stock.adobe.io/Rest/Media/1/Search/Files?locale=pl_PL&search_parameters[words]=${temat}&search_parameters[limit]=24&search_parameters[offset]=1`;
try {
const fetchData = await fetch(url, {
headers: { ... }
});
const objectAdobeStock = await fetchData.json();
res.json(objectAdobeStock);
const totalObj = await objectAdobeStock.nb_results;
const adobeImages = await objectAdobeStock.files;
} catch (error) {
console.log(error);
}
});
and that looks like getInitialProps on page next page
Zdjecia.getInitialProps = async ({req}) => {
const res = await fetch("/kategoria-zdjec");
const json = await res.json();
return { total: json.nb_results, images: json.files };
}
I think it is problem due asynchronous.
I think this might be due to the fact that you are using fetch which is actually part of the Web API and this action fails when executed on server.
You could either use isomorphic-fetch which keeps fetch API consistent between client and server, or use node-fetch when fetch is called on the server:
Zdjecia.getInitialProps = async ({ req, isServer }) => {
const fetch = isServer ? require('node-fetch') : window.fetch;
const res = await fetch("/kategoria-zdjec");
const json = await res.json();
return { total: json.nb_results, images: json.files };
}
This problem is solved, the issue was in another part of my app, directly in state management, just created new variables, and pass to link state value.

How to mock variable responses based on the url when fetch(url) is called using the Jest testing framework?

I have a module that looks like follows:
calculate-average.js
const fetch = require('node-fetch')
const stats = require('stats-lite')
const BASE_URL = 'https://www.example.com/api'
const calculateAverage = async(numApiCalls) => {
const importantData = []
for (let i = 0; i < numApiCalls; i++) {
const url = `${BASE_URL}/${i}` // will make requests to https://www.example.com/api/0, https://www.example.com/api/1 and so on....
const res = await fetch(url)
const jsonRes = await res.json()
importantData.push(jsonRes.importantData)
}
return stats.mean(importantData)
}
module.exports = calculateAverage
I tried testing it along the following lines but I am clearly way off from the solution:
calculate-average.test.js
const calculateAverage = require('../calculate-average')
jest.mock(
'node-fetch',
() => {
return jest.fn(() => {})
}
)
test('Should calculate stats for liquidation open interest delatas', async() => {
const stats = await calculateAverage(100) // Should make 100 API calls.
console.log(stats)
})
What I need to do is the following:
Be able to specify custom varied responses for each API call. For example, I should be able to specify that a call to https://www.example.com/api/0 returns { importantData: 0 }, a call to https://www.example.com/api/1 returns { importantData: 1 } and so on...
If a request is made to a url that I have not specified a response for, a default response is provided. For example if a response is made to https://www.example.com/api/101, then a default response of { importantData: 1000 } is sent.
I would preferably like to do this only using Jest without depending on modules like mock-fetch and jest-mock-fetch. However, if the solution without using is way too complex, then I would be happy to use them. Just don't want to create unnecessary dependencies if not required.
Sure you can! You can use mock function mockResolvedValueOnce method to return a result for a specific call and mockResolvedValue to return the default result.
jest.mock('node-fetch', () => {
const generateResponse = (value) => {
return { json: () => ({ importantData: value }) };
};
return jest
.fn()
.mockResolvedValue(generateResponse(1000)) // default response
.mockResolvedValueOnce(generateResponse(0)) // response for first call
.mockResolvedValueOnce(generateResponse(1)) // response for second call
.mockResolvedValueOnce(generateResponse(2)); // response for third call
});
Note that we are returning an object with the json property so that it returns the json data when you call res.json() in calculate-average.js.
If you want to return a specific response based on the url parameter, you will have to mock the desired behaviour in the returned mock function for node-fetch. The following example will mock the returned value so that for URLs where the counter is greater than 100 it will return 1000. Otherwise, it will return the same value present in the url:
jest.mock('node-fetch', () => {
return jest.fn((url) => {
// Get and parse the URL parameter.
const value = parseInt(url.split('/').slice(-1)[0], 10);
return Promise.resolve({
json: () => ({ importantData: value > 100 ? 1000 : value })
});
});
});

Getting data from external API before render

I have an application based on the React Starter Kit.
Every page have a fetch function that getting data from API in componentDidMount lifecycle.
I want to get data first and then render page with data and return it to the client. UX in my case no matter.
I know that RSK is isomorphic, I'm ready to change boilerplate or create my own. But I do not understand how to fetch data from API before render page(I mean how to tell express server what data requires).
How App fetching data now:
example_page.js:
import getBooks from 'queries/getAllBooks';
...
class IdTag extends React.Component {
componentDidMount(){
this.getBooks();
}
getBooks() => {
const request = getBooks();
request
.then(...)
}
}
getAllBooks.js:
import doGet from './doGet';
let result = '';
const request = async () => {
const reqUrl = '/api/books/';
result = await doGet(reqUrl);
return result;
};
export default request;
doGet.js:
const request = async reqUrl => {
let requestResult = null;
const doQuery = async () => {
const response = await fetch(reqUrl, {
method: 'GET',
});
const result = await response.json();
result.status = response.status;
return result;
};
requestResult = await doQuery();
return requestResult
}
...
export default request;
server.js:
...
app.get('/api/*', async (req, res) => {
const newUrl = config.gate.URL + req.url.replace('/api', '');
const accessToken = req.cookies.access_token;
const response = await nodeFetch(newUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const result = await response.json();
res.status(response.status);
res.json(result);
});
...
If each page has api calls, then Its better to use redux and redux saga. Purpose of redux saga is to handle api calls. It will process the actions in a Q. The moment u call api using fetch method, create below actioncreators
1) InitialLoading(true)
2) Fetch api call action creator
3) Based on success, error create action creator to store fetch method output data in store
4) InitialLoading(false)
You could simply set a flag when you begin your fetch, and while it's fetching return null instead of rendering. Something like:
flag = true;
request = getBooks();
request.then(flag = false);
and then:
render(){
if (flag){
return null;
} else {
return this.view;
}
}

Resources