Not sure what the issue is but my Navigo router is duplicating routes.
The Router:
this.Navigo.hooks({
before: (done, params) => {
// some tomfoolery
done();
}
});
this.Navigo.on({
'/:region/travel': (params) => {
// import Travel module
// some nonsense
},
'/:region/travel/car': (params) => {
// import TravelCar module
// some nonsense
}
)};
this.Navigo.resolve();
The Problem
this.Navigo.navigate('/london/travel/car');
Navigating to /london/travel/car is also triggering the route for /london/travel and thus causing all kinds of twaddle.
Is this standard behaviour? If not, what could be wrong?
I could rewrite the routes so they don't collide e.g. /london/travel-by-car, but I really don't want to if I can avoid it.
UPDATE 1:
I tried switching the order of routes but makes no difference. I did this by declaring the longest travel routes first, /:region/travel/car, and the smallest, /:region/travel, last.
UPDATE 2:
The more I look into this, the more I'm convinced this cannot be achieved with Navigo. Navigo do not support nested routes. If somebody could confirm that my routes are in fact 'nested', I will use an alternative routing library that does support them.
My code is a little different, but works the way you expect:
var router = new Navigo("/");
var render = (content) => (document.querySelector("#app").innerHTML = content);
router
.on('/:id', ({ data }) => {
setuserId(data.id)
if (verifiedUser) {
console.log("User verified");
} else {
console.log("User NOT verified");
}
rendertemplate(userDataURL(), "#landing-template", "#app")
})
.on('/:id/q', ({ data }) => {
// Example - flaging a send with 's' from 'SMS', perhaps a diff flow?
setuserId(data.id)
rendertemplate(userDataURL(), "#landing-template", "#app")
console.log("Source was a QRcode");
})
.on('/:id/q/t', ({ data }) => {
// Example - flaging a send with 's' from 'SMS', perhaps a diff flow?
setuserId(data.id)
rendertemplate(userDataURL(), "#landing-template", "#app")
console.log("Source was a QRcode in a Train");
})
This will give me a single discreet ".. verified"/"Source was a QRcode"/"Source was a QRcode in a Train" console.log response.
B
Related
So, you know when you access a view with django rest api on the browser, you get an html page, and when you send something like an ajax request, you get the json? I'm trying to figure out how to mess with the proxy setting for vite, but I can't find a single decent documentation around it. I want to redirect '/' to 'http://localhost:8000/api', but there's really weird behavior going on.
If I have a route on localhost:8000/api, I can do:
//vite.config.js
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
//Focus here
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => { console.log(path); return path.replace('/^\/api/', '') }
}
}
}
})
//todo-component.vue
export default {
data() {
return {
todos: []
}
},
components: {
TodoElement
},
beforeCreate() {
//Focus here as well
this.axios.get('/api').then((response) => {
this.todos = response.data
})
.catch((e) => {
console.error(e)
})
}
}
This will return the json response as expected. However, if I try to make it so that '/' routes to 'localhost:8000/api/', like this:
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
//change here
'/': {
target: 'http://localhost:8000/api',
changeOrigin: true,
rewrite: (path) => { console.log(path); return path.replace('/^\/api/', '') }
}
}
}
})
import TodoElement from "./todo-element.vue"
export default {
data() {
return {
todos: []
}
},
components: {
TodoElement
},
beforeCreate() {
//change here
this.axios.get('/').then((response) => {
this.todos = response.data
})
.catch((e) => {
console.error(e)
})
}
}
It just spews out the html version of the api view, but with no styling, with a bunch of errors
No idea what to do. If someone could explain how this proxy works, i'd really love it. I don't want to keep writing "api/", and it'd be really valuable if I can manage to understand how this works.
You are a bit confusing things and I will try to show you why.
If you redirect root path / to /api, every request sent to your app running at http://localhost:3000 will be forwarded to http://localhost:8000/api. It mean that you will not be able to serve anything from the running app, but you will get an answer from the configured endpoint (localhost:8000/api) for every request.
To understand easily what is going on, keep in mind that this vite config option (server.proxy) act like a reverse proxy. As example, I take the favicon.ico resource of your app.
With your current configuration, when from your browser you try to access your app, the /favicon.ico (and all other resources) is then loaded from http://localhost:8000/api/favicon.ico and not anymore from your app running at http://localhost:3000/favicon.ico.
This explain all the errors in the console. Again, for example, /static/rest_framework is loaded from http://localhost:8000/api/ and not http://localhost:3000/.
The documentation is quite clear, it's just a matter of understanding what a http-proxy is. To get more information you can head to https://github.com/http-party/node-http-proxy#core-concept
I am getting this error in Next.js:
Error: The provided 'href' (/subject/[subject]) value is missing query values (subject) to be interpolated properly. Read more: https://err.sh/vercel/next.js/href-interpolation-failed`.
I have a dynamic page set up as /subject/[subject].tsx. Now in my navigation I have:
<Link href={'/subject/${subject}'} passHref><a>{subject}</a></Link>
It works fine when I access the page with different paths but when I am pressing on a button on the page it throws the error above which I imagine is because the component rerenders. If you go to the page in the error it says: Note: this error will only show when the next/link component is clicked not when only rendered.
I have tried to look for a solution and I tried doing:
<Link href={{pathname: '/subject/[subject]', query: {subject: subject}}}></Link>
but nothing changed. I read the docs and it seems that the as prop is only an optional decorator that is not used anymore so I fail to see how that can help me.
I got the same issue when trying to redirect user to locale. I did it in useEffect. After investigate I discovered that on first render router.query is empty, so it's missing required field id. I fix it by using router.isReady
export const useUserLanguageRoute = () => {
const router = useRouter()
useEffect(() => {
const {
locales = [],
locale,
asPath,
defaultLocale,
pathname,
query,
isReady // here it is
} = router
const browserLanguage = window.navigator.language.slice(0, 2)
const shouldChangeLocale =
isReady && // and here I use it
locale !== browserLanguage
&& locale === defaultLocale
&& locales.includes(browserLanguage)
if (shouldChangeLocale) {
router.push(
{
pathname,
query,
},
asPath,
{ locale: browserLanguage }
)
}
}, [router])
}
Another possible solution could be redirect using the router.push function:
const myRedirectFunction = function () {
if (typeof window !== 'undefined') {
router.push({
pathname: router.pathname,
query: {...router.query, myqueryparam: 'myvalue'},
})
}
}
return (
<>
<button onClick={() => {myRedirectFunction()}}> Continue </button>
</>
)
It is important to include ...router.query because there is where the [dynamic] current value is included, so we need to keep it.
Reference: https://github.com/vercel/next.js/blob/master/errors/href-interpolation-failed.md
You better do a null || undefined check in prior.
#Bentasy, Like you rightly pointed out, any re-render of the page is expecting you to pass in the dynamic route params again. The way I avoided page re-rendering with subsequent clicks on the page was to replace Link tag on the page with a label tag. I was using the Next Link tag to navigate between the tabs on this image which generated this same error. So I replaced the Link tags with label tags and the error was solved.
I was having the same problem, this is how I solved it.
While pushing something to the router, we need to give the old values ββin it.
In that:
const router = useRouter()
router.push({
...router,
pathname: '/your-pathname'
})
try add this function, it work for me:
export async function getServerSideProps(context) {
return {
props: {},
};
}
After putting off testing for a while now due to Cypress not allowing visiting chrome:// urls, I decided to finally understand how to unit/integration test my extension - TabMerger. This comes after the many times that I had to manually test the ever growing functionality and in some cases forgot to check a thing or two. Having automated testing will certainly speed up the process and help me be more at peace when adding new functionality.
To do this, I chose Jest since my extension was made with React (CRA). I also used React Testing Library (#testing-library/react) to render all React components for testing.
As I recently made TabMerger open source, the full testing script can be found here
Here is the test case that I want to focus on for this question:
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import * as TabFunc from "../src/Tab/Tab_functions";
import Tab from "../src/Tab/Tab";
var init_groups = {
"group-0": {
color: "#d6ffe0",
created: "11/12/2020 # 22:13:24",
tabs: [
{
title:
"Stack Overflow - Where Developers Learn, Share, & Build Careersaaaaaaaaaaaaaaaaaaaaaa",
url: "https://stackoverflow.com/",
},
{
title: "lichess.org β’ Free Online Chess",
url: "https://lichess.org/",
},
{
title: "Chess.com - Play Chess Online - Free Games",
url: "https://www.chess.com/",
},
],
title: "Chess",
},
"group-1": {
color: "#c7eeff",
created: "11/12/2020 # 22:15:11",
tabs: [
{
title: "Twitch",
url: "https://www.twitch.tv/",
},
{
title: "reddit: the front page of the internet",
url: "https://www.reddit.com/",
},
],
title: "Social",
},
};
describe("removeTab", () => {
it("correctly adjusts groups and counts when a tab is removed", () => {
var tabs = init_groups["group-0"].tabs;
const { container } = render(<Tab init_tabs={tabs} />);
expect(container.getElementsByClassName("draggable").length).toEqual(3);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
fireEvent.click(container.querySelector(".close-tab"));
expect(removeTabSpy).toHaveBeenCalledTimes(1);
expect(container.getElementsByClassName("draggable").length).toEqual(2); // fails (does not remove the tab for some reason)
});
});
I mocked the Chrome API according to my needs, but feel that something is missing. To mock the Chrome API I followed this post (along with many others, even for other test runners like Jasmine): testing chrome.storage.local.set with jest.
Even though the Chrome storage API is mocked, I think the issue lies in this function which gets called upon initial render. That is, I think the chrome.storage.local.get is not actually being executed, but am not sure why.
// ./src/Tab/Tab_functions.js
/**
* Sets the initial tabs based on Chrome's local storage upon initial render.
* If Chrome's local storage is empty, this is set to an empty array.
* #param {function} setTabs For re-rendering the group's tabs
* #param {string} id Used to get the correct group tabs
*/
export function setInitTabs(setTabs, id) {
chrome.storage.local.get("groups", (local) => {
var groups = local.groups;
setTabs((groups && groups[id] && groups[id].tabs) || []);
});
}
The reason I think the mocked Chrome storage API is not working properly is because when I manually set it in my tests, the number of tabs does not increase from 0. Which forced me to pass a prop (props.init_tabs) to my Tab component for testing purposes (https://github.com/lbragile/TabMerger/blob/f78a2694786d11e8270454521f92e679d182b577/src/Tab/Tab.js#L33-L35) - something I want to avoid if possible via setting local storage.
Can someone point me in the right direction? I would like to avoid using libraries like jest-chrome since they abstract too much and make it harder for me to understand what is going on in my tests.
I think I have a solution for this now, so I will share with others.
I made proper mocks for my chrome storage API to use localStorage:
// __mocks__/chromeMock.js
...
storage: {
local: {
...,
get: function (key, cb) {
const item = JSON.parse(localStorage.getItem(key));
cb({ [key]: item });
},
...,
set: function (obj, cb) {
const key = Object.keys(obj)[0];
localStorage.setItem(key, JSON.stringify(obj[key]));
cb();
},
},
...
},
...
Also, to simulate the tab settings on initial render, I have a beforeEach hook which sets my localStorage using the above mock:
// __tests__/Tab.spec.js
var init_ls_entry, init_tabs, mockSet;
beforeEach(() => {
chrome.storage.local.set({ groups: init_groups }, () => {});
init_ls_entry = JSON.parse(localStorage.getItem("groups"));
init_tabs = init_ls_entry["group-0"].tabs;
mockSet = jest.fn(); // mock for setState hooks
});
AND most importantly, when I render(<Tab/>), I noticed that I wasn't supplying the id prop which caused nothing to render (in terms of tabs from localStorage), so now I have this:
// __tests__/Tab.spec.js
describe("removeTab", () => {
it("correctly adjusts storage when a tab is removed", async () => {
const { container } = render(
<Tab id="group-0" setTabTotal={mockSet} setGroups={mockSet} />
);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
var chromeSetSpy = jest.spyOn(chrome.storage.local, "set");
fireEvent.click(container.querySelector(".close-tab"));
await waitFor(() => {
expect(chromeSetSpy).toHaveBeenCalled();
});
chrome.storage.local.get("groups", (local) => {
expect(init_tabs.length).toEqual(3);
expect(local.groups["group-0"].tabs.length).toEqual(2);
expect(removeTabSpy).toHaveBeenCalledTimes(1);
});
expect.assertions(4);
});
});
Which passes!!
Now on to drag and drop testing π
I'm seeking some wisdom from the Angular community. I am working on a simple project using the MEAN stack. I have set up my back-end api and everything is working as expected. Using Postman, I observe expected behavior for both a GET and PUT routes to retrieve/update a single value - a high score - which is saved in it's own document in its own collection in a MongoDB. So far so good.
Where things go off track is when trying to access the PUT api endpoint from within Angular. Accessing the GET endpoint is no problem, and retrieving and displaying data works smoothly. However, after considerable reading and searching, I am stll unable to properly access the PUT endpoint and update the high score data when that event is triggered by gameplay. Below are the snippets of code that I believe to be relevant for reference.
BACK-END CODE:
SCHEMA:
const _scoreSchema = {
name: { type: String, required: true },
value: { type: Number, "default": 0 }
};
ROUTES:
router
.route('/api/score/:highScore')
.put(scoreController.setHighScore);
CONTROLLER:
static setHighScore(req, res) {
scoreDAO
.setHighScore(req.params.highScore)
.then(highScore => res.status(200).json(highScore))
.catch(error => res.status(400).json(error));
}
DAO:
scoreSchema.statics.setHighScore = (value) => {
return new Promise((resolve, reject) => {
score
.findOneAndUpdate(
{"name": "highScore"},
{$set: {"value": value} }
)
.exec(function(err, response) {
err ? reject(err)
: resolve(response);
});
});
}
ANGULAR CODE:
CONTROLLER:
private _updateHighScore(newHighScore): void {
console.log('score to be updated to:', newHighScore)
this._gameService
.updateHighScore(newHighScore);
}
SERVICE:
updateHighScore(newHighScore: Number): Observable<any> {
console.log(newHighScore);
let url = '/api/score/' + newHighScore;
let _scoreStringified = JSON.stringify({value: newHighScore});
let headers = new Headers();
headers.append("Content-Type", "application/json");
return this._http
.put(url , _scoreStringified, {headers})
.map((r) => r.json());
}
Note that the console.log(newHighScore) in the last block of code above correctly prints the value of the new high score to be updated, it's just not being written to the database.
The conceptual question with PUT routes in angular is this: If the api is already set up such that it receives all the information it needs to successfully update the database (via the route param) why is it required to supply all of this information again in the Angular .put() function? It seems like reinventing the wheel and not really utilizing the robust api endpoint that was already created. Said differently, before digging into the docs, I naively was expecting something like .put(url) to be all that was required to call the api, so what is the missing link in my logic?
Thanks!
In Meteor, using iron-router, I'm trying to implement a route mapping which has a dynamic segment and a fallback route if the dynamic segment does not match any items in a collection. For example, say I have a URL like so:
http://foobar.com/harold
I would like to first check if harold matches any IDs in a Posts collection. If there is a match then it should take me to a postPage template.
If there are no matches, then the router should render harold matches any items.
I've searched through all the iron-router documentation, but can't seem to figure out the right approach. I wonder if there is something like this.next() which cancels the current route mapping and goes to the next route mapping. Here's my attempt to try to do it:
Router.map(function () {
this.route('postPage', {
// matches: '/MFLS6aKqqRZ2JGz9q'
// matches: '/81zBqGE85aAfjk1js'
path: '/:postId',
before: function () {
//check if segment matches a post ID
post = Posts.findOne(this.params.postId);
if (!post) {
//If no matches, then go to next route
//Something like this.next()?
}
},
data: function() {
return Posts.findOne(this.params.postId);
}
});
this.route('profilePage', {
// matches: '/harold'
// matches: '/brendan'
path: '/:username',
data: function() {
return Profiles.findOne(this.params.username);
}
});
});
What you're describing is really just one route: /:input where input can be anything. As #David Weldon mentioned, this isn't a good idea; it basically subverts the point of using a router, and causes a lot of extra code and database queries to run every time the URL changes.
That said, if you really want to do it that way, you just need to collapse all your code into one route:
Router.map(function () {
this.route('theOneRouteToRuleThemAll', {
path: '/:input',
data: function() {
if (Profiles.findOne({username: this.params.input}) != null)
return Profiles.findOne({username: this.params.input});
else if (Posts.findOne({postId: this.params.input} != null)
return Posts.findOne({postId: this.params.input};
else
return null; // No user or post found; could handle with notFoundTemplate
}
});
});