Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Component not re rendering when value from useContext is updated

I'm using React's context api to store an array of items. There is a component that has access to this array via useContext() and displays the length of the array. There is another component with access to the function to update this array via useContext as well. When an item is added to the array, the component does not re-render to reflect the new length of the array. When I navigate to another page in the app, the component re-renders and reflects the current length of the array. I need the component to re-render whenever the array in context changes.

I have tried using Context.Consumer instead of useContext but it still wouldn't re-render when the array was changed.

//orderContext.js//

import React, { createContext, useState } from "react"

const OrderContext = createContext({
  addToOrder: () => {},
  products: [],
})

const OrderProvider = ({ children }) => {
  const [products, setProducts] = useState([])

  const addToOrder = (idToAdd, quantityToAdd = 1) => {
    let newProducts = products
    newProducts[newProducts.length] = {
      id: idToAdd,
      quantity: quantityToAdd,
    }
    setProducts(newProducts)
  }

  return (
    <OrderContext.Provider
      value={{
        addToOrder,
        products,
      }}
    >
      {children}
    </OrderContext.Provider>
  )
}

export default OrderContext
export { OrderProvider }
//addToCartButton.js//

import React, { useContext } from "react"
import OrderContext from "../../../context/orderContext"

export default ({ price, productId }) => {
  const { addToOrder } = useContext(OrderContext)

  return (
    <button onClick={() => addToOrder(productId, 1)}>
      <span>${price}</span>
    </button>
  )
}

//cart.js//

import React, { useContext, useState, useEffect } from "react"
import OrderContext from "../../context/orderContext"

export default () => {
  const { products } = useContext(OrderContext)
  return <span>{products.length}</span>
}
//gatsby-browser.js//

import React from "react"
import { OrderProvider } from "./src/context/orderContext"
export const wrapRootElement = ({ element }) => (
   <OrderProvider>{element}</OrderProvider>
)

I would expect that the cart component would display the new length of the array when the array is updated, but instead it remains the same until the component is re-rendered when I navigate to another page. I need it to re-render every time the array in context is updated.

like image 391
Jordan Paz Avatar asked Sep 08 '19 02:09

Jordan Paz


People also ask

How do I rerender a component with the latest context value?

When the nearest <MyContext.Provider>above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memoor shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext. ...

Does usecontext automatically re-render when the value changes from the method?

This does not appear to work with the useContext hooks, as components with useContext won’t automatically re-render when the value is changed from the method. I force a re-render I then see the correct values.

Why can’t usecontext trigger rerenders?

This is by design. If useContext were to conditionally trigger rerenders, the hook would become non-composable. There has been several discussions, especially in this issue . Currently, there’s no direct solution from React core. Three options are described in this issue.

How to update the context value of a component in Android?

The consumer then can update the context value by invoking the update function setUserName (newContextValue). <UserInfo /> is another consumer of the context. When <UserNameInput /> updates the context, this component is updated too. Note that <Application /> memoizes the context value.


3 Answers

@skovy's description helped me understand why my component was not re-rendering.

In my case I had a provider that held a large dictionary and everytime I updated that dictionary no re-renders would happen.

ex:

const [var, setVar] = useState({some large dictionary});

...mutate same dictionary
setVar(var) //this would not cause re-render
setVar({...var}) // this caused a re-render because it is a new object

I would be weary about doing this on large applications because the re-renders will cause major performance issues. in my case it is a small two page applet so some wasteful re-renders are ok for me.

like image 77
Cory Lewis Avatar answered Oct 11 '22 22:10

Cory Lewis


The issue is likely that you're mutating the array (rather than setting a new array) so React sees the array as the same using shallow equality.

Changing your addOrder method to assign a new array should fix this issue:

const addToOrder = (idToAdd, quantityToAdd = 1) =>
  setProducts([
    ...products,
    {
      id: idToAdd,
      quantity: quantityToAdd
    }
  ]);

Edit context-arrays

like image 39
skovy Avatar answered Oct 11 '22 22:10

skovy


As @skovy said, there are more elegant solutions based on his answer if you want to change the original array.

setProducts(prevState => {
  prevState[0].id = newId
  return [...prevState]
})
like image 28
mutoe Avatar answered Oct 11 '22 22:10

mutoe