React router push to history and preserve relative path - react-router

I am using react router and I have the following code:
let history = useHistory();
let goToReviewPage = () => history.push(`review/${productId}`);
My current url is: /foo/bar and calling goToReviewPage() will redirect me to
/foo/review/${productId} instead of /foo/bar/review/${productId}
I am not sure how to set the base path will pushing the history.

You can use React Router's match.url. Example:
const Component = ({ productId }) => {
const match = useRouteMatch();
const history = useHistory();
const handleClick = () => {
history.push(`${match.url}/review/${productId}`);
};
return (
<button onClick={handleClick}>Click me</button>
);
};

One way is to use window.location to obtain the current path.
For example:
history.push(window.location.pathname + '/' + `review/${productId}`);
window.location api

Related

How do I write an async request to get a markdown file's content? Svelte

I'm having a great time building my blog with Svelte, but I'm switching the structure to to be accessed through a JSON API.
Right now it's easy to get the markdown metadata and path, but I'd love to also get the content.
How would I modify this posts.json.js file to also get the content?
const allPostFiles = import.meta.glob('../blog/posts/*.md')
const iterablePostFiles = Object.entries(allPostFiles)
const allPosts = await Promise.all(
iterablePostFiles.map(async ([path, resolver]) => {
const { metadata } = await resolver()
const postPath = path.slice(2, -3)
return {
meta: metadata,
path: postPath
}
})
)
const sortedPosts = allPosts.sort((a, b) => {
return new Date(b.meta.date) - new Date(a.meta.date)
})
return {
body: sortedPosts
}
Install and enable the vite-plugin-markdown
// svelte.config.js
import { plugin as markdown, Mode } from "vite-plugin-markdown";
/** #type {import('#sveltejs/kit').Config} */
export default {
kit: {
vite: {
plugins: [markdown({ mode: Mode.HTML })],
},
},
};
then the content will be available as html and frontmatter data as attributes
iterablePostFiles.map(async ([path, resolver]) => {
const { attributes, html } = await resolver();
return {
attributes,
html,
path: path.slice(2, -3),
};
})
(I suggest adding the metadata into the markdown files via frontmatter )
The answer above works perfectly, but it also works to tweak the API with this code:
const allPosts = await Promise.all(
iterablePostFiles.map(async ([path, resolver]) => {
const { metadata } = await resolver()
// because we know every path will start with '..' and end with '.md', we can slice from the beginning and the end
const postPath = path.slice(2, -3)
const post = await resolver()
const content = post.default.render()
return {
meta: metadata,
path: postPath,
text: content
}
})
)
The important addition is this:
const post = await resolver()
const content = post.default.render()
using these variable chains to avoid using the JS reserved word default.

Please tell me what a unique selector is set on puppeteer, when elements have duplicate query selector

