I'm creating a login page using react, but when creating the context and an authentication hook I had a problem, the value of "authenticated" does not persist in true, I performed some tests to see its values and I saw that it alternated between true and false, but I didn't identify a loop or anything like that.
import backend from "../utils/backend";
import { useState, useEffect } from "react";
export default function useAuth() {
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
console.log(authenticated);
const token = localStorage.getItem("token");
if (token && !authenticated) {
authUser(token);
}
}, []);
setInterval(() => {
console.log(authenticated);
}, 300);
async function authUser(token) {
backend.defaults.headers.Authorization = `Bearer ${JSON.parse(token)}`;
try {
const data = await backend.get("/users/verify").then((res) => {
return res.data;
});
console.log(data, "response from back-end");
setAuthenticated(data);
} catch (error) {
console.log(error);
}
}
return { authenticated };
}
the console result
I tried to recreate the system, I changed the authUser function to setAuthenticated(true), but nothing I did worked
From react official document, you can learn about the custom hook.
https://reactjs.org/docs/hooks-custom.html
Do two components using the same Hook share state? No. Custom Hooks
are a mechanism to reuse stateful logic (such as setting up a
subscription and remembering the current value), but every time you
use a custom Hook, all state and effects inside of it are fully
isolated.
So, whenever you use useAuth in the separated components, it creates isolated state like useState. If you want to persist the value, you will need to use useContext or Redux
Related
I am new to React so please forgive me if this issue is entry-level to you.
Basically, I want to do the thing as the title goes,
here I have a function designed for this purpose:
function saveAndUpdate() {
// data processing and wrap up the finalized data, lets call it newData
useEffect(() => {
async function loadData() {
try {
await axios.put(API/updateEntry,{objToUpdate: newData}).then(() => {
const { loading, error, data } = axios.get(API/dataForTheTable)
callback(data);
});
} catch (error) {
console.error(error);
}
}
loadData();
}, []);
}
callback is a function in parent component in order to update the state related to the data immediately after the data is updated.
const handleCallback = (data) => {
setInfo(data);
}
And by running this, I get the classic error which I still do not fully understand:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Any help will be appreciated, thank you!
Seems that what you're doing is creating a hook (saveAndUpdate) and trying to access to It somewhere (for example, inside another hook or function).
Althought, a hook to update data (with axios.put, where you need to pass an argument; the data for the update) is not a good approach, as an UseEffect hook will run when the component is mounted, not when the user clicks on a button or whatever. For example, it would be better if you use this for gathering data (get request) from your backend when the component mounts.
Anyway this is a way you could do what you want to achieve, using the useCallback. I've created a button that fires the function and pass some data (just you can see the expected behaviour):
In the saveAndUpdate hook (name it as useSaveAndUpdate, just a convention for hooks names):
import { useEffect, useState } from "react";
export default function useSaveAndUpdate(dataUpdate) {
const [data, setData] = useState(null);
const [updating, setUpdating] = useState(false);
useEffect(() => {
if (updating) {
const req = function () {
// the put request (dataUpdate will be the data we use for the request)...
// for this i'm just going to set data to the argument we've passed
// the idea is to setData to the returned data from the server
setData(dataUpdate);
setUpdating(false);
// setting updating to false, if there's a re-render it won't do the request again
};
req();
}
}, [updating, dataUpdate]);
return [data, updating, setUpdating];
}
In the parent component:
import { useState, useCallback } from "react";
import useSaveAndUpdate from './useSaveAndUpdate'
const parent = () => {
const [updateTarget,setUpdateTarget = useState(null)
const [data,updating,setUpdating] = useSaveAndUpdate(updateTarget)
const updateFunction = useCallback((updateData) => {
setUpdating(true)
//
// ^ Setting updating to true will fire the request inside useSaveAndUpdate when re-render
//
setUpdateTarget(updateData)
// Setting the data for the update (this data will be used in the put request)
},[setUpdating,setUpdateTarget])
return (
<h3>{loading?'loading...':'not loading'}</h3>
<h3>{data?data.name:'data is null'}</h3>
{/*data will be updated when button clicked, so if it's a table just do data.map()...*/}
<button onClick={() => updateFunction({name:'jhon'})}>Click me to update</button>
)
}
So at first render, It will outpout that It's not loading and data is null.
When click It'll say loading for a sec (the time the request lasts), and the data will be displayed ('jhon').
A small codesandbox so you can see how it works.
This question already has answers here:
How to re-render a custom hook after initial render
(3 answers)
Closed 1 year ago.
I have custom hook named useIsUserSubscribed that checks to see a specific user is subscribed. It returns true if the user is subscribed and false if the user is not subscribed...
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { checkSubscription } from "../services";
// this hook checks if the current user is subscribed to a particular user(publisherId)
function useIsUserSubscribed(publisherId) {
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
const currentUserId = useSelector((state) => state.auth.user?.id);
useEffect(() => {
if (!currentUserId || !publisherId) return;
async function fetchCheckSubscriptionData() {
try {
const res = await checkSubscription(publisherId);
setUserIsSubscribed(true);
} catch (err) {
setUserIsSubscribed(false);
}
}
fetchCheckSubscriptionData();
}, [publisherId, currentUserId]);
return userIsSubscribed;
}
export default useIsUserSubscribed;
...I have a button using this hook that renders text conditionally based on the boolean returned from useIsUserSubscribed...
import React, { useEffect, useState } from "react";
import { add, remove } from "../../services";
import useIsUserSubscribed from "../../hooks/useIsUserSubscribed";
const SubscribeUnsubscribeBtn = ({profilePageUserId}) => {
const userIsSubscribed = useIsUserSubscribed(profilePageUserId);
const onClick = async () => {
if (userIsSubscribed) {
// this is an API Call to the backend
await removeSubscription(profilePageUserId);
} else {
// this is an API Call to the backend
await addSubscription(profilePageUserId);
}
// HOW CAN I RERENDER THE HOOK HERE!!!!?
}
return (
<button type="button" className="sub-edit-unsub-btn bsc-button" onClick={onClick}>
{userIsSubscribed ? 'Subscribed' : 'Unsubscribed'}
</button>
);
}
After onClick I would like to rerender my the useIsUserSubscribed hook So that my button text toggles. Can this be done? Should I use a different approach?
SubscribeUnsubscribeBtn has a dependency on useIsUserSubscribed, but useIsUserSubscribed don't depend on anything from SubscribeUnsubscribeBtn.
Instead, useIsUserSubscribed is keeping a local state. You have a couple of choices here:
Move the state regarding whetehr user is subscribed or not one level up, since you are using Redux, perhaps in Redux.
Communicate to useIsUserSubscribed that you need to change its internal state.
For 1)
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
move this state to Redux store and use it with useSelector.
For 2), return an array of value and callback from the hook, instead of just the value. It will allow you to communicate from component back into the hook.
In useIsUserSubscribed,
return [userIsSubscribed, setUserIsSubscribed];
Then in onClick, you can call setUserIsSubscribed(false), changing the hook's internal state, and re-rendering your component.
Recently I posted a question in which I was having some conditional rendering issues with a React/Okta project using TypeScript (for context):
Am I unable to use inline "if" with useEffect in a functional React component?
I've since solved that issue but now I'm experiencing another issue within the following component:
import React, { useState, useEffect } from 'react';
import { useOktaAuth } from '#okta/okta-react';
import { Dropdown } from 'semantic-ui-react';
import { UserInfo } from '../../interfaces/UserInfo';
export const HeaderUserMenu = () => {
// Okta and State hooks
const { authState, oktaAuth } = useOktaAuth();
const [ userInfo, setUserInfo ] = useState<UserInfo>({});
// Log current user data
const logUser = () => {
console.log(userInfo);
}
// Watch the auth state and user info for changes, update state.
useEffect(() => {
if (!authState || !authState.isAuthenticated) {
setUserInfo({});
} else {
oktaAuth.getUser((info: UserInfo) => {
setUserInfo(info);
})
}
}, [authState, oktaAuth]);
// If we have an empty user object, return nothing, otherwise...
if (Object.keys(userInfo).length === 0) {
return null;
} else {
return (
<Dropdown item text={userInfo.name}>
<Dropdown.Item onClick={logUser}>Log User</Dropdown.Item>
</Dropdown>
)
}
}
When I successfully log into Okta, authState and oktaAuth in the useOktaAuth() hook should have stuff in them, and via useState and the useEffect hook, I should be able to fetch the current user's info.
However, it's seeming as if authState and oktaAuth from the hook aren't making it into the effect being run, so even though I'm currently logged into the app, this component is not being rendered at all.
Is it not possible to use hooks like this inside of an effect? The documentation uses this exact pattern so I would have assumed this would be proper usage of useOktaAuth() and the Effect hook, but I can't get it working.
Also, one of the things I had to do to fix my previous issue was the addition of checking for !authState at the beginning of the effect, otherwise it -always- returns an error of:
TypeError: Cannot read property 'isAuthenticated' of null
So I'm not sure if this has something to do with it? There's NO reason that authState or oktaAuth should be null, unless the hook just isn't working? Or there's some issue in combining that hook with useEffect.
Any thoughts?
I have multiple tables, of small size, and I want to be able to write / read / update my components when the corresponding table has been updated by the app (we can consider it's a single user app for the moment).
I've been inspired by this question to write a custom Provider and associated hook for data fetching (and eventually posting) in my app: React useReducer async data fetch
I came up with this:
import React from "react";
import { useContext, useState, useEffect } from "react";
import axios from "axios";
const MetadataContext = React.createContext();
function MetadataContextProvider(props) {
let [metadata, setMetadata] = useState({});
async function loadMetadata(url) {
let response = await axios.get(url);
// here when I console.log the value of metadata I get {} all the time
setMetadata({ ...metadata, [url]: response.data });
}
async function postNewItem(url, payload) {
await axios.post(url, payload);
let response = await axios.get(url);
setMetadata({ ...metadata, [url]: response.data });
}
return (
<MetadataContext.Provider value={{ metadata, loadMetadata, postNewItem }}>
{props.children}
</MetadataContext.Provider>
);
}
function useMetadataTable(url) {
// this hook's goal is to allow loading data in the context provider
// when required by some component
const context = useContext(MetadataContext);
useEffect(() => {
context.loadMetadata(url);
}, []);
return [
context.metadata[url],
() => context.loadMetadata(url),
(payload) => context.postNewItem(url, payload),
];
}
function TestComponent({ url }) {
const [metadata, loadMetadata, postNewItem] = useMetadataTable(url);
// not using loadMetadata and postNewItem here
return (
<>
<p> {JSON.stringify(metadata)} </p>
</>
);
}
function App() {
return (
<MetadataContextProvider>
<TestComponent url="/api/capteur" />
<br />
<TestComponent url="/api/observation" />
</MetadataContextProvider>
);
}
export default App;
(the code should run in CRA context, both apis can be replaced with almost any API)
When I run it, a request is fired on both endpoints (/api/capteur and /api/observation), but where I'm expecting the metadata object in the MetadataContextProvider to have 2 keys: "/api/capteur" and "/api/observation", only the content of the last request made appears.
When I console.log metadata in the loadMetadata function, metadata always has the initial state hook value, that is {}.
I'm fairly new to React, I tried hard and I'm really not figuring out what's going on here. Can anyone help?
Your problem is how you update the metadata object with setMetadata.
The operation of updating the metadata object via loadMetadata in your context is done by two "instances" respectively: TestComponent #1 and TestComponent #2.
They both have access to the metadata object in your context, but they're not instantly synchronized, as useState's setter function works asynchronously.
The easy solution for your problem is called functional updates.
useState's setter does also provide a callback function, which will then use (I'm oversimplifying here) the "latest" state.
In your context provider:
async function loadMetadata(url) {
let response = await axios.get(url);
setMetadata((existingData) => ({ ...existingData, [url]: response.data }));
// instead of
// setMetadata({ ...metadata, [url]: response.data });
}
Here is a working CodeSandbox: https://codesandbox.io/s/elegant-mclean-syiol?file=/src/App.js
Look at the console to see the order of execution.
I highly recommend to fully read React hooks documentation, especially the "Hooks API Reference". There are also other problems with your code (for example missing dependencies in the useEffect hook, do you have ESLint enabled?).
If you want to have a better overview on how to use React's context I can recommend Kent C. Dodds' blog:
https://kentcdodds.com/blog/application-state-management-with-react
https://kentcdodds.com/blog/how-to-use-react-context-effectively
I have seen much more cases related to redirecting users in react applications and every case was just a different approach to the solution. There are some cases, where redirecting has occurred in actions like this`
export const someAction = (values, history) => async dispatch => {
const res = await someAsyncOperation(props);
history.push('/home');
dispatch(someAction);
}
In this example history object (form react-router) is being passed in react component. For me, this approach is not acceptable.
There is also a special Redirect from react-router.
After then I have already searched many articles and couldn't just find anything.
So in your opinion, what's the best practice for redirecting and where to handle such kind of processes ?
In React, you usually achieve redirects in the componentDidUpdate of your components.
In the case of async actions, you will check a flag stored in the Redux store, generally a boolean like isFetching, isCreating, isUpdating, etc…, which will be modified by the actions.
Simple example:
class EditUser extends Component {
compondentDidUpdate(prevProps) {
if (prevProps.isUpdating && !this.props.isUpdating) {
// ↑ this means that the async call is done.
history.push('/users')
}
}
updateUser() {
const modifiedUser = // ...
this.props.updateUser(modifiedUser)
// ↑ will change state.users.isUpdating from false to true during the async call,
// then from true to false once the async call is done.
}
render() {
// ...
<button onClick={this.updateUser}>Update</button>
// ...
}
}
const mapStateToProps = (state, props) => ({
userToEdit: state.users.items.find(user => user.id === props.userId)
isUpdating: state.users.isUpdating,
})
const mapActionsToProps = {
updateUser: usersActions.updateUser,
}
export default connect(mapStateToProps, mapActionsToProps)(EditUser)
The next step is usually to add another flag in your Redux store to track if the async calls are successful or not (e.g. state.users.APIError, in which you can keep the error returned by the API). Then you achieve the redirect only if there are no errors.
We mostly redirect a user due to when user logged in or when sign out. For example here's basic requireAuth HOC component to check if user is logged in or not and redirect him to another place.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};
There are two position to check if user is logged in
When the first time that component mount - in componentDidMount()
When user try to sign in , log in or sign out - in componentDidUpdate()
Also in your code sample, history.push is in an action creator. Action creators belongs to redux side. Keep redux & react separate.