nuxt.jsnuxt3.js

How to implement a SSR Friendly composable with shared state in Nuxt3?


I have a search function in my app. It calls a Rest API and returns results. In order to avoid double calls, I am using useAsyncData:

const { data, status, error, execute } = await useAsyncData('item', () => $fetch('https://dummyjson.com/users/search?q=John'));

Now I want to use this search function in multiple components in my app, e.g. a navbar search and a search page. In order to avoid code duplication, I placed the all the search related code inside a new composable: useSearch.

export default () => {  
    const toSearch = ref('John');
    const fetchResponse = async () => useAsyncData('item', () => $fetch('https://dummyjson.com/users/search?q=' + toSearch));
    return {
        toSearch,
        fetchResponse,
    };
};

which I can then use like this in my components:

const { toSearch, fetchResults } = useSearch();
toSearch.value = 'john';
const { data, status, error, execute  } = await fetchResults();

This works, but it poses a few problems when I want to have only one global state with the current search results and loading state. So far, if two components use this composable, each will get their own instance of that composable (as per design).

In order to share the state, I used useState:

const toSearch = useState(() => 'John');

Now every instance of that composable will have the same search term. But I also need the results, pending and status to be shared. I cannot do this because I cannot access those inside my composable. Those values are only returned once useAsyncData is called from somewhere. The only thins I can access are the results, inside the transform hook:

export default () => {  
    const toSearch = useState(() => 'John');
    const results = useState(() => undefined);
    const fetchResponse = async () => useAsyncData('item', () => $fetch('https://dummyjson.com/users/search?q=' + toSearch), {
        transform: (res) => {
            results.value = res;
        }
    } );
    return {
        toSearch,
        fetchResponse,
        results 
    };
};

My question:

Am I doing this completely wrong? I just need a search function with a global state in order to access it from different components. One sets the search term, another executes the search, and a third components shows the results. A loading state can be used to enable/disable a search button. How can I achieve this in a SSR friendly way?


Solution

  • You can initialise a plugin, with the state and just refresh API.

    export default defineNuxtPlugin(async (nuxtApp) => {
      const query = useState('search-query', () => "");
      
      const data = useState('search-data');
      const status = useState('search-status');
      const error = useState('search-error');
    
      const res = useAsyncData('..', () =>
        $fetch('....', {
          query: { q: query.value },
          immediate: false, // this will prevent wasted API call.
        }),
        { watch: [query] }
      );
      
      watchEffect(() => {
        data.value = res.data.value;
        status.value = res.status.value;
        error.value = res.error.value;
      });
    });
    

    Now use a composable to just expose the state.

    export function useSearch() {
      const query = useState('search-query');
      
      const data = useState('search-data');
      const status = useState('search-status');
      const error = useState('search-error');
    
      return { query, data, status, error };
    }