1

I need the useProductList function to execute and finish all process before randomProduct function will execute.
For some reason it doesnt work when fetchData is a Promise so randomProduct wont be executed.
I even tried without Promise, nothing did work.

my custom hook

import { useState, useEffect } from "react";

export default function useProductList() {
  const [productList, setProductObjsList] = useState([]);
  const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
  let randomProducts = [];

  const fetchData = () =>
    new Promise(() => {
      arr.splice(1, 7);
      setProductObjsList(arr);
      return arr;
    });

  const randomProduct = (productArr) => {
    //some Math.random() and algorithm  with the productArr
    console.log("randomProduct()", productArr);
  };

  useEffect(() => {
    fetchData().then((result) => randomProduct(result));
  }, []);

  return randomProducts;
}

CodeSandbox

I will be glad if someone will open my eyes and show me the right way of how to do it.


EDIT:
My original fetchData function

const fetchData = () =>
   {
      fetch('http://localhost:49573/WebService.asmx/ProductList')
        .then((response) => response.text())
        .then(
          (xml) =>
            new window.DOMParser().parseFromString(xml, 'text/xml')
              .documentElement.firstChild.textContent
        )
        .then((jsonStr) => JSON.parse(jsonStr))
        .then((data) => {
          setProductObjsList(data);
        })
     });

randomProduct function

  ```const randomProduct = (productObjectList) => {
    const products = [...productObjectList];
    for (let index = 0; index < products.length; index++) {
      let idx = Math.floor(Math.random() * products.length);
      randomProducts.push(products[idx]);
      products.splice(idx, 1);
    }
  };```
Elassoa
  • 133
  • 2
  • 18
  • `setProductObjList` needs a new copy of the array every time for React to detect the state change. You don't need all that promise stuff. – tromgy Jan 07 '22 at 13:58
  • what are you expecting `arr.pop(1,7)` to do? `arr.pop()` has no parameters and removes on element from the end of `arr` – Mulan Jan 07 '22 at 18:51
  • 1
    can you describe in plain words how `useProductList` should function? does it fetch all products then return a single random product? what is `arr = [1,2,3,...10,11]` supposed to represent? `randomProducts` is initialized as `[]` but it is never updated elsewhere. – Mulan Jan 07 '22 at 18:58
  • @Mulan Hi, I edited and fixed and updated my question – Elassoa Jan 08 '22 at 07:19

2 Answers2

1

It's unclear what your exact intentions are, but from the name useRandomProduct I'm going to make a guess. Top things I'd like for you to take from this answer -

  1. You cannot mutate state in React. You cannot use Array.prototype.pop and expect your React components to work correctly. If a value is meant to change over the lifetime of the component, use useState.

  2. Don't put all of your functions inside of the hook. This tendency probably comes from class-oriented thinking but has no place in functional paradigm. Functions can be simple and do just one thing.

import { useState, useEffect } from "react"

// mock products
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

// mock fetch
const fetchData = () =>
  new Promise(resolve => {
    resolve(arr)
  })

// random picker
function choose1(all) {
  const i = Math.floor(Math.random() * all.length)
  return all[i]
}

export default function useRandomProduct() {
  // state
  const [product, setProduct] = useState(null)
  // effect
  useEffect(async () => {
    // fetch products
    const products = await fetchData()
    // then choose one
    setProduct(choose1(products))
  }, [])
  // return state
  return product
}

To use your new hook -

import { useRandomProduct } from "./useRandomProduct.js"
import Product from "./Product.js"

function MyComponent() {
  const product = useRandomProduct()
  if (product == null)
    return <p>Loading..</p>
  else
    return <Product {...product} />
}

Full demo -

const { useState, useEffect } = React

// useRandomProduct.js
const arr = [{name:"apple"},{name:"carrot"},{name:"pear"},{name:"banana"}]

const fetchData = () =>
  new Promise(r => setTimeout(r, 2000, arr))

function choose1(all) {
  const i = Math.floor(Math.random() * all.length)
  return all[i]
}

function useRandomProduct() {
  const [product, setProduct] = useState(null)
  useEffect(() => {
    fetchData().then(products =>
      setProduct(choose1(products))
    )
  }, [])
  return product
}

// MyComponent.js
function MyComponent() {
  const product = useRandomProduct()
  if (product == null)
    return <p>Loading..</p>
  else
    return <div>{JSON.stringify(product)}</div>
}

// index.js
ReactDOM.render(<div>
  <MyComponent />
  <MyComponent />
  <MyComponent />
</div>, document.querySelector("#main"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="main"></div>
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 2
    Hi and thank you for your effort, you're right I missed some things here and I edited, fixed and updated my question. I will be glad if you take a look at my two functions how they really are. – Elassoa Jan 08 '22 at 07:23
  • 1
    This part I didn't understand `new Promise(r => setTimeout(r, 2000, arr))` what is the `r` ? and why is it print two apples then 1 banana ? and 3. should my `let randomProducts = []` be a state (with `useState()`) ? – Elassoa Jan 08 '22 at 07:41
0

The problem is you are not resolving the promise. So, until the promise is resolved it will not go inside then() block.

Here's what you have to do

const fetchData = () =>
new Promise((resolve,reject) => {
  arr.pop(1, 7);
  setProductObjsList(arr);
  resolve(arr);
});

A promise has three stage.

  1. Pending
  2. Resolved
  3. Rejected

In your example the promise is always in pending state and never goes to the Resolved state. Once, a promise is resolved it moves to then() block and if it is rejected it moves to catch() block.

Pawan Patel
  • 195
  • 1
  • 1
  • 8
  • There are other problems here too, like using `arr` not as a part of `useState` and using `arr.pop` which mutates `arr`. – Mulan Jan 07 '22 at 18:52
  • I edited and updated my question :). Now where should I put the `catch()` ? and should my `let randomProducts = []` be a state ? – Elassoa Jan 08 '22 at 07:38