简体   繁体   中英

no data is passed into state when using useContext/useReducer together with useQuery

export const itemReducer = (state, action) => {
  switch (action.type) {
    default:
      return state
  }
}
import React, { useState, useReducer, createContext, useContext } from 'react'
import { useQuery } from '@apollo/client'
import { CURRENT_MONTH_BY_USER } from '../graphql/queries'
import { itemReducer } from '../reducers/ItemReducer'

const Items = createContext()

export const ItemProvider = ({ children }) => {
  let items = []
  const [state, dispatch] = useReducer(itemReducer, { items: items })

  const result = useQuery(CURRENT_MONTH_BY_USER)
  if (result.data && result.data.getCurrentMonthByUser) {
    items = [...result.data.getCurrentMonthByUser]
  }

  return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}

export const ItemsState = () => {
  return useContext(Items)
}

export default ItemProvider

let items gets correct data from the useQuery , however nothing is passed into the state, therefore I am unable to transfer data into another components from the context. What am I doing wrong here?

When debugging both items and state they're initially empty because of the loading however then only the items receives correct data and state remains as empty array.

If i put static data into let items it works just fine, so maybe there can be something wrong with my useQuery as well?

It's easy to see your problem if you look at where items is used. That's only as the initial state to your useReducer call - but items is only set to a non-empty value after this. That has absolutely no effect on the component, because items is not used later in your component function, and the initial state is only ever set once, on the first render.

To solve this you need to embrace your use of a reducer, adding a new action type to set this initial data, and then dispatching that when you have the data. So add something like this to your reducer:

export const itemReducer = (state, action) => {
  switch (action.type) {
    case SET_INITIAL_DATA: // only a suggestion for the name, and obviously you need to define this as a constant
      return { ...state, items: action.items };
    /* other actions here */
    default:
      return state
  }
}

and then rewrite your component like this:

export const ItemProvider = ({ children }) => {
  const [state, dispatch] = useReducer(itemReducer, { items: [] })

  const result = useQuery(CURRENT_MONTH_BY_USER)
  if (result.data && result.data.getCurrentMonthByUser) {
    dispatch({ type: SET_INITIAL_DATA, items: result.data.getCurrentMonthByUser });
  }

  return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}

Also, while this is unrelated to your question, I will note that your ItemsState export appears to be a custom hook (it can't be anything else since it isn't a component but uses a hook) - that is perfectly fine but there is a very strong convention in React that all custom hooks have names of the form useXXX , which I strongly suggest you should follow. So you could rename this something like useItemsState (I would prefer useItemsContext to make clear it's just a useContext hook specialised to your specific context).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM