Unable to fetch query parameter in NextJS - api

I have an API route /api/form
// ./pages/api/form.js
import Router from 'next/router';
...
export default async function handler(req, res) {
...
res.redirect(307, '/summary?username=username');
Router.push({
pathname: '/summary',
query: {
username: username
}
});
}
// ./pages/summary.js
import { useRouter } from 'next/router';
export default function Summary() {
const router = useRouter();
console.log(router.query); // undefined
}
I am not able to fetch the query param. Also, if change the order of Router.push and res.redirect, I still stay on the /api/form route
I also tried using useRouter().push as per the documentation. Still, I stay in the /api/form route.
How to get the query param?

next/router allows you to do client-side transitions: https://vercel.fyi/next-router-client-side
For your use case I suggest putting the router.push event inside your form submit event, something like this:
<form
onSubmit={() => {
fetch(`/api/form`, {
...
}).then((res) => {
if (res.status === 200) {
router.push({
pathname: '/summary',
query: {
username: username
}
})
}
)
}
}
>
...
</form>

Related

Cannot use Vue-Router to get the parameters in the URL

Today, when trying to use Vue-Router (in Vue-CLI) to get URL parameters, I encountered difficulties ($route.query is empty), the code is as follows.
Code purpose: Get the parameters carried after the URL (such as client_id in "http://localhost:8080/#/?client_id=00000000000077")
Project file structure:
router/index.js:
App.vue(Get part of the code for URL parameters):
The running result of this part of the code:
I'm not sure why $router.currentRoute and $route aren't matching up, but you could simply use $router.currentRoute.query.client_id if you need it in mounted().
Another workaround is to use a $watch on $route.query.client_id:
export default {
mounted() {
const unwatch = this.$watch('$route.query.client_id', clientId => {
console.log({ clientId })
// no need to continue watching
unwatch()
})
}
}
Or watch in the Composition API:
import { watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
mounted() {
console.log({
route: this.$route,
router: this.$router,
})
},
setup() {
const route = useRoute()
const unwatch = watch(() => route.query.client_id, clientId => {
console.log({ clientId })
// no need to continue watching
unwatch()
})
}
}

How to use Nuxt Context to call Axios request with param

so I'm trying to get my Axios to do a get request with a param that'll end the url in
'/?user= {id}'
the id is passed in by my loggedInUser.id from Vuex. I know that async functions won't accept 'this' inside the call so I included store as a parameter. Something's still off with how I passed the data around thought I think. Would appreciate any help, thanks!
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters(["loggedInUser"])
},
head() {
return {
title: "Actors list"
};
},
components: {
EditProfile
},
async asyncData({ store }) {
try {
const body = { data: store.getters.loggedInUser.id };
const { actors } = await $axios.$get(`/api/v1/actors/`, {
params: {
user: body
}
});
return { actors };
} catch (e) {
return { actors: [] };
}
},
data() {
return {
actors: []
};
Edit
I got it to work when I removed the data: from 'const body' and removed the brackets as well around 'actor'
try {
const body = store.getters.loggedInUser.id;
const actors = await $axios.$get(`/api/v1/actors/`, {
params: {
user: body
}
});
You can access your params from Context.
Context is available in special nuxt lifecycle areas like asyncData, fetch, plugins, middleware and nuxtServerInit.
In Nuxt, with asyncData hook you can get query parameters from the route context key.
Please read the Nuxt.js Context documentation. The context provides additional objects/params from Nuxt to Vue components
With your-domain/?user=wonderman
asyncData({ route: { query: queryParams} }) {},
variable queryParams is an object:
{ user: "wonderman" }

How can I access data in asyncData with Nuxt

I'm attempting to build a server-side sortable table with Nuxt, and I'd like to be able to specify the default sort column and direction in my Vue data, and access that in my asyncData function. Something like this:
<script>
export default {
async asyncData ({ $axios, params }) {
const things = await $axios.$get(`/api/things`, {
params: {
sort_column: this.sortColumn,
sort_ascending: this.sortAscending,
}
});
return { things };
},
data () {
return {
sortColumn: 'created_at',
sortAscending: true
}
},
// ...
}
</script>
But it appears that data is not yet available, as this.sortColumn and this.sortAscending are not defined. How can I access these defaults when asyncData runs while also allowing them to be changed when the user interacts with the page. (Alternatively, what's a better way to structure this?)
Note: This question was asked here, but the accepted answer is not relevant to this situation.
You can just return it all into asyncData. E.g. something like this:
async asyncData ({ $axios, params }) {
const sortColumn = 'created_at'
const sortAscending = true
const things = await $axios.$get(`/api/things`, {
params: {
sort_column: sortColumn,
sort_ascending: this.sortAscending,
}
});
return { things, sortColumn, sortAscending };
},
And it will behave like you want.

Computed Getter causes maximum stack size error

I'm trying to implement the following logic in Nuxt:
Ask user for an ID.
Retrieve a URL that is associated with that ID from an external API
Store the ID/URL (an appointment) in Vuex
Display to the user the rendered URL for their entered ID in an iFrame (retrieved from the Vuex store)
The issue I'm currently stuck with is that the getUrl getter method in the store is called repeatedly until the maximum call stack is exceeded and I can't work out why. It's only called from the computed function in the page, so this implies that the computed function is also being called repeatedly but, again, I can't figure out why.
In my Vuex store index.js I have:
export const state = () => ({
appointments: {}
})
export const mutations = {
SET_APPT: (state, appointment) => {
state.appointments[appointment.id] = appointment.url
}
}
export const actions = {
async setAppointment ({ commit, state }, id) {
try {
let result = await axios.get('https://externalAPI/' + id, {
method: 'GET',
protocol: 'http'
})
return commit('SET_APPT', result.data)
} catch (err) {
console.error(err)
}
}
}
export const getters = {
getUrl: (state, param) => {
return state.appointments[param]
}
}
In my page component I have:
<template>
<div>
<section class="container">
<iframe :src="url"></iframe>
</section>
</div>
</template>
<script>
export default {
computed: {
url: function (){
let url = this.$store.getters['getUrl'](this.$route.params.id)
return url;
}
}
</script>
The setAppointments action is called from a separate component in the page that asks the user for the ID via an onSubmit method:
data() {
return {
appointment: this.appointment ? { ...this.appointment } : {
id: '',
url: '',
},
error: false
}
},
methods: {
onSubmit() {
if(!this.appointment.id){
this.error = true;
}
else{
this.error = false;
this.$store.dispatch("setAppointment", this.appointment.id);
this.$router.push("/search/"+this.appointment.id);
}
}
I'm not 100% sure what was causing the multiple calls. However, as advised in the comments, I've now implemented a selectedAppointment object that I keep up-to-date
I've also created a separate mutation for updating the selectedAppointment object as the user requests different URLs so, if a URL has already been retrieved, I can use this mutation to just switch the selected one.
SET_APPT: (state, appointment) => {
state.appointments = state.appointments ? state.appointments : {}
state.selectedAppointment = appointment.url
state.appointments = { ...state.appointments, [appointment.appointmentNumber]: appointment.url }
},
SET_SELECTED_APPT: (state, appointment) => {
state.selectedAppointment = appointment.url
}
Then the getUrl getter (changed its name to just url) simply looks like:
export const getters = {
url: (state) => {
return state.selectedAppointment
}
}
Thanks for your help guys.

Preventing unauthed ajax requests from redux actions

I have a component defined like this. fetchBrands is a redux action.
class Brands extends Component {
componentWillMount() {
this.props.fetchBrands();
}
render() {
return (
// jsx omitted for brevity
);
}
}
function mapStateToProps(state) {
return { brands: state.brands.brands }
}
export default connect(mapStateToProps, { fetchBrands: fetchBrands })(Brands);
This component is wrapped in a Higher Order Component that looks like this:
export default function(ComposedComponent) {
class Authentication extends Component {
// kind if like dependency injection
static contextTypes = {
router: React.PropTypes.object
}
componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('/');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('/');
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { authenticated: state.auth.authenticated };
}
return connect(mapStateToProps)(Authentication);
}
Then, in my router config, I am doing the following:
<Route path="brands" component={requireAuth(Brands)} />
If the auth token doesn't exist in local storage, I redirect to a public page. However, the fetchBrands action is still being called which is firing off an ajax request. The server is rejecting it because of the lack of an auth token, but I don't want the call to even be made.
export function fetchBrands() {
return function(dispatch) {
// ajax request here
}
}
I could wrap the ajax request with an if/else check, but that isn't very DRY considering all the action functions I'd need to do this in. How can I implement something DRY to prevent the calls if auth fails at the browser level?
You should write a middleware for that. http://redux.js.org/docs/advanced/Middleware.html
Since you are using axios I would really recommend using something like https://github.com/svrcekmichal/redux-axios-middleware
Then you can use a middleware like
const tokenMiddleware = store => next => action => {
if (action.payload && action.payload.request && action.payload.request.secure) {
const { auth: { isAuthenticated } } = store.getState()
const secure = action.payload.request.secure
if (!isAuthenticated && secure) {
history.push({ pathname: 'login', query: { next: history.getCurrentLocation().pathname } })
return Promise.reject(next({ type: 'LOGIN_ERROR', ...omit(action, 'payload') }))
}
}
return next(action)
}
action.payload.request.secure is a custom prop I use to indicate the request needs authentication.
I this case I also redirect using history from react-router but you can handle this to dispatch another action (store.dispatch({ whatever })) and react as you need