NUXT JS dynamic multiple search query parameters - javascript

Please, I am new to NUXT JS and need help.
I have a search page where the search bar is located in its header component which is shared across pages, and upon submitting, it goes to the search page and the search logic is done in the store store/search.js
The issue is that no matter what I search and the parameters;
I cannot get the values of the query to show on the browser URL like search?Q=jump&color=yellow
Since my search logic is in the store, I am confused about how to pass parameters from the URL and searching dynamically like that.
below is my code
search bar component
<input type="text" class="search-input" placeholder="Search for a location" #keypress="search">
methods: {
search(event) {
const btn = event.key;
if (btn === "Enter") {
const search_terms = event.target.value;
this.$store.dispatch("search/search", search_terms);
this.$router.push('/search');
}
}
}
store
store/search.js
export const state = () => ({
result: []
})
export const mutations = {
searchTerms(state, text) {
state.searchTerms = text
},
searchResult(state, result) {
state.result = result
}
}
export const actions = {
search(vuexContext, search_terms) {
vuexContext.commit('loadingTrue')
return this.$axios
.$get('https://help.herokuapp.com/place', { params:
{
search: 'lagos',
idealfor: ["production"],
page: 1,
limit: 12,
}
})
.then(data => {
let searchResult = data.results
vuexContext.commit('searchResult', searchResult)
})
.catch(e => {
console.log(e)
})
},
}
export const getters = {
searchResult(state) {
return state.result
},
}
i would like the search bar to show like
localhost:3000/search?search=lagos&page=1&limit=12&idealfor=["production"]
and when the link is sg[shared and also visited the desired result would show.
please how would you go about this in Nuxt and are there resources anyone can recommend that would help me with this.

you can update url's query by using $router.push to the same page with query included, it won't refresh the page and only updates the query.
for example:
this.$router.push('/search?search=lagos&page=1&limit=12&idealfor=["production"]');
you can access query values using $route.query.
if you want to show the result when the link is visited, you need to check for $route.query in mounted cycle and dispatch it to the store to get the results.

You can achieve it by creating the following action
async moveToGivenPathWithQuery({ _ }, { q = {}, color = {} }) {
// eslint-disable-next-line no-undef
await $nuxt.$router.push({ path: 'search', query: { q, color } })
},
index.vue
<template>
<div>
<button #click="moveToGivenPathWithQuery({ q: 'jump', color: 'yellow' })">
Go to search
</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions('search', ['moveToGivenPathWithQuery']),
},
}
</script>
search.vue
<template>
<div>Search page</div>
</template>
<script>
export default {
mounted() {
console.log('query received', this.$route.query)
},
}
</script>

Related

Nuxt page HTML is loaded before data

I have a dynamic page that loads product details, but the html is loaded before the data.
So when I try to use static elements like an image I get an error stating the object "product" does not exist.
To fix this I gave every dynamic element v-if="product != undefined" which does work, but doesn't seem like a very good way of solving this.
I'm initiating my data through the store like this
In my page i do:
async mounted() {
await this.fetchProducts()
},
computed: {
product() {
return this.$store.state.products.producten.filter(product => product.id == this.$route.params.id)[0]
}
}
Then in my store:
export const state = () => ({
producten: []
})
export const mutations = {
setProducts(state, data) {
state.producten = data
}
}
export const actions = {
async fetchProducts({ commit }) {
await axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
I tried replacing mounted() with:
beforeMount(),
created(),
fetch()
but none seemed to work.
I also tried:
fetch() {return this.$store.dispatch('fetchProducts')}
Loader(v-if="$fetchState.pending")
Error(v-if="$fetchState.pending")
.product(v-else)
// Product details...
You could use the fetch hook to dispatch fetchProducts:
<script>
export default {
fetch() {
return this.$store.dispatch('fetchProducts')
}
}
</script>
In your template, use the $fetchState.pending flag to prevent rendering the data elements until ready:
<template>
<div>
<Loader v-if="$fetchState.pending" />
<Error v-else-if="$fetchState.error" />
<Product v-else v-for="product in products" v-bind="product" />
</div>
</template>
demo

I want to use this. $ axios with Vuex constants

What I want to come true
I use this.$axios many times, so I tried to put it in a constant, but it doesn't work.
I read the official docs but didn't understand.
Is it because this isn't available in the Nuxt.js lifecycle?
Code
url.js
export const AXIOS_POST = this.$axios.$post
export const POST_API = '/api/v1/'
export const POST_ITEMS_API = '/api/v1/post_items/'
Vuex
import * as api from './constants/url.js' // url.js in this.
export const state = () => ({
list: [],
hidden: false
})
export const mutations = {
add (state, response) {
state.list.push({
content: response.content,
status: response.status
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
edit (state, { todo, text }) {
state.list.splice(state.list.indexOf(todo), 1, { text })
},
toggle (state, todo) {
todo.status = !todo.status
},
cancel (state, todo) {
todo.status = false
},
// アクション登録パネルフラグ
switching (state) {
state.hidden = !state.hidden
}
}
export const actions = {
post ({ commit }, text) {
//I want to use it here
this.$axios.$post(api.POST_ITEMS_API + 'posts', {
post_items: {
content: text,
status: false
}
})
.then((response) => {
commit('add', response)
})
}
}
Error
Uncaught TypeError: Cannot read property '$axios' of undefined
Since your file is located into a constants directory, you should probably use some .env file.
Here is a guide on how to achieve this in Nuxt: https://stackoverflow.com/a/67705541/8816585
If you really want to have access to it into a non .vue file, you can import it as usual with something like this
/constants/url.js
import store from '~/store/index'
export const test = () => {
// the line below depends of your store of course
return store.modules['#me'].state.email
}
PS: getters, dispatch and everything alike is available here.
Then call it in a page or .vue component like this
<script>
import { test } from '~/constants/url'
export default {
mounted() {
console.log('call the store here', test())
},
}
</script>
As for the lifecyle question, since the url.js file is not in a .vue file but a regular JS one, it has no idea about any Vue/Nuxt lifecycles.

Recursion when pushing to Component Page Vue

I'm experiencing an error "InternalError: too much recursion" when trying to push from my Layout to a Post site.
Code of Layout.vue:
watch(searchText, (newValue, oldValue) => {
log('Current State of SearchText', newValue);
if (newValue !== null) {
if (newValue.value !== undefined) {
let id = newValue.value;
// push to post with id
router.push(`/post/${id}`);
} else {
// todo: start search
}
}
});
I'm using the watch to react when my QSelect model value is changing.
My route:
{ path: '/post/:id', component: () => import('pages/Post.vue'),
My Post-page:
<template>
<q-page class="">
<Post // I'm getting no error when deleting this Component
:description="post.description"
:title="post.title"
:author="post.user"
:date="post.date"
:tags="post.tags"
:commentArray="post.comments"
/>
<h1>
Test
</h1>
</q-page>
</template>
<script>
import Post from 'src/components/PostComp.vue';
import { useRouter, useGetters, useActions } from '#u3u/vue-hooks';
import { ref } from '#vue/composition-api';
import moment from 'moment';
const debug = require('debug');
const log = debug('app:PostPage');
export default {
name: 'Post',
components: {
Post,
},
setup() {
const { route } = useRouter();
const post = ref({});
const getters = {
...useGetters('post', ['post']),
};
const actions = {
...useActions('post', ['findAll']),
};
log('params:', route.value.params);
const p1 = getters.post.value(route.value.params.id);
post.value = {
title: p1[0].title,
user: 'Mary Sullyman',
description: p1[0].description,
tags: p1[0].postTags,
comments: p1[0].commentDTOList,
date: moment(p1[0].createdDate).format('DD.MM.YYYY HH:mm') + ' Uhr',
};
log(post);
What I'm trying to do:
I have a QSelect in my Toolbar to search for posts which works just fine. Now I'm trying to push to a dynamically generated site for the post clicked.
What if you remove name: 'Post' (from export default)? The name you set matches the component tag name, so it falls into an infinite render loop.
See Recursive Components (still applies to Vue 3 even though it's from Vue 2 docs)

how to update the url after saving a post

I'm building an editor which can save and update post. The problem I'm facing is that after saving the post for the first time, I get a snippetId from the server which I want to show in the url immediately or else my route is still http://localhost:8000/editor and if I hit save button again then it saves a duplicate copy with a different id. I want the editor url to be something like http://localhost:8000/editor/123 after saving for the first time so that when I hit the save button again then it updates the post instead of saving a duplicate copy with a different id. I was wondering how to tackle this problem? can someone help me find a solution for this problem
codesandbox
editor.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { savePost, retrievePost } from "./actions/posts";
class Editor extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
enteredText: ""
};
this.commonChange = this.commonChange.bind(this);
}
commonChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
componentDidMount() {
//Load the snippet
retrievePost(this.props.match.params.snippetId);
}
// Save Snippet
performSave = snippets => {
console.log("save function clicked");
const { enteredText, title } = this.state;
this.props.savePost({
snippetId: this.props.match.params.snippetId, //if the url doesn't change then this also doesn't change so I get duplicate copies
snippetDescription: enteredText,
snippetTitle: title
});
};
render() {
return (
<>
<input
type="text"
id="titletext"
placeholder="Enter title here"
limit-to="64"
className="inptxt"
name="title"
onChange={this.commonChange}
/>
<button className="btn savebtn" onClick={this.performSave}>
Save Snippet
<i className="fas fa-save" />
</button>
<textarea name="enteredText" onChange={this.commonChange} />
</>
);
}
}
const mapStateToProps = state => ({
snippets: state.snippets
});
export default connect(
mapStateToProps,
{ savePost, retrievePost }
)(Editor);
action.js
import { SAVE_POST, UPDATE_POST, RETRIEVE_POST, HOME_LOADED } from "./types";
import axios from "axios";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
let snippetData = {
title: snippetTitle,
snippetDescription: snippetDescription
};
// --------------------------------------
console.log("in savePost action");
try {
if (snippetId == null) {
const res = await axios.post("/api/savesnippets", snippetData, config);
snippetData.snippetId = res.data; //cause I only get snippetId from the server
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
//add snippetId here for update use only --------------------------------------
await axios.post(
"/api/update",
JSON.stringify({ ...snippetData, snippetId }),
config
);
// --------------------------------------
dispatch({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
You can history.push() from react-router for this purpose like:
history.push('/editor' + id_you_get_from_ajax_call);
And use it where you are getting the ajax response, so for every success you will get a new id_you_get_from_ajax_call and it will update the route.
and create a matching route for editor like:
<Route path="editor/:id" component={ YOUR_COMPONENT } />
React-router history.push() Reference

How to combine two dependent GraphQL queries with 'compose'?

Solved!
I'm trying to combine two dependent GraphQL queries.
The first one should get an ID and the second one should take that ID. I read that compose behaves like flowRight(), but no matter in what order I put the queries, if queryId is below queryDetails, queryDetail's is always skipped (as expected). No matter how I put my code together the variable is undefined.
import { graphql, compose } from 'react-apollo'
import gql from 'graphql-tag'
class Home extends Component {
constructor(props) {
super(props)
console.log("Where's my data?")
console.log(props)
}
render() {
return(
<div />
)
}
}
export const queryIdConst = gql`
query IdQuery {
account(name:"SuperAccount")
{
lists {
edges {
id
}
}
}
}
`
export const queryDataConst = gql`
query DataQuery($id: ID!) {
account(name:"SuperAccount")
{
list(id: $id) {
displayTitle
}
}
}
`
export default compose(
graphql(queryIdConst, {
name: 'listId',
}),
graphql(queryDataConst, {
name: 'data',
skip: ({ listId }) => !listId.data,
options: ({ listId }) => ({
variables: {
id: list.data.account.lists.edges[0].id
}
})
})
)(Home)
I have already tried to change the compose functions order, but anyway this is not working, as I expected it to work.
Thanks for any help!
Edit: Switched the two graphql() in compose() to be inline with AbsoluteSith's comment link
Solution
With hints and help from Daniel Rearden and AbsoluteSith I implemented the following solution:
Changed the compose():
export default compose(
graphql(queryIdConst, {
name: 'listId',
}),
graphql(queryDataConst, {
name: 'dataHome', // changed to 'dataHome' to avoid confusion
skip: ({ listId }) => !listId.account,
options: ({ listId }) => ({
variables: {
id: listId.account.lists.edges[0].id
}
})
})
)(Home)
And my render():
return(
<div>
{ dataHome && !dataHome.loading &&
<div>{dataHome.account.list.displayTitle}</div>
}
</div>
)
When using the graphql HOC, by default, the wrapped component receives a prop called data (or mutate if passing in a mutation). Given a query like
query IdQuery {
account(name:"SuperAccount") {
lists {
edges {
id
}
}
}
}
once the query loads, the query result is available under this.props.data.account. When you use the name configuration option, you're telling the HOC to use something other than data for the prop name. So if you set name to listId, then your query result will be available at
this.props.listId.account
That means the second HOC inside of compose should look more like this:
graphql(queryDataConst, {
skip: ({ listId }) => !listId.account, // <--
options: ({ listId }) => ({
variables: {
id: listId.account.lists.edges[0].id // <--
}
})
})

Categories