SkyeBoniwell
SkyeBoniwell

Reputation: 7092

Why is my state not being set in my functional component?

I have this small React component, and it doesn't seem to be setting the state of my authToken in the useEffect() call.

Here is my code:

const App = ({ personId, buildingId }) => {
 
     const [authToken, setAuthToken] = useState();

     useEffect(() => {
             axios.post('/api/auth/' + personId + '/location/' + buildingId, {
                 headers: {
                     'Content-Type': 'application/json',
                 }
             }).then((res) => {
                 setAuthToken(res.data); 
             })
         }, []);
         

    return (
                 <Editor
                     init={{
                         tinydrive_token_provider: function (success, failure) {
                             success({ token: authToken.token });
                        }}
                    />
             )
         }

export default App;

Do I need to set it some other way?

Thanks!

Upvotes: 0

Views: 70

Answers (1)

Yanick Rochon
Yanick Rochon

Reputation: 53536

Try this:

const App = ({ personId, buildingId }) => {
  const handleTokenProvider = useCallback((success, failure) => {
    axios.post('/api/auth/' + personId + '/location/' + buildingId, {
      headers: {
        'Content-Type': 'application/json',
      }
    }).then((res) => {
      if (res && req.data && req.data.token) {
        success(res.data.token);
      } else {
        failure("Authentication failed");
      }
    }).catch(err => {
      failure(err);
    });
  }, [personId, buildingId]);

  return (
    <Editor
      init={{
        tinydrive_token_provider: handleTokenProvider
      }}
    />
  );
}

export default App;

The code above assumes a few things :

  1. That the property tinydrive_token_provider detects when it's value change and calls the function again when it does. If it does not, then there must be a way to update the token. And if it still does not, then unmounting the component and re-mounting it again will force recreating it.
  2. That you do not need the authentication token somewhere else in the component. If you do, then this solution will not be optimal. There would be ways to set a state (useState) or a reference (useRef) but there should be a way to call success/failure without requiring a component update.
  3. That every time you render the App component, a request will be made, even if you just got the token for the same props. Having a way to store this token in a cache (e.g. localStorage) could greatly improve performance on refresh.

Alternative

If you need to have access to the token elsewhere than the Editor, this would also work :

// this function get be in a separate module!
const fetchAuthToken = (personId, buildingId) => new Promise((resolve, reject) => {
  // TODO : check some cache first, and resolve if a
  //        previous token already exists and is not expired, etc.

  axios.post('/api/auth/' + personId + '/location/' + buildingId, {
    headers: {
      'Content-Type': 'application/json',
    }
  }).then((res) => {
    if (res?.data?.token) {
      resolve(res.data.token);
    } else {
      reject("Authentication failed");
    }
  }).catch(err => {
    reject(err);
  });
});


const App = ({ personId, buildingId }) => {

  const tokenPromise = useMemo(() => 
     fetchAuthToken(personId, buildingId),
     [personId, buildingId]
  );

  // DEBUG ONLY
  useEffect(() => {
    tokenPromise.then(token => {
       console.log('Token for', personId, buildingId, 'is', token);
    }, err => {
       console.error('Failed to get token for', personId, buildingId);
       console.error(err);
    });
  }, [tokenPromise, personId, buildingId]);

  return (
    <Editor
      init={{
        tinydrive_token_provider: (success, failure) => {
           tokenPromise.then(success, failure);
        }
      }}
    />
  );
}

export default App;

Upvotes: 1

Related Questions