Creating an app which allows you to sign up and sign in user in the database. After sign up / sign in process the app moves you to the content.
Code for authorization process:
const Auth = ({ handleSetAuthorized }) => {
const { refetch } = useQuery(CURRENT_USER)
const { error } = useSelector(({ error }) => error)
const history = useHistory()
const dispatch = useDispatch()
const [signup] = useMutation(SIGN_UP, {
onCompleted: async (data) => {
const token = data.signup
localStorage.setItem("token", token)
await refetch()
handleSetAuthorized()
history.push("/pages/edituser")
},
onError: error => dispatch(getError(error))
})
const [login] = useMutation(SIGN_IN, {
onCompleted: async (data) => {
const token = data.login.token
localStorage.setItem("token", token)
await refetch()
handleSetAuthorized()
history.push("/pages/process")
},
onError: error => dispatch(getError(error))
})
const signUp = (values) => {
signup({
variables: {
firstName: values.firstName,
secondName: values.secondName,
email: values.email,
password: values.password,
}
})
}
const signIn = (values) =>{
login({
variables: {
email: values.email,
password: values.password,
}
})
}
const removeError = () => {
dispatch(cleanError())
}
return (
<>
<div className="auth">
<img
className="auth__logo"
src={logo}
alt="logo"
/>
<div className="auth__content">
<Switch>
<Route path="/auth/signin" >
<SignIn onSubmit={signIn} removeError={removeError}/>
</Route>
<Route path="/auth/signup">
<SignUp onSubmit={signUp} removeError={removeError}/>
</Route>
</Switch>
{error &&
<ErrorMessage className="mistake">
{error.message}
</ErrorMessage>
}
</div>
</div>
</>
)
}
export default Auth
As you can see after mutation is completed, I need to refetch my CURRENT_USER query to understand who is current user and to move user to the content. I do that here:
const { refetch } = useQuery(CURRENT_USER)
const [signup] = useMutation(SIGN_UP, {
onCompleted: async (data) => {
const token = data.signup
localStorage.setItem("token", token)
await refetch() // <- HERE!
handleSetAuthorized()
history.push("/pages/edituser")
},
onError: error => dispatch(getError(error))
})
Code works but the problem is I don't want to refetch query like that: import CURRENT_USER query itself, get refetch function from useQuery hook and use it inside onCompleted option of mutation.
ApolloClient provides next:
I can put refetchQueries option inside useMutation hook to refetch my CURRENT_USER query like that:
const [signup] = useMutation(SIGN_UP, {
refetchQueries: [{query: CURRENT_USER }], // <- HERE!
onCompleted: (data) => {
const token = data.signup
localStorage.setItem("token", token)
handleSetAuthorized()
history.push("/pages/edituser")
},
onError: error => dispatch(getError(error))
})
That approach doesn't work because refetchQueries option updates CURRENT_USER query before I get and put token from mutation result in localStorage. So user is not moved to content because CURRENT_USER query result is empty and app shows user a mistake.
I can use 'refetch-queries' npm package. It provides the option to refetch Apollo queries anywhere by name and partial variables. Perfect for my case and look like that:
const [signup] = useMutation(SIGN_UP, {
onCompleted: (data) => {
const token = data.signup
localStorage.setItem("token", token)
refetchQueries: [{query: CURRENT_USER }], // <- HERE!
handleSetAuthorized()
history.push("/pages/edituser")
},
onError: error => dispatch(getError(error))
})
So here I use that option inside onCompleted option and after putting token in localStorage. Seems perfect in my case but doesn't work. I have no idea why, it throws the same mistake which shows that CURRENT_USER query result is empty. Maybe package is not supported.
My wish is to refetch CURRENT_USER query after pushing user to the content and before content (Pages component) initialization. But how to do that using useEffect hook inside content (Pages component) and without componentWillMount method.
And what is the best practice for refetching queries after mutation in my situation?
Related
I don't know why the below code works properly without any error and response is also recorded once the loginHandler() is fired but once I add the dispatch function inside the try block after the response has got, the catch block with the check whether a response has been got or not executes!
function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [msg, setMsg] = useState("");
const dispatch = useDispatch();
const LoginHandler = async (e) => {
e.preventDefault();
try {
const response = await axios.post(
LOGIN_URL,
JSON.stringify({ username, password }),
{
headers: { "Content-Type": "application/json" },
withCredentials: false,
}
);
console.log(JSON.stringify(response?.data?.access));
dispatch(
login({
username: username,
accessToken: response?.data?.access,
})
);
const userName = useSelector((state) => state.user.value.userName);
const accessToken = useSelector(
(state) => state.user.value.userAccessToken
);
console.log("USER NAME STORED IN STORE = " + userName);
console.log("USER ACESS TOKEN STORED IN STORE = " + accessToken);
setMsg("Login Successful!");
} catch (err) {
if (!err?.response) {
console.log("NO SERVER RESPONSE");
setMsg("NO SERVER RESPONSE!");
} else {
console.log("SOMETHING WRONG!");
setMsg("SOMETHING WENT WRONG!");
}
}
};
return (
<LoginForm />
);
}
export default Login;
The Error I get is,
react-dom.development.js:16227 Uncaught (in promise) 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:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
at Object.throwInvalidHookError (react-dom.development.js:16227:9)
at useContext (react.development.js:1618:21)
at useReduxContext (useReduxContext.js:21:24)
at useSelector2 (useSelector.js:40:9)
at LoginHandler (Login.jsx:49:22)
I have also tried to put the dispatch function inside another function and then call it, but the problem persists!
The issue it seems is that the code is attempting to use the useSelector hook in a nested function, which breaks the Rules of Hooks.
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function,
before any early returns. By following this rule, you ensure that
Hooks are called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of Hooks
between multiple useState and useEffect calls.
Move the useSelector calls out of the callback, or just reference the response values directly.
Use the useEffect hook if you care to log the updated state values.
Example:
function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [msg, setMsg] = useState("");
const dispatch = useDispatch();
const userName = useSelector((state) => state.user.value.userName);
const accessToken = useSelector(
(state) => state.user.value.userAccessToken
);
useEffect(() => {
console.log("USER NAME STORED IN STORE = " + userName);
console.log("USER ACCESS TOKEN STORED IN STORE = " + accessToken);
}, [userName, accessToken]);
const LoginHandler = async (e) => {
e.preventDefault();
try {
const response = await axios.post(
LOGIN_URL,
JSON.stringify({ username, password }),
{
headers: { "Content-Type": "application/json" },
withCredentials: false,
}
);
console.log(JSON.stringify(response?.data?.access));
dispatch(
login({
username: username,
accessToken: response?.data?.access,
})
);
console.log("USER NAME USED = " + username);
console.log("USER ACCESS TOKEN RETURNED = " + response?.data?.access);
setMsg("Login Successful!");
} catch (err) {
if (!err?.response) {
console.log("NO SERVER RESPONSE");
setMsg("NO SERVER RESPONSE!");
} else {
console.log("SOMETHING WRONG!");
setMsg("SOMETHING WENT WRONG!");
}
}
};
return (
<LoginForm />
);
}
export default Login;
Hopefully a simply one.
I make an API call in my component which brings down some account information such as AccountUid, Category etc, i use state to set these.
useEffect(() => {
fetch(feed_url, {
headers: {
//Headers for avoiding CORS Error and Auth Token in a secure payload
"Access-Control-Allow-Origin": "*",
Authorization: process.env.REACT_APP_AUTH_TOKEN,
},
})
//Return JSON if the Response is recieved
.then((response) => {
if (response.ok) {
return response.json();
}
throw response;
})
//Set the Account Name state to the JSON data recieved
.then((accountDetails) => {
setAccountDetails(accountDetails);
console.log(accountDetails.accounts[0].accountUid);
console.log(accountDetails.accounts[0].defaultCategory);
})
//Log and Error Message if there is an issue in the Request
.catch((error) => {
console.error("Error fetching Transaction data: ", error);
});
}, [feed_url]);
This Works perfectly well and it Logs the correct values in my .then when testing it.
The issue however is that i want to pass these down as props. But i get an error that they are being returned as null (My default state).. i presume as they're jumping ahead.
<div className="App">
<GetAccountName
accountUID={accountDetails.accounts[0].accountUID}
defCategory={accountDetails.accounts[0].defaultCategory}
/>
</div>
How do i pass the the 2 details im logging as props?? I've tried setting default state to "" instead of null and just get that it is undefined.
If you dont want to use conditional render in your child component, so you should try optional chaining
<GetAccountName
accountUID={accountDetails?.accounts?.[0]?.accountUID}
defCategory={accountDetails?.accounts?.[0]?.defaultCategory}
/>
Since fetching is asyncronous, the most common way is to show some loading indicator (like a spinner) & once the data come in, show the component instead.
If you don't need an indicator, you might just return null.
The general idea is to manipulate some intermediary states (e.g. data, isError) based on the promise state.
Check out react-query library example or a lighter abstraction like useFetch hook to see how they manage it.
Here's a sample implementation of useFetch taken from this article:
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const [abort, setAbort] = React.useState(() => {});
React.useEffect(() => {
const fetchData = async () => {
try {
const abortController = new AbortController();
const signal = abortController.signal;
setAbort(abortController.abort);
const res = await fetch(url, {...options, signal});
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
return () => {
abort();
}
}, []);
return { response, error, abort };
};
I'm using the Listen Notes podcast-api in Next.js api routes. I want the user query from my front-end form to interact with the Next.js api. So when a user inputs "history", it will send that to the api to look for podcasts that are about that topic.
I tried adding params to axios request but that doesn't seem to work.
Front-end:
export default function SearchBar() {
const [query, setQuery] = useState("");
const [address, setAddress] = useState("");
const fetcher = async (url) => await axios.get(url, { params: {q: query } }).then((res) => res.data);
const { data, error } = useSWR(address, fetcher);
const handleSubmit = (e) => {
setAddress(`/api/podcast`);
e.preventDefault();
};
return (
<>
<Form onSubmit={handleSubmit}>
<Input
onChange={(e) => setQuery(e.target.value)}
/>
<SearchButton type="submit">Search</SearchButton>
</Form>
</>
);
}
Api code:
const { Client } = require("podcast-api");
export default function handler(req, res) {
const client = Client({
apiKey: process.env.REACT_APP_API_KEY || null,
});
//"q" below should contain the user query from front-end
client
.search({ q: req.params })
.then((response) => {
res.status(200).json(response.data);
})
.catch((error) => {
console.log("apiError: " + error);
});
}
Please let me know if I need to provide more information.
Check if API on you NextJs app, is receiving correctly the environment variable process.env.REACT_APP_API_KEY. Use console.log to test.
Reference :
https://nextjs.org/docs/basic-features/environment-variables
Another check is the API path, cause NextJs use your own method to path this, you know ?!
Reference :
https://nextjs.org/docs/api-routes/introduction
Hope this tips help you ;)
The problem was in the api route, it should be:
.search({ q: req.query.q })
req.params is something else on server side and that's why it wasn't coming through.
I am using Firebase's Web SDK in my Expo project, Redux is included too.
firebase.auth().signInWithEmailAndPassword() returns a Promise; I used .then() to handle the Promise and .catch() for any errors. This action is done in a separate file, not in the Login Screen because I dispatch an action in the then() clause.
Question is how would I be able to catch the error at the client's side? I can't seem to find any scenario that is similar to mine.
It's a school project :') Appreciate any help please!
LoginScreen:
I have tried calling the Toast from a useEffect and in the catch (error) { ... } clause too but to no avail. I know this way would not work but I cant seem to find any solution online.
const [loading, setLoading] = React.useState(false);
const [showAlert, setShowAlert] = React.useState(false);
const [alertMessage, setAlertMessage] = React.useState('');
const [alertStatus, setAlertStatus] = React.useState('');
const [error, setError] = React.useState(null);
const dispatch = useDispatch();
React.useEffect(() => {
if (error) {
// setAlertMessage(error);
// setShowAlert(true);
// setAlertStatus('danger');
Alert.alert('Error Occured', error, [{ text: 'Close' }]);
}
}, [error]);
const logInHandler = async () => {
try {
if (email.length === 0 || password.length === 0) {
setAlertMessage('You have empty fields!');
setShowAlert(true);
setAlertStatus('warning');
return;
}
dispatch(authActions.logIn(email, password));
setError(null);
setLoading(true);
setAlertMessage('Logging In...');
setShowAlert(true);
setAlertStatus('info');
} catch (err) {
// setAlertMessage(err.message);
// setShowAlert(true);
// setAlertStatus('danger');
setError(err.message);
setLoading(false);
}
};
return ( // i still don't get how to format properly when i paste codes here, sorry
{showAlert && (
<Toast // this is a custom toast in a separate component that i made that only shows when there is an error
message={alertMessage}
status={alertStatus}
hide={show => setShowAlert(show)}
/>
)}
);
File containing the firebase auth methods:
Throwing the error causes an Exception, but what i'm trying to achieve is to get the error message and display it using the custom Toast if there is an error from firebase
export const signUp = (email, password, name, gender) => dispatch => {
firebase
.auth()
.createUserWithEmailAndPassword(email.trim().toLowerCase(), password)
.then(res => {
firebase
.auth()
.currentUser.updateProfile({ displayName: name })
.then(() => {
dispatch({
type: STORE_USER_DATA,
id: res.user.uid,
user: res.user.displayName
});
dispatch({
type: SET_REGISTER,
isRegistering: true,
gender: gender
});
})
.catch(err => console.log('err.message', err.message));
})
.catch(err => {
let message = 'actions.signUp: An error has occured!';
let hasError =
err.code === 'auth/email-already-in-use' ||
err.code === 'auth/invalid-email' ||
err.code === 'auth/weak-password';
if (hasError) {
message = 'Invalid Credentials!';
}
throw new Error(message);
});
};
you probably need to dispatch the error instead of throwing the error - because the main usage of redux is state management so rather than throwing the error it makes more sense that you have a state that indicates if the login has failed or succeeded.
However i think it might be worth it to look at react-redux-firebase package its a clean integration between firebase and react/redux application - you could checkout authReducers, and authErrorReducers i think they might be helpful
I'm using nextjs and apollo (with react hooks). I am trying to update the user object in the apollo cache (I don't want to refetch). What is happening is that the user seems to be getting updated in the cache just fine but the user object that the component uses is not getting updated. Here is the relevant code:
The page:
// pages/index.js
...
const Page = ({ user }) => {
return <MyPage user={user} />;
};
Page.getInitialProps = async (context) => {
const { apolloClient } = context;
const user = await apolloClient.query({ query: GetUser }).then(({ data: { user } }) => user);
return { user };
};
export default Page;
And the component:
// components/MyPage.jsx
...
export default ({ user }) => {
const [toggleActive] = useMutation(ToggleActive, {
variables: { id: user.id },
update: proxy => {
const currentData = proxy.readQuery({ query: GetUser });
if (!currentData || !currentData.user) {
return;
}
console.log('user active in update:', currentData.user.isActive);
proxy.writeQuery({
query: GetUser,
data: {
...currentData,
user: {
...currentData.user,
isActive: !currentData.user.isActive
}
}
});
}
});
console.log('user active status:', user.isActive);
return <button onClick={toggleActive}>Toggle active</button>;
};
When I continuously press the button, the console log in the update function shows the user active status as flipping back and forth, so it seems that the apollo cache is getting updated properly. However, the console log in the component always shows the same status value.
I don't see this problem happening with any other apollo cache updates that I'm doing where the data object that the component uses is acquired in the component using the useQuery hook (i.e. not from a query in getInitialProps).