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
Related
I would like to know if it is possible to reload asyncData in an emit in a function like this
Page
<template>
<component-child :products="products" #asyncData="asyncData" />
</template>
async asyncData({ $axios, store }) {
const customerId = store.getters['user/auth/customerId'];
if (!customerId) {
return;
}
const products = await customerApi.getProducts(
{ $axios },
customerId,
);
return {
products: products
};
},
component-child
methods: {
infiniteHandler() {
this.$emit('asyncData);
}
}
Is it possible? Otherwise how to do it?
You can try this.$nuxt.refresh() to refresh fetch() or asyncData() hooks.
As explained in the documentation: https://nuxtjs.org/docs/2.x/concepts/context-helpers#refreshing-page-data
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>
I'm creating my first MERN stack application, and trying to implement a simple API that calls my express server from my React front-end components. I have the API working on the back end, and it is sending the data correctly through fetch(), but I'm having trouble resolving the promise from fetch() in my React component, with the call not stopping firing. My code looks as follows (assuming as of right now all API calls return a dummy format like { title: 'foo', ... }:
import React, { useState } from 'react';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
const getApiData = async (route) => {
try {
let apiData = await fetch(route);
let apiDataJson = await apiData.json();
return apiDataJson;
} catch (err) {
throw new Error('Error on fetch', {
error: err
})
}
}
var retrieve_data = async (route, setterCallback) => {
await getApiData(`/api/${route}`).then((data) => {
console.log('Data retrieved from API')
setterCallback(<div>{data.title}</div>)
}).catch(() => {
setterCallback(<div>ERROR</div>)
})
}
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
let data = retrieve_data('myEndpoint', setinnerDiv);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}
When I compile the above the component successfully renders (i.e. <MyComponent /> looks like:
<div>
<h1>Data Retrieved in MyComponent:</h1>
<div>foo</div>
</div>
However, then then block keeps executing (i.e. the 'Data retrieved from API' logs to the console hundreds of times/second until I close the application. How can I stop this from executing once it has set the component? Thanks!
You need to useEffect to stop the component from re-rendering. Try something like this.
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
useEffect(() => {
retrieve_data('myEndpoint', setinnerDiv);
}, []);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}
I'm trying to call an action in my vue from my store.
This is my file aliments.js in my store:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex, axios);
export const state = () => ({
aliments: {},
})
export const mutations = () => ({
SET_ALIMENTS(state, aliments) {
state.aliments = aliments
}
})
export const actions = () => ({
async getListAliments(commit) {
await Vue.axios.get(`http://localhost:3080/aliments`).then((response) => {
console.log(response);
commit('SET_ALIMENTS', response);
}).catch(error => {
throw new Error(`${error}`);
})
// const data = await this.$axios.get(`http://localhost:3080/aliments`)
// commit('setUser', user)
// state.user = data;
// return state.user;
}
})
export const getters = () => ({
aliments (state) {
return state.aliments
}
})
I want to diplay a list of aliments in my vue with :
{{ this.$store.state.aliments }}
I call my action like this :
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters(['loggedInUser', 'aliments']),
...mapActions(['getListAliments']),
getListAliments() {
return this.$state.aliments
}
}
}
</script>
I don't understand where is my mistake :/
NB: I also tried with a onclick method on a button with a dispatch('aliments/getListAliments')... but doesn't work...
The problem is that you're mapping your actions in the "computed" section of the component, you should map it in the "methods" section !
Hi and Welcome to StackOverflow
to quickly answer to your question, you would call an action as:
this.$store.dispatch('<NAME_OF_ACTION>', payload)
or though a mapActions as
...mapActions(['getListAliments']), // and you call `this.getListAliments(payload)`
or yet
...mapActions({
the_name_you_prefer: 'getListAliments' // and you call `this.the_name_you_prefer(payload)`
}),
for getters, it's the same process, as you already have 2 definitions ['loggedInUser', 'aliments'] you simply call the getter like if it was a computed value <pre>{{ aliments }}</pre>
or when we need to do a bit more (like filtering)
getListAliments() {
return this.$store.getters['aliments']
}
But I can see your store is as we call, one-to-rule-them-all, and because you are using Nuxt, you can actually leverage the module store very easy
as your application grows, you will start store everything in just one store file (the ~/store/index.js file), but you can easily have different stores and instead of what you wrote in index.js it can be easier if you had a file called, taken your example
~/store/food.js with
import axios from 'axios'
export const state = () => ({
aliments: {},
})
export const getters = {
aliments (state) {
return state.aliments
}
}
export const mutations = {
SET_ALIMENTS(state, aliments) {
state.aliments = aliments
}
}
export const actions = {
async getListAliments(commit) {
await axios.get('http://localhost:3080/aliments')
.then((response) => {
console.log(response);
commit('SET_ALIMENTS', response.data);
}).catch(error => {
throw new Error(`${error}`);
})
}
}
BTW, remember that, if you're using Nuxt serverMiddleware, this line
axios.get('http://localhost:3080/aliments')...
would simply be
axios.get('/aliments')...
and to call this store, all you need is to prefix with the filename, like:
...mapActions(['food/getListAliments'])
// or
...mapActions({ getListAliments: 'food/getListAliments' })
// or
this.$store.commit('food/getListAliments', payload)
another naming that could help you along the way:
on your action getListAliments you're actually fetching data from the server, I would change the name to fetchAliments
on your getter aliments you're actually returning the list, I would name it getAllAliments
have fun, Nuxt is amazing and you have a great community on Discord as well for the small things :o)
EDIT
also remember that actions are set in methods
so you can do:
...
export default {
methods: {
...mapActions(['getListAliments]),
},
created() {
this.getListAliments()
}
}
and in your Store action, please make sure you write
async getListAliments({ commit }) { ... }
with curly braces as that's a deconstruction of the property passed
async getListAliments(context) {
...
context.commit(...)
}
We are building an offline first React Native Application with Apollo Client. Currently I am trying to update the Apollo Cache directly when offline to update the UI optimistically. Since we offline we do not attempt to fire the mutation until connect is "Online" but would like the UI to reflect these changes prior to the mutation being fired while still offline. We are using the readQuery / writeQuery API functions from http://dev.apollodata.com/core/read-and-write.html#writequery-and-writefragment. and are able to view the cache being updated via Reacotron, however, the UI does not update with the result of this cache update.
const newItemQuantity = existingItemQty + 1;
const data = this.props.client.readQuery({ query: getCart, variables: { referenceNumber: this.props.activeCartId } });
data.cart.items[itemIndex].quantity = newItemQuantity;
this.props.client.writeQuery({ query: getCart, data });
If you look at the documentation examples, you will see that they use the data in an immutable way. The data attribute passed to the write query is not the same object as the one that is read. Mutating this object is unlikely to be supported by Apollo because it would not be very efficient for it to detect which attributes you modified, without doing deep copies and comparisons of data before/after.
const query = gql`
query MyTodoAppQuery {
todos {
id
text
completed
}
}
`;
const data = client.readQuery({ query });
const myNewTodo = {
id: '6',
text: 'Start using Apollo Client.',
completed: false,
};
client.writeQuery({
query,
data: {
todos: [...data.todos, myNewTodo],
},
});
So you should try the same code without mutating the data. You can use for example set of lodash/fp to help you
const data = client.readQuery({...});
const newData = set("cart.items["+itemIndex+"].quantity",newItemQuantity,data);
this.props.client.writeQuery({ ..., data: newData });
It recommend ImmerJS for more complex mutations
Just to save someones time. Using the data in an immutable way was the solution. Agree totally with this answer, but for me I did something else wrong and will show it here. I followed this tutorial and updating the cache worked fine as I finished the tutorial. So I tried to apply the knowledge in my own app, but there the update didn’t work even I did everything similar as showed in the tutorial.
Here was my approach to update the data using the state to access it in the render method:
// ... imports
export const GET_POSTS = gql`
query getPosts {
posts {
id
title
}
}
`
class PostList extends Component {
constructor(props) {
super(props)
this.state = {
posts: props.posts
}
}
render() {
const postItems = this.state.posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
const PostListQuery = () => {
return (
<Query query={GET_POSTS}>
{({ loading, error, data }) => {
if (loading) {
return (<div>Loading...</div>)
}
if (error) {
console.error(error)
}
return (<PostList posts={data.posts} />)
}}
</Query>
)
}
export default PostListQuery
The solution was just to access the date directly and not using the state at all. See here:
class PostList extends Component {
render() {
// use posts directly here in render to make `cache.writeQuery` work. Don't set it via state
const { posts } = this.props
const postItems = posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
Just for completeness here is the input I used to add a new post and update the cache:
import React, { useState, useRef } from 'react'
import gql from 'graphql-tag'
import { Mutation } from 'react-apollo'
import { GET_POSTS } from './PostList'
const ADD_POST = gql`
mutation ($post: String!) {
insert_posts(objects:{title: $post}) {
affected_rows
returning {
id
title
}
}
}
`
const PostInput = () => {
const input = useRef(null)
const [postInput, setPostInput] = useState('')
const updateCache = (cache, {data}) => {
// Fetch the posts from the cache
const existingPosts = cache.readQuery({
query: GET_POSTS
})
// Add the new post to the cache
const newPost = data.insert_posts.returning[0]
// Use writeQuery to update the cache and update ui
cache.writeQuery({
query: GET_POSTS,
data: {
posts: [
newPost, ...existingPosts.posts
]
}
})
}
const resetInput = () => {
setPostInput('')
input.current.focus()
}
return (
<Mutation mutation={ADD_POST} update={updateCache} onCompleted={resetInput}>
{(addPost, { loading, data }) => {
return (
<form onSubmit={(e) => {
e.preventDefault()
addPost({variables: { post: postInput }})
}}>
<input
value={postInput}
placeholder="Enter a new post"
disabled={loading}
ref={input}
onChange={e => (setPostInput(e.target.value))}
/>
</form>
)
}}
</Mutation>
)
}
export default PostInput