Search code examples
reactjsstaterecoiljs

React Custom Hook function is not reading recoil Atom


I am trying to make a multi-request, multi-step form with formik, typescript and recoil in a react project. I have created a custom hook that returns three functions, and one of them requires a specific UUID for the model it is working with, so that it can make a PUT request. When I make the POST request with another one of this same hook's functions, this UUID is set to a Recoil Atom, that I then try to access in the function below it, but it is not showing up properly. I have tried to await it and assign it to a variable, but to no avail, the state returns the value I need when I log it outside the function, but when I log it inside the function (where I need it to be), it's empty. And I am trying to avoid localstorage, since it would mean another type of data to manage that could potentially break the rest of my program.

This is the hook:

const useInstitution = () => {
  const setCurrentId = useSetRecoilState(institutionId);
  const currentId = useRecoilValue(institutionId);

  console.log(currentId);
  // The above line properly logs the UUID I want
  
  const handleValues = useCallback((values: InstitutionFormType) => {
    
    return {
      ...values,
      institution_registered_number: values.institution_registered_number.replace(/\D+/g, ''),
      institution_id: currentId || values.institution_id,
    };
  }, [currentId]);

  const insertInstitution = useCallback(async (values, callback) => {
    const newValues = handleValues(values);
    const {
      institution_logo_file,
    } = newValues;
    const response = await createInstitution(newValues);
    setCurrentId(response?.data.institution_id);
  }, [handleValues, setCurrentId]);

  const institutionEdit = useCallback(async (values, callback) => {
    const newValues = handleValues(values);
    console.log(currentId);
    // This line logs an empty value

  }, [currentId, handleValues]);

  return {
    insertInstitution, institutionEdit, currentId,
  };
};

export default useInstitution;

And this is the atom:

export const institutionId = atom({
  key: 'institutionId',
  default: '',
});

In this hook, when the createInstitution request is called and is succesful, the id is set to the Recoil Atom institutionId, and I try to log it in two different lines, one outside the functions and one inside the function institutionEdit, as such:

enter image description here

The first line is the log from the console.log outside the functions and inside the hook. The second line is the console.log inside the institutionEdit function.

Why does this happen? How can I properly access it in the hook's function?


Solution

  • useSetRecoilState documentation states that

    Returns a setter function which can be used asynchronously to change the state. The setter may either be passed a new value or an updater function which receives the previous value as an argument.

    The state is changed asynchronously by setter returned by useSetRecoilState. In order to execute something on the updated state, you should use a useEffect to monitor change and run the required code.

    See the example below.

    const {
      useCallback,
      useEffect,
      useContext,
    } = React;
    const {
      RecoilRoot,
      atom,
      useSetRecoilState,
      useRecoilValue
    } = Recoil;
    
    const institutionId = atom({
      key: 'institutionId',
      default: '',
    });
    
    const createInstitution = () => new Promise(res => setTimeout(() => res({
      data: {
        institution_id: Math.round(Math.random() * 50)
      }
    }), 1500))
    
    const useInstitution = () => {
      const setCurrentId = useSetRecoilState(institutionId);
      const currentId = useRecoilValue(institutionId);
    
      console.log(currentId);
      // The above line properly logs the UUID I want
    
      const handleValues = useCallback((values: InstitutionFormType) => {
    
        return {
          ...values,
          institution_registered_number: values.institution_registered_number.replace(/\D+/g, ''),
          institution_id: currentId || values.institution_id,
        };
      }, [currentId]);
    
      const insertInstitution = useCallback((values, callback) => {
        const newValues = handleValues(values);
        const {
          institution_logo_file,
        } = newValues;
        createInstitution(newValues).then(response =>
          setCurrentId(response && response.data.institution_id));
      }, [handleValues, setCurrentId]);
    
      const institutionEdit = useCallback((values, callback) => {
        const newValues = handleValues(values);
        console.log(currentId);
        // This line logs an empty value
    
      }, [currentId, handleValues]);
    
      return {
        insertInstitution,
        institutionEdit,
        currentId,
      };
    };
    
    const insts = new Array(10).fill().map((v, i) => ({
      institution_logo_file: 'logo 1',
      institution_registered_number: `reg no ${i}`
    }))
    
    const App = () => {
        const {
          insertInstitution,
          institutionEdit,
          currentId,
        } = useInstitution();
    
        useEffect(() => {
          insertInstitution(insts[0])
        }, [])
    
        // Getting the recoil state value
        const recoilValue = useRecoilValue(institutionId)
        useEffect(() => {
          institutionEdit(insts[0])
        }, [institutionEdit, recoilValue]) // execute only when `recoilValue` changes
        console.log(recoilValue)
    
        return ( < div > Hi < /div>)
        }
    
        ReactDOM.render( < RecoilRoot > < App / > < /RecoilRoot> , document.querySelector('#root'))
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/recoil.min.js"></script>
    <div id="root" />