1

I am trying to update setState in a for loop, but for some reason state isn't being copied it's just being replaced. There should be 2 clients, instead I am getting one. Can anyone tell me why this is happening? The console.log is returning both clients.

const handleViewClients = () => {
    for (let i = 0; i < clients.length; i++) {
      console.log(clients[i].clientid);
      fetch("http://localhost:3005/all-clients/" + clients[i].clientid)
        .then((response) => response.json())
        .then((result) => {
          console.log(result);
          setBarbersClient({
            ...barbersClient,
            client: result,
          });
        });
    }
  };

I have also tried this... The console.log is returning what I need

 Promise.all(
      clients.map((client) =>
        fetch("http://localhost:3005/all-clients/" + client.clientid)
      )
    )
      .then((resp) => resp.json())
      .then((result) => {
        console.log(result.username)
        setBarbersClient({
          ...barbersClient,
          client: result,
        });
        
      });

Here is the route from the server side

app.get("/all-clients/:clientid", (req, res) => {
  db.NewClientsx.findOne({
    where: {
      id: req.params.clientid,
    },
  }).then((response) => {
    res.json(response);
  });
});
Jayg713
  • 327
  • 3
  • 14
  • 2 `client` on your state? – bertdida Aug 16 '20 at 03:32
  • If I console.log result.username which is the key of the clients, it returns both clients to the console. But for some reason after I set to state only one is there @bertdida – Jayg713 Aug 16 '20 at 03:50
  • Will you be able to include the `/all-clients` response on your question? – bertdida Aug 16 '20 at 04:21
  • yes it is there now @bertdida – Jayg713 Aug 16 '20 at 04:24
  • What I mean on `response` is the actual `json` data - the data you'll see when you `console.log(result)`, is that something you can include? – bertdida Aug 16 '20 at 04:28
  • I'd rather not because it has information I don't want public at the moment. an example would be 2 different objects containing client information such as {id: 1, username: johndoe, etc} – Jayg713 Aug 16 '20 at 04:38

2 Answers2

0

There some fundamental concepts of sync vs. async code that you aren't accounting for here. State changing (and fetching) is asynchronous, so it won't run until after this synchronous loop has finished being executed (during which the state value will remain unchanged). Also, it's a bad idea to change state in a loop, for this reason and others.

Fetch all the clients, then do one state change at the end with all the fetched data. You can utilise things like Promise.all and Promise.spread to achieve this. Here's an example of doing multiple fetches then dealing with the results in one batch: How can I fetch an array of URLs with Promise.all?

Jayce444
  • 8,725
  • 3
  • 27
  • 43
  • I have added the updated code using promise.all at the top, still getting the same problem. Just returning one client. Thanks for the small lesson on async and sync @Jayce444 – Jayg713 Aug 16 '20 at 02:52
  • @Jayg713 `Promise.all` returns an array of results, so you shouldn't be calling `resp.json()` on that array. Print out each step in the `.then`, before adding each new one. Start with the `Promise.all(...).then(res => console.log(res))` and go from there – Jayce444 Aug 16 '20 at 03:43
  • I do need to call resp.json() otherwise it returns a non json array with headers and status codes, not the information that i need. When i console.log result.username it returns both clients, But when it is set to state only one client is there. I have even tried setting it in redux, same problem @Jayce444 – Jayg713 Aug 16 '20 at 03:49
0

You're making two distinct mistakes of which either is enough to cause the behaviour you're seeing.

1. You're overwriting the client property.

Every time you call the setter function you're overwriting the previous value of the client property. You'll need some data structure that supports multiple values like a map:

setBarbersClient({
  ...barbersClient,
  clients: {
    ...barbersClient.clients,
    [result.id]: result
  },
});

You will need to change your render logic somewhat to accomodate the new data structure.

2. You're using a stale reference.

When you access barbersClient its setter may have already been called with a different value and your reference to it still refers to the value of the previous run of the render function. You can make sure your reference is fresh by using a set state action callback.

setBarbersClient(previousValue => {
  ...previousValue,
  clients: {
    ...previousValue.clients,
    [result.id]: result
  },
});

previousValue will never be stale inside the set state action function body.

Jemi Salo
  • 3,401
  • 3
  • 14
  • 25