Atualizar product-summary

Ei pessoal, bom dia. Alguém já precisou atualizar tanto o contexto de product, quanto o contexto de product-summary da PLP (vtex.search)? Se sim, como consigo fazer isso?

Contexto da pergunta

  • Estou usando os dispatches SET_PRODUCT que estão disponíveis em vtex.product-context e vtex.product-summary-context.

  • No ProductContextProvider, existe um useEffect chamado useSelectedItemFromId que roda sempre que product ou query muda.

  • Esse effect useSelectedItemFromId procura o SKU de query dentro desse product, e chama um SET_SELECTED_PRODUCT. Caso nenhum SKU seja encontrado, selectedProduct fica undefined e todos os componentes que usam esse atributo param de funcionar corretamente (add-to-cart-button, sku-selector, etc).

  • O contexto de produto é criado através de um produto que vêm como prop, que é recuperado através de uma chamada de GraphQL. Esse produto é então copiado para um estado interno do provider, este que temos acesso e conseguimos alterar quando usamos um useProduct ou useProductDispatch.

  • O problema é que, o useEffect ali de cima é chamado apenas quando o product ou
    query que vêm de fora mudam. Por algum motivo que não consegui identificar, o SET_PRODUCT do productSummaryDispatch muda a variável query para um SKU do produto que acabei de selecionar. Isso trigga setSelectedItemFromId, mas como o product não está sincronizado com o valor do estado interno, ele não encontra o SKU em questão e selectedItem vai para null.

Não consegui entender porque nem onde query muda, e não consegui encontrar qual componente que cria esse product-context ao redor do product-summary. Alguém já passou por isso?

Código que estou usando

O código que estou usando, que adaptei do código encontrado em
vtex.product-summary-context:

function useSetProduct() {
  const productSummaryDispatch = useProductSummaryDispatch()
  const productDispatch = useProductDispatch()

  return (
    product: ProductSummaryTypes.Product,
    { replaceURL = false } = {}
  ) => {
    // Get first available SKU
    // If there is no SKU available, returns the first one
    const firstAvailableSKU =
      product?.items?.find((d) => {
        const seller = getDefaultSeller(d.sellers)

        return (seller?.commertialOffer?.[PROTERTY_AVAILABLE_QUANTITY] ?? 0) > 0
      }) || product?.items?.[0]

    /*
     * For some reason, the product within productSummaryContext has some extra properties,
     * such as an SKU attribute. This seems to be set by ProductSummarySKUSelector during initialization
     * and it's likely to be deprecated in the future, as the comment bellow suggests:
     *
     * "//TODO: STOP USING PRODUCT.SKU https://app.clubhouse.io/vtex/story/18547/productsummarycontext-refactor"
     *
     * ProductSummaryContext.tsx
     * https://github.com/vtex-apps/product-summary-context/blob/master/react/ProductSummaryContext.tsx#L81
     *
     * ProductSummarySKUSelector.tsx
     * https://github.com/vtex-apps/product-summary/blob/1d908de85f310541f058e0449a2aa553b8a1f783/react/ProductSummarySKUSelector.tsx#L189
     */

    const sku = {
      ...firstAvailableSKU,
      image: firstAvailableSKU.images[0],
      seller: getDefaultSeller(
        firstAvailableSKU.sellers
      ) as ProductSummaryTypes.Seller,
    }

    const newProduct = {
      ...product,
      selectedItem: firstAvailableSKU,
      sku,
    }

    productSummaryDispatch?.({
      type: 'SET_PRODUCT',
      args: { product: newProduct },
    })

    productDispatch?.({
      type: 'SET_PRODUCT',
      args: { product: (product as unknown) as ProductTypes.Product },
    })

    productDispatch?.({
      type: 'SET_SELECTED_ITEM',
      args: { item: (firstAvailableSKU as unknown) as ProductTypes.Item },
     })
  }
}

1 Like

Olá @pedrobernardina!

Não ficou muito claro pra mim qual a sua motivação para manipular os valores do ProductSummaryContext, você poderia explicar o que de fato está tentando mudar e a motivação?

De qualquer forma, pra esclarecer um pouco de onde estão vindo os dados, a query que você se referiu é “calculada” pelo próprio componente do ProductSummary, e na realidade seu valor é apenas o skuId do produto que o componente recebeu. Quanto ao ProductContext que está em volta de todo bloco product-summary, ele é criado no mesmo lugar onde a prop query é passada.

