set data only on first load with nuxt.js - javascript

I'm new to nuxt.js so I'm wondering what could be the best way to set up some data via REST api.
I have a store folder like this:
store
-posts.js
-categories.js
-index.js
I've tried to set the data with nuxtServerInit actions in the index.js:
export const actions = {
async nuxtServerInit({ dispatch }) {
await dispatch('categories/setCategories')
await dispatch('posts/loadPosts','all')
}
}
But doesn't works: actions are dispatched (on the server) but data are not set.
So I've tried with fetch but this method is called every time the page where I have to display posts is loaded. Even if, in the general layout, I do this:
<template>
<div>
<Header />
<keep-alive>
<nuxt/>
</keep-alive>
</div>
</template>
So my solution, for now, is to use fetch in this way,
In the page component:
async fetch({store}){
if(store.getters['posts/getPosts'].length === 0 && store.getters['categories/getCategories'].length === 0 ){
await store.dispatch('categories/setCategories')
await store.dispatch('posts/loadPosts','all')
}
}
Also, one thing I noted is that fetch seems not working on the root page component (pages/index.vue)
My solution seems works, but there is maybe another better way to set the data?

There's no out of the box solution for this as it's specific to your requirements/needs. My solution is very similar to yours but instead of checking the size of data array I introduced additional variable loaded in every store module. I only fetch data if loaded is false. This approach is more suitable in apps that have user generated content and require authentication. It will work optimally with SSR and client-side, and it won't try to fetch data on every page visit if user has no data.
You could also simplify your fetch method like this:
async fetch()
{
await this.$store.dispatch('posts/getOnce')
}
Now your posts.js store module will look something like this:
export const state = () => ({
list: [],
loaded: false
})
export const actions = {
async getOnce({ dispatch, state }) {
if (!state.loaded) {
dispatch('posts/get')
}
},
async get({ commit, state }) {
await this.$axios.get(`/posts`)
.then((res) => {
if (res.status === 200) {
commit('set', res.data.posts)
}
})
}
}
export const mutations = {
set(state, posts) {
state.list = posts
state.loaded = true
}
}

Related

How do I get my layout component to remain static in Next13 app folder

