0

how can I get the latest value of config in doSomething function? Do I need to put all the state values to a ref and then access it everywhere with the latest value? Is there a better/alternative way of handling this?

import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { createSelector } from 'reselect'

import { saveConfiguration } from '../../store/actions'

const selectConfig = createSelector([(state) => state.config.config], (val) => val)

function Configuration() {
  const [config, setConfig] = useState({})

  const dispatch = useDispatch()

  const store = {
    configuration: useSelector(selectConfig)
  }

  function doSomething() {
    console.log(config)
    // 3. How to get the updated config value from state?
    // Even I use useStateWithCallback custom hook and then call doSomething
    // again I didn't get the updated state value of config
  }

  function updateRedux() {
    return dispatch(saveConfiguration({ person: { name: 'John', age: 30 } })) // this returns a promise
  }

  function handleClick() {
    updateRedux().then(() => {
      //1. How to get latest value of store.configuration not in then params above?
      //2. Imagine I get the latest value and update the state with it
      setConfig(store.configuration)
      doSomething()
    })
  }

  return (
    <button onClick={() => handleClick()}>Update Redux</button>
  )
}

export default Configuration
kenanyildiz90
  • 176
  • 3
  • 13

1 Answers1

1

Instead of calling the function after the setConfig, try useEffect:

useEffect(() => {
  // do something..
}, [config])

This effect will be triggered automatically when config changed.

Not related to the question, but in your current code I can see that you are trying to sync a configuration from the redux store to a local state. I'm not sure about your use case, but it's unnecessary in most cases. Why not just use the value from the redux store? Having a single source of truth can prevent your code from becoming buggy and unmaintainable.


A more complete code:

function externalFunction(config) {
  console.log(config) // Always the latest
}

const Configuration = () => {
  const [config, setConfig] = useState({})

  function doThis() {
    console.log(config) // Always the latest
  }
  function doThat() {
    console.log(config.person) // Always the latest
  }

  useEffect(() => {
    doThis()
    externalFunction(config)
  }, [config])

  useEffect(() => {
    doThat()
  }, [config.person])

  return null
}

The above code should work, but it's not the best practice. If you have the hooks rule in eslint you will see some warning. You can use useCallback to solve the issue:

const doThis = useCallback(() => {
  console.log(config)
}, [config])

const doThat = useCallback(() => {
  console.log(config.person)
}, [config.person])

useEffect(() => {
  doThis()
  externalFunction(config)
}, [doThis, config])

useEffect(() => {
  doThat()
}, [doThat])

A small demo: https://codesandbox.io/s/stackoverflow-useeffect-config-mn1x7v

CodinCat
  • 15,530
  • 5
  • 49
  • 60
  • I update state in multiple places and therefore If I use a useEffect, I wouldn't understand after which certain action [config] is changed. Calling useEffect is not work in my case. Imagine I have doThat function and I need to use config(latest version) how to handle it? I must copy the redux store to the state, because user can do some changes over the config and if user wants to save it, I update the redux store, otherwise redux store should stay same. – kenanyildiz90 Apr 02 '22 at 10:32
  • If an action only changes a certain part of the config, then you can use something like `[config.person]` as the `useEffect`'s dependency. In this case it will only be triggered when `person` changed. – CodinCat Apr 02 '22 at 10:38
  • @CodinCat there are some scenarios where it's acceptable to duplicate the global state locally to a Component, imagine he has some controlled inputs that change that config locally, you don't want to edit the config globally until the user submitted it and they are updated on the db. In any case, a useEffect is the right way to proceed for me, you just have to decide what deps you want to trigger that effect. – Cesare Polonara Apr 02 '22 at 13:29
  • Yes op has explained his use case. I think it's a reasonable scenario to copy the state. – CodinCat Apr 02 '22 at 13:33
  • @CodinCat and Cesare, Imagine I have multiple functions and they use state values inside themselves. Every time when I need the latest value how can program it with useEffect? useEffect only works at the top level of the component. I just need to get latest version of state in functions, as we do in react class components.. – kenanyildiz90 Apr 02 '22 at 17:33
  • I don't quite understand why it's not working for you, but I just added a more complete code to the answer. You can have multiple `useEffect` listens to different parts of the config. And of course you can extract those `doSomething` functions to wherever you want, and just call them in the `useEffect` – CodinCat Apr 03 '22 at 02:16
  • Thank you. I've checked your codesandbox project. The problem is whenever you update config.person (resetConfig, updateConfig, updateNote) it trigger the useEffect works because config.person changed. It's OK. What if I would need to run different function than doSomething function if config.person changes? For some conditions, I want to run doSomething in other condition i want to run doThat function? In your example, doSomething runs everytime config.person changes, I want to make them work under some if conditions. – kenanyildiz90 Apr 04 '22 at 15:59
  • I'm not sure what you mean by the "conditions". You can always put some conditionals to the useEffect. Or for example, if doThat only cares about the `person.age`, then just create a new useEffect that only has `config.person.age` dependency. – CodinCat Apr 04 '22 at 16:22