Uma coisa que me chamou atenção foi esse trecho aqui:

  • O problema é que, o useEffect ali de cima é chamado apenas quando o product ou
    query que vêm de fora mudam. Por algum motivo que não consegui identificar, o SET_PRODUCT do productSummaryDispatch muda a variável query para um SKU do produto que acabei de selecionar. Isso trigga setSelectedItemFromId, mas como o product não está sincronizado com o valor do estado interno, ele não encontra o SKU em questão e selectedItem vai para null.

Não entendi em qual cenário ele deveria se comportar diferente. As actions SET_PRODUCT em ambos os contextos não foram feitas para serem usadas “isoladas”, justamente porque podem gerar inconsistências. Você já chegou a dar uma olhada em como o SKU Selector lida com as mudanças de SKU? Ele é um bom exemplo de como manter a consistência quando você está mudando o contexto de produto.

Ei @victor.miranda, boa tarde!

Tentei ser o mais claro possível, mas como acabou ficando muito técnico, posso ter me perdido nas explicações.

Contexto: Nossos produtos possuem 2 variações: cor e tamanho. Por conta da limitação do número de SKUs por produto, tomamos a decisão de fazer cada cor como um produto diferente, e os tamanhos como SKUs. Sendo assim, montamos nosso seletor de cores manualmente, através da lista de produtos similares. Atualmente na PDP, cada cor é um link que redireciona o usuário para a página do produto correspondente (comportamento que queremos mudar inclusive, já que causa um refresh na página).

Problema: queremos trazer o seletor de cores para cada produto da PLP. Mas como as cores não são SKUs, e sim produtos, ao selecionar uma cor, precisamos alterar todo o contexto de product e product-summary.

Sobre o SET_PRODUCT, nós estamos chamando os 2, mas o erro está acontecendo por uma questão de hierarquia de providers. Hoje temos algo assim:

// https://github.com/vtex-apps/product-summary/blob/master/react/ProductSummaryCustom.tsx

function ProductSummaryWrapper({product, ...props}) {
  return (
    <ProductSummaryProvider product={product} ...>
      <ProductSummary product={product} ... />
    </ProductSummaryProvider>
  )
}

function ProductSummary({ product, ...props }) {
  ...
  return (
    <LocalProductSummaryContext.Provider value={oldContextProps}>
        <ProductContextProvider
          product={(product as unknown) as ProductTypes.Product}
          query={{ skuId }}
        >
         ...
       </ProductContextProvider>
    </LocalProductSummaryContext.Provider>
  )
}

Em ambos esses casos, quando chamamos SET_PRODUCT, apenas o product do estado interno dos providers é atualizado, enquanto a prop product permanece. Como consequência, skuId muda, o que muda a prop query do ProductContextProvider, e isso trigga o effect setSelectedItemFromId do ProductContext. O problema é que esse efeito utiliza product da prop ao invés do state. O app tenta então buscar um sku que não faz parte daquele produto, e selectedItem acaba ficando undefined.

Reorganizei os providers do ProductSummaryCustom.tsx e assim parece funcionar da forma que eu esperava. Basicamente, crio o contexto de produto do lado mais externo do componente, e acesso product através do hook de useProduct ao invés de ler da prop que vem de fora.

function Wrapper({ product, ...props }: PropsWithChildren<Props>) {
  const { query } = useRuntime()

  return (
    <ProductContextProvider
        product={(product as unknown) as ProductTypes.Product}
        query={query as any}>
        <ProductSummaryWrapper {...props} />
    </ProductContextProvider>
  )

function ProductSummaryWrapper(props: PropsWithChildren<Omit<Props, 'product'>>) {
  const { product } = useProduct(); // vem do contexto de produto ao invés de props
  ...
}

Qualquer outra dúvida estou disponível

Ei @victor.miranda, bom dia!

Seguimos a solução que coloquei ali, criando um ProductSummary customizado invertendo a ordem dos providers. Vamos investigar a forma sugerida, de alterar o dado em sua parte mais superior.

A limitação que encontramos é que esses componentes são passados como block, o que impossibilita (até onde sei) de criarmos um customizado, por conta do atributo allowed ou required no interfaces.json. Isso ocorre por exemplo na PLP, onde o block gallery é required para o search-content

Alguma sugestão de como podemos contornar isso e fazer do jeito certo?