My Html code has Button-tags that have same id "hoge".
If you get the selector from the Chrome Dev Tool, it will be the same for both "#hoge".
<html>
<body>
<button id="hoge">Hoge</button>
<div class="shadow">
#shadow-root (open)
<button id="hoge">Hoge</button>
</div>
</body>
</html>
I want to get element of button-tag in shadow dom with puppeteer.
But, my javascript code gets element of 1st button.
const element = page.waitForSelector("pierce/#hoge");
This is not what I want.
I'm guessing it's because you didn't specify a unique selector, but i don't know what is unique selector for puppeteer.
If you know how to solve this problem, please let me know.
Long story short
I work with puppeteer a lot and wanted this knowlegde to be in my bag. One way to select a shadow Element is by accessing the parent DOM Node's shadowRoot property. The answer is based on this article.
Accessing Shadow Root property
For your html example this does the trick:
const button = document.querySelector('.shadow').shadowRoot.querySelector('#hoge')
waiting
Waiting though is a little more complicated but can be acquired using page.waitForFunction().
Working Sandbox
I wrote this full working sandbox example on how to wait for a certain shadowRoot element.
index.html (located in same directory as app.js)
<html>
<head>
<script>
// attach shadowRoot after 6 seconds for emulating waiting..
setTimeout(() => {
const btn = document.getElementById('hoge')
const container = document.getElementsByClassName('shadow')[0]
const shadowRoot = container.attachShadow({
mode: 'open'
})
shadowRoot.innerHTML = `<button id="hoge" onClick="doStuff()">hoge2</button>`
console.log('attached!.')
}, 6000)
function doStuff() {
alert('shadow button clicked!')
}
</script>
</head>
<body>
<button id="hoge">Hoge</button>
<div class="shadow">
</div>
</body>
</html>
app.js (located in same directory as index.html)
var express = require('express')
var { join } = require('path')
var puppeteer = require('puppeteer')
//utility..
const wait = (seconds) => {
console.log('waiting', seconds, 'seconds')
return new Promise((res, rej) => {
setTimeout(res, seconds * 1000)
})
}
const runPuppeteer = async() => {
const browser = await puppeteer.launch({
defaultViewport: null,
headless: false
})
const page = await browser.newPage()
await page.goto('http://127.0.0.1:5000')
await wait(3)
console.log('page opened..')
// only execute this function within a page context!.
// for example in page.evaluate() OR page.waitForFunction etc.
// don't forget to pass the selector args to the page context function!
const selectShadowElement = (containerSelector, elementSelector) => {
try {
// get the container
const container = document.querySelector(containerSelector)
// Here's the important part, select the shadow by the parentnode of the
// actual shadow root and search within the shadowroot which is like another DOM!,
return container.shadowRoot.querySelector(elementSelector)
} catch (err) {
return null
}
}
console.log('waiting for shadow elemetn now.')
const containerSelector = '.shadow'
const elementSelector = '#hoge'
const result = await page.waitForFunction(selectShadowElement, { timeout: 15 * 1000 }, containerSelector, elementSelector)
if (!result) {
console.error('Shadow element not found..')
return
}
// since waiting succeeded we can get the elemtn now.
const element = await page.evaluateHandle(selectShadowElement, containerSelector, elementSelector)
try {
// click the element.
await element.click()
console.log('clicked')
} catch (err) {
console.log('failed to click..')
}
await wait(10)
}
var app = express()
app.get('/', (req, res) => {
res.sendFile(join(__dirname, 'index.html'))
})
app.listen(5000, '127.0.0.1', () => {
console.log('listening!')
runPuppeteer()
})
Start example
$ npm i express puppeteer
$ node app.js
Make sure to use headless:false option to see what's happening.
The application does this:
start a small express server only serving index.html on /
open puppeteer after server has started and wait for the shadow root element to appear.
Once it appeared, it gets clicked and an alert() is shown. => success!
Browser Support
Tested with chrome.
Cheers ' ^^

I'm getting a 404 error when trying to render my results for my API Hack assignment

I'm working on an API Hack assignment for my class with Thinkful and my issue has been that I've been trying to make a call to spoonacular's food api and render the results onto the DOM. However, when I try to do that, All I get in return is a 404 error. I'm wondering if i did something wrong or is some unforeseen problem that is beyond my control?
I've already look at manually typing the composed URL and postman as well.
function queryParams(params) {
const queryItems = Object.keys(params).map(key => `${encodeURIComponent(key)}= ${encodeURIComponent(params[key])}`)
return queryItems.join('&');
}
function displayResults(responseJson){
console.log(responseJson);
$('#results-list').empty();
for(let i = 0; i < responseJson.results.length; i++){
$('#results-list').append(
`<li><h3>${responseJson.results[i].id},${responseJson.results[i].protein}</h3>
<p>By ${responseJson.results[i].calories}</p>
<img src='${responseJson.results[i].image}'>
</li>`
)};
$('#results').removeClass('hidden');
};
function getRecipe(query,maxResults,){
const params ={
q:query,
number: maxResults,
};
const queryString = queryParams(params)
const url = searchUrl+'?'+ queryString +'?apiKey='+ apikey;
console.log(url);
fetch(url,option)
.then(response =>{
if(response.ok){
return response.json();
}
throw new Error(response.statusText);
})
.then(response => console.log(responseJson))
.catch(err =>{
$('#js-error-message').text(`Something went wrong: ${err.message}`);
});
}
function watchForm() {
$('form').submit(event => {
event.preventDefault();
const searchRecipe = $('.js-search-recipe').val();
const maxResults = $('.js-max-results').val();
getRecipe(searchRecipe, maxResults);
});
}
$(watchForm);
It looks like you have a couple issues:
First, you're constructing an invalid url:
const url = searchUrl+'?'+ queryString +'?apiKey='+ apikey;
notice the 2 ?s
Also, when you're constructing the query params, you're adding a space between the = and the value of your param
${encodeURIComponent(key)}= ${encodeURIComponent(params[key])}
If you're using the correct path and a valid API key, fixing those things may be enough to make it work.

Empty GET response on requesting JSON file's content | koa2

I am a new to koa2, and I trying to GET the contents of a JSON file with koa2
app.use( async ( ctx ) => {
let url = ctx.request.url;
if (url == "list") {
let res = ctx.request.get('http://domain/hello.json');
ctx.body = res.body;
}
})
The JSON file hello.json looks like the following:
{"da": "1212", "dad": "12addsf12"}
I want the route /list to return the contents of hello.json, however, the response is empty. What do I do?
Update:
Change the following lines of code:
let res = ctx.request.get('http://domain/hello.json');
ctx.body = res.body;
to:
let res = ctx.get('http://domain/hello.json');
ctx.body = res;
You should get the content now.
Koa by itself does not support routing, only middleware, you need to have a router middleware for that, try koa-router.
Your app would look something like
const route = require('koa-route')
app.use(route.get('/list', ctx => {
// Route handling logic
});
Also note that ctx.get is an alias for ctx.request.get which returns header information.
This may not be Koa's way of doing things, but this is what I tried and worked for me (complete code for noobs like me):
// jshint ignore: start
const koa2 = require("koa2");
const router = require('koa-simple-router');
const app = new koa2();
const request = require('request-promise-native');
// response
app.use(router(_ => {
_.get('/list', async (ctx) => {
const options = {
method: 'GET',
uri: 'http://www.mocky.io/v2/5af077a1310000540096c672'
}
await request(options, function (error, response, body) {
// I am leaving out error handling on purpose,
// for brevity's sake. You must in your code.
ctx.body = body;
})
});
}));
app.listen(3000);
And, like what J Pichardo's answer points out, Koa by itself does not support routing. You need to use some routing middleware.

How to use cheerio to get the URL of an image on a given page for ALL cases

right now I have a function that looks like this:
static getPageImg(url) {
return new Promise((resolve, reject) => {
//get our html
axios.get(url)
.then(resp => {
//html
const html = resp.data;
//load into a $
const $ = cheerio.load(html);
//find ourself a img
const src = url + "/" + $("body").find("img")[0].attribs.src;
//make sure there are no extra slashes
resolve(src.replace(/([^:]\/)\/+/g, "$1"));
})
.catch(err => {
reject(err);
});
});
}
this will handle the average case where the page uses a relative path to link to an image, and the host name is the same as the URL provided.
However,
most of the time the URL scheme will be more complex, like for example the URL might be stackoverflow.com/something/asdasd and what I need is to get stackoverflow.com/someimage link. Or the more interesting case where a CDN is used and the images come from a separate server. For example if I want to link to something from imgur ill give a link like : http://imgur.com/gallery/epqDj. But the actual location of the image is at http://i.imgur.com/pK0thAm.jpg a subdomain of the website. More interesting is the fact that if i was to get the src attribute I would have: "//i.imgur.com/pK0thAm.jpg".
Now I imagine there must be a simple way to get this image, as the browser can very quickly and easily do a "open window in new tab" so I am wondering if anyone knows an easy way to do this other than writing a big function that can handle all these cases.
Thank you!
This is my function that ended up working for all my test cases uysing nodes built in URL type. I had to just use the resolve function.
static getPageImg(url) {
return new Promise((resolve, reject) => {
//get our html
axios.get(url)
.then(resp => {
//html
const html = resp.data;
//load into a $
const $ = cheerio.load(html);
//find ourself a img
const retURL = nodeURL.resolve(url,$("body").find("img")[0].attribs.src);
resolve(retURL);
})
.catch(err => {
reject(err);
});
});
}