I am trying to create a layout component that fetches its own data, I have tried adding the cache: 'force-cache' to the fetch but every time I update my CMS content and refresh my page the new content is loaded. Here is an example of my code:
const getLayoutData = async () => {
const response = await fetch(
`https://cdn.contentful.com/spaces/${
process.env.CONTENTFUL_SPACE_ID
}/environments/${
process.env.CONTENTFUL_ENVIRONMENT || "master"
}/entries/${fieldId}?access_token=${process.env.CONTENTFUL_ACCESS_TOKEN}`,
{
cache: "force-cache",
}
);
const {entryTitle, ...headerData} = await response.json();
return { headerData };
}
export default async function Layout() {
const data = await getLayoutData();
...
You can use the getStaticProps() function to fetch data at build time and make it available to your component as a prop. This way, the data will be pre-rendered on the server and will not change when the user refreshes the page:
import getLayoutData from './getLayoutData';
export async function getStaticProps() {
const data = await getLayoutData();
return { props: { data } };
}
export default function Layout({ data }) {
// Use data in your component
...
}
Alternatively you could use getServerSideProps(), it runs on the server at request time instead of build time. I would recommend that if you have dynamic data that changes frequently:
import getLayoutData from './getLayoutData';
export async function getServerSideProps(context) {
const data = await getLayoutData();
return { props: { data } };
}
export default function Layout({ data }) {
// Use data in your component
...
}
By default, Next.js automatically does static fetches. This means that the data will be fetched at build time, cached, and reused on each request. As a developer, you have control over how the static data is cached and revalidated.
Refer to the docs - https://beta.nextjs.org/docs/data-fetching/fundamentals
Also, this will work in production mode. So, make sure you are using next build && next start and not next dev.
In case you are fetching data from same URL anywhere else, the cache might be getting updated. As Next.js also does request deduplication built into the fetch function itself.

catch all URLs in [...slug] and only create static props for valid addresses in Next.js

I'm using dynamic routes and using fallback:true option to be able to accept newly created pages
first i check if parameters is true then i create related props and show the component with that props.
In console i can also see that next.js create new json file for that related page to not go to server again for the next requests to the same page.
But even I type wrong address next create new json file for that path. It means that next.js create json file for every wrong path request.
How can avoid that vulnerable approach?
export const getStaticProps: GetStaticProps = async ({ params }) => {
if (params.slug[0] === "validurl") {
const { products } = await fetcher(xx);
const { categories } = await fetcher(xx);
return { props: { categories, products } };
} else {
return { props: {} };
}
};
const Home = (props: any) => {
if (!props) {
return <div>404</div>;
}
return (
<MainLayout {...props}>
<FlowItems items={props.products} />
</MainLayout>
);
};
export async function getServerSideProps(context) {
console.log(context.params.slug);
...
You are in Server Side inside this getServerSideProps and passing the context lets you dynamically catch whatever value it takes for whatever request.
Then you can check the data you want to load like:
const slug = context.params.slug;
const data = await fetch(`${host}/endpoint/${slug.join('/')}`);
so the request will be like 'localhost:3000/endpoint/foo/slug/test
Then you can deal with those slugs and it's data in a backend logic (where it should be) in your endpoint (just to clarify this sort of logic it usually belongs to a gateway and not to an endpoint, this is just for educational purposes).
If the endpoint/gateway returns a 404 - Not found you can simply redirect to the 404.js page (which can be static), same for the rest of the possible errors available in your backend.

Trying call useQuery in function with react-apollo-hooks

I want to call useQuery whenever I need it,
but useQuery can not inside the function.
My trying code is:
export const TestComponent = () => {
...
const { data, loading, error } = useQuery(gql(GET_USER_LIST), {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
...
...
const onSaveInformation = async () => {
try {
await updateInformation({...})
// I want to call useQuery once again.
} catch (e) {
return e
}
}
...
How do I call useQuery multiple times?
Can I call it whenever I want?
I have looked for several sites, but I could not find a solutions.
From apollo docs
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to
events other than component rendering
I suggest useLazyQuery. In simple terms, useQuery will run when your component get's rendered, you can use skip option to skip the initial run. And there are some ways to refetch/fetch more data whenever you want. Or you can stick with useLazyQuery
E.g If you want to fetch data when only user clicks on a button or scrolls to the bottom, then you can use useLazyQuery hook.
useQuery is a declarative React Hook. It is not meant to be called in the sense of a classic function to receive data. First, make sure to understand React Hooks or simply not use them for now (90% of questions on Stackoverflow happen because people try to learn too many things at once). The Apollo documentation is very good for the official react-apollo package, which uses render props. This works just as well and once you have understood Apollo Client and Hooks you can go for a little refactor. So the answers to your questions:
How do I call useQuery multiple times?
You don't call it multiple times. The component will automatically rerender when the query result is available or gets updated.
Can I call it whenever I want?
No, hooks can only be called on the top level. Instead, the data is available in your function from the upper scope (closure).
Your updateInformation should probably be a mutation that updates the application's cache, which again triggers a rerender of the React component because it is "subscribed" to the query. In most cases, the update happens fully automatically because Apollo will identify entities by a combination of __typename and id. Here's some pseudocode that illustrates how mutations work together with mutations:
const GET_USER_LIST = gql`
query GetUserList {
users {
id
name
}
}
`;
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $name: String!) {
updateUser(id: $id, update: { name: $name }) {
success
user {
id
name
}
}
}
`;
const UserListComponen = (props) => {
const { data, loading, error } = useQuery(GET_USER_LIST);
const [updateUser] = useMutation(UPDATE_USER);
const onSaveInformation = (id, name) => updateUser({ variables: { id, name });
return (
// ... use data.users and onSaveInformation in your JSX
);
}
Now if the name of a user changes via the mutation Apollo will automatically update the cache und trigger a rerender of the component. Then the component will automatically display the new data. Welcome to the power of GraphQL!
There's answering mentioning how useQuery should be used, and also suggestions to use useLazyQuery. I think the key takeaway is understanding the use cases for useQuery vs useLazyQuery, which you can read in the documentation. I'll try to explain it below from my perspective.
useQuery is "declarative" much like the rest of React, especially component rendering. This means you should expect useQuery to be called every render when state or props change. So in English, it's like, "Hey React, when things change, this is what I want you to query".
for useLazyQuery, this line in the documentation is key: "The useLazyQuery hook is perfect for executing queries in response to events other than component rendering". In more general programming speak, it's "imperative". This gives you the power to call the query however you want, whether it's in response to state/prop changes (i.e. with useEffect) or event handlers like button clicks. In English, it's like, "Hey React, this is how I want to query for the data".
You can use fetchMore() returned from useQuery, which is primarily meant for pagination.
const { loading, client, fetchMore } = useQuery(GET_USER_LIST);
const submit = async () => {
// Perform save operation
const userResp = await fetchMore({
variables: {
// Pass any args here
},
updateQuery(){
}
});
console.log(userResp.data)
};
Read more here: fetchMore
You could also use useLazyQuery, however it'll give you a function that returns void and the data is returned outside your function.
const [getUser, { loading, client, data }] = useLazyQuery(GET_USER_LIST);
const submit = async () => {
const userResp = await getUser({
variables: {
// Pass your args here
},
updateQuery() {},
});
console.log({ userResp }); // undefined
};
Read more here: useLazyQuery
You can create a reusable fetch function as shown below:
// Create query
const query = `
query GetUserList ($data: UserDataType){
getUserList(data: $data){
uid,
first_name
}
}
`;
// Component
export const TestComponent (props) {
const onSaveInformation = async () => {
// I want to call useQuery once again.
const getUsers = await fetchUserList();
}
// This is the reusable fetch function.
const fetchUserList = async () => {
// Update the URL to your Graphql Endpoint.
return await fetch('http://localhost:8080/api/graphql?', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
}).then(
response => { return response.json(); }
).catch(
error => console.log(error) // Handle the error response object
);
}
return (
<h1>Test Component</h1>
);
}
Here's an alternative that worked for me:
const { refetch } = useQuery(GET_USER_LIST, {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
}
);
const onSaveInformation = async () => {
try {
await updateInformation({...});
const res = await refetch({ variables: { ... }});
console.log(res);
} catch (e) {
return e;
}
}
And here's a similar answer for a similar question.
Please use
const { loading, data, refetch } = useQuery(Query_Data)
and call it when you need it i.e
refetch()

How to structure API code in a Vue Single-Page App?

I'm building a fairly large SPA using Vue (and Laravel for RESTful API). I'm having a hard time finding resources about this online - what's a good practice to organise the code that communicates with the server?
Currently I have src/api.js file, which uses axios and defines some base methods as well as specific API endpoints (truncated):
import axios from 'axios';
axios.defaults.baseURL = process.env.API_URL;
const get = async (url, params = {}) => (await axios.get(url, { params }));
const post = async (url, data = {}) => (await axios.post(url, data));
export const login = (data) => post('users/login', data);
And then in my component, I can do
...
<script>
import { login } from '#/api';
...
methods: {
login() {
login({username: this.username, password: this.password})
.then() // set state
.catch() // show errors
}
}
</script>
Is this a good practice? Should I split up my endpoints into multiple files (e.g. auth, users, documents etc.)? Is there a better design for this sort of thing, especially when it comes to repetition (e.g. error handling, showing loading bars etc.)?
Thanks!
If you're just using Vue and expect to be fetching the same data from the same component every time, it's generally idiomatic to retrieve the data and assign it using the component's mounted lifecycle hook, like so:
<template>
<h1 v-if="name">Hello, {{name}}!</h1>
</template>
<script>
export default {
data() {
return {
name: '',
}
},
mounted() {
axios.get('https://example.com/api')
.then(res => {
this.name = res.data.name;
})
.catch(err =>
// handle error
);
},
};
</script>
If you're going to be using Vuex as mentioned in one of your comments, you'll want to put your API call into the store's actions property.
You'll end up with a Vuex store that looks something like this:
const store = new Vuex.Store({
state: {
exampleData: {},
},
mutations: {
setExampleData(state, data) {
state.exampleData = data;
},
},
actions: {
async getExampleData() {
commit(
'setExampleData',
await axios.get('https://www.example.com/api')
.then(res => res.data)
.catch(err => {
// handle error
});
);
},
}
});
Of course, breaking out your state, actions, and mutations into modules as your app grows is good practice, too!
If you use Vue CLI it will setup a basic project structure. With a HelloWorld component. You will want to break your vue app into components. Each component should have a defined role that ideally you could then unit test.
For example lets say you want to show list of products then you should create a product list component.
<Products :list="products" />
In your app you would do something like
data() {
return {
prodcuts: []
}
},
mounted() {
axios.get('/api/products').then(res => {
this.products = res.data
})
}
Whenever you see something that "is a block of something" make a component out of it, create props and methods and then on the mounted hook consume the api and populate the component.

What is the best way to fetch api in redux?

How to write best way to fetch api resource in react app while we use redux in application.
my actions file is actions.js
export const getData = (endpoint) => (dispatch, getState) => {
return fetch('http://localhost:8000/api/getdata').then(
response => response.json()).then(
json =>
dispatch({
type: actionType.SAVE_ORDER,
endpoint,
response:json
}))
}
is it best way to fetch api?
The above code is fine.But there are few points you should look to.
If you want to show a Loader to user for API call then you might need some changes.
You can use async/await the syntax is much cleaner.
Also on API success/failure you might want to show some notification to user. Alternatively, You can check in componentWillReceiveProps to show notification but the drawback will be it will check on every props changes.So I mostly avoid it.
To cover this problems you can do:
import { createAction } from 'redux-actions';
const getDataRequest = createAction('GET_DATA_REQUEST');
const getDataFailed = createAction('GET_DATA_FAILURE');
const getDataSuccess = createAction('GET_DATA_SUCCESS');
export function getData(endpoint) {
return async (dispatch) => {
dispatch(getDataRequest());
const { error, response } = await fetch('http://localhost:8000/api/getdata');
if (response) {
dispatch(getDataSuccess(response.data));
//This is required only if you want to do something at component level
return true;
} else if (error) {
dispatch(getDataFailure(error));
//This is required only if you want to do something at component level
return false;
}
};
}
In your component:
this.props.getData(endpoint)
.then((apiStatus) => {
if (!apiStatus) {
// Show some notification or toast here
}
});
Your reducer will be like:
case 'GET_DATA_REQUEST': {
return {...state, status: 'fetching'}
}
case 'GET_DATA_SUCCESS': {
return {...state, status: 'success'}
}
case 'GET_DATA_FAILURE': {
return {...state, status: 'failure'}
}
Using middleware is the most sustainable way to do API calls in React + Redux applications. If you are using Observables, aka, Rxjs then look no further than redux-observable.
Otherwise, you can use redux-thunk or redux-saga.
If you are doing a quick prototype, then making a simple API call from the component using fetch is good enough. For each API call you will need three actions like:
LOAD_USER - action used set loading state before API call.
LOAD_USER_SUCC - when API call succeeds. Dispatch on from then block.
LOAD_USER_FAIL - when API call fails and you might want to set the value in redux store. Dispatch from catch block.
Example:
function mounted() {
store.dispatch(loadUsers());
getUsers()
.then((users) => store.dispatch(loadUsersSucc(users)))
.catch((err) => store.dispatch(loadUsersFail(err));
}

Categories

Resources