skip to Main Content

I am trying to call the useQuery hook (something not allowed outside of functional components), in a redux toolkit slice. Is this something that is doable, perhaps using createAsyncThunk or some similar method? My application is built with the MERN stack, so my database is MongoDB, and I’m using Graphql to fetch the data. (I’m ignoring the reducers in this example because I’m just focused on getting the data in and setting it to the initial state.)

import { useQuery } from "@apollo/client";
import { createSlice } from "@reduxjs/toolkit";
import { GET_USER_SETTINGS } from "../graphql/queries/settingsQueries";

const { loading, error, data } = useQuery(GET_USER_SETTINGS);

const { gridView } = data.settings;

const initialState = {
   gridView,
};

const settingsSlice = createSlice({
  name: "settings",
  initialState,
  reducers: {
    setGridViewOff(state) {
      state.gridView = false;
    },
    setGridViewOn(state) {
      state.gridView = true;
    }, 
},
});

export const { setGridViewOff, setGridViewOn } = settingsSlice.actions;

export default settingsSlice.reducer;

2

Answers


  1. You are correct that you cannot call useQuery from the Redux slice code. What I’d suggest here is to use some "undefined" initial gridView state and use the useEffect hook to "initialize" the gridView value.

    Something similar to the following example:

    import { createSlice } from "@reduxjs/toolkit";
    
    const initialState = {
       gridView: undefined,
    };
    
    const settingsSlice = createSlice({
      name: "settings",
      initialState,
      reducers: {
        initializeGridView: (state, action) => {
          state.gridView = action.payload;
        },
        setGridViewOff(state) {
          state.gridView = false;
        },
        setGridViewOn(state) {
          state.gridView = true;
        }, 
      },
    });
    
    export const {
      initializeGridView,
      setGridViewOff,
      setGridViewOn
    } = settingsSlice.actions;
    
    export default settingsSlice.reducer;
    
    import { useEffect } from "react";
    import { useQuery } from "@apollo/client";
    import { useDispatch } from "react-redux";
    import { initializeGridView } from "../path/to/settings.slice";
    import { GET_USER_SETTINGS } from "../graphql/queries/settingsQueries";
    
    const useInitializeGridView = () => {
      const dispatch = useDispatch();
      const { loading, data } = useQuery(GET_USER_SETTINGS);
    
      useEffect(() => {
        if (!loading) {
          const gridView = !!data?.settings?.gridView;
          dispatch(initializeGridView(gridView));
        }
      }, [loading, data]);
    };
    

    Call useInitializeGridView somewhere in your app near the root where Redux is in scope, e.g. under the Redux Provider component in the ReactTree. Any UI that is dependent on the state.settings.gridView value should check first if it is defined, then use the defined true/false value.

    Login or Signup to reply.
  2. You can handle this by creating a Redux Thunk action to fetch data and set the initial state and then dispatch the Redux Thunk action from your component.

    Example

    import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
    import { useQuery } from "@apollo/client";
    import { GET_USER_SETTINGS } from "../graphql/queries/settingsQueries";
    
    export const fetchUserSettings = createAsyncThunk("settings/fetchUserSettings", async (_, { dispatch }) => {
      try {
        const { data } = await useQuery(GET_USER_SETTINGS);
        const gridView = data.settings.gridView;
    
        return { gridView };
      } catch (error) {
        throw error;
      }
    });
    
    const initialState = {
      gridView: null,
    };
    
    const settingsSlice = createSlice({
      name: "settings",
      initialState,
      reducers: {
        setGridViewOff(state) {
          state.gridView = false;
        },
        setGridViewOn(state) {
          state.gridView = true;
        },
      },
      extraReducers: (builder) => {
        builder.addCase(fetchUserSettings.fulfilled, (state, action) => {
          state.gridView = action.payload.gridView;
        });
      },
    });
    
    export const { setGridViewOff, setGridViewOn } = settingsSlice.actions;
    
    export default settingsSlice.reducer;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search