How can I invoke function after screen mounted? - javascript

In the home screen, I have a function that's getting the 'user type: Paid / UnPaid' from API,
So in this screen, I have a drawer inside it a user can navigate to sign in screen, so after user sign in I navigate him back to Home screen
but in this case, the Home screen is already mounted that's mean userIsPaid function is already invoked for the first time, so I want to recheck if this account is paid or not again "invoke userisPaid()"?
because in Bottom Tab, I render a new tab conditionally based on user type and it does not work because I'm not rechecking user type.
Code
Root.js
const BottomTabStack = createBottomTabNavigator();
function BottomTabs() {
const isPaid = useSelector(state => state.userIsPaid.isPaid);
...
<BottomTabStack.Navigator
lazy={false}
tabBar={props => <TabBar {...props} />}
>
...
{isPaid && (
<BottomTabStack.Screen
name="Library"
component={YourLibraryStackScreen}
/>
)}
</BottomTabStack.Navigator>
}
Home.js
userIsPaid = async () => {
try {
const {token} = this.props;
let AuthStr = `Bearer ${token}`;
let response = await API.get('/profile', {
headers: {Authorization: AuthStr},
});
const {account} = response.data.data;
console.log('account??', account);
account === 'unpaid'
? this.props.isUserPaid(false)
: this.props.isUserPaid(true);
} catch (err) {
console.log(err);
}
};
componentDidMount() {
const {token} = this.props;
token && this.userIsPaid();
}
SignIn.js
sendData = async data => {
// this.setState({loading: true});
try {
let response = await API.post('/google', data);
// this.setState({loading: false});
let {
data: {
data: {
response: {token},
},
},
} = response;
this.props.dispatch(saveToken(token));
this.props.dispatch(isLoginFunc(true));
this.props.navigation.push('BottomTabNavigator'); // bottom navigation 'home,about....'
} catch (err) {
console.log(err);
}
};

Using shouldComponentUpdate() lifecycle method mybe useful.
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases, you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.

Related

React.js, Auth Component does not redirect properly

I have created this Auth Component and it works fine. Except that, It does not redirect if the unauthenticated user tries to visit /dashboard.
The backend upon receiving /api/me request, knows the user by having the cookie. So I have (Cookie-Session) Authentication technique.
export const UserContext = createContext();
const Auth = ({ children }) => {
const [user, setUser] = useState(null);
const [gotUser, setGotUser] = useState(false);
const navigate = useNavigate();
const getUser = async () => {
const res = await fetch('/api/me');
const data = await res.json();
setUser(data);
if (user) {
setGotUser(true);
}
};
useEffect(() => {
if (!gotUser) {
getUser();
}
}, [user, gotUser, navigate]);
if (!user) {
navigate('/login');
}
console.log(user);
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
So the main issue is that no redirection done. Also, The user passed to the context is not updated properly. Maybe because I am confused about what to use in useEffect .
Any help is appreciated.
Issues
There are a couple issues:
The "unauthenticated" state matches the "I don't know yet" state (i.e. the initial state value) and the component is redirecting too early. It should wait until the user state is confirmed.
The navigate function is called as an unintentional side-effect directly in the body of the component. Either move the navigate call into a useEffect hook or render the Navigate component to issue the imperative navigation action.
Solution
Use an undefined initial user state and explicitly check that prior to issuing navigation action or rendering the UserContext.Provider component.
const Auth = ({ children }) => {
const [user, setUser] = useState(); // <-- initially undefined
const navigate = useNavigate();
const getUser = async () => {
try {
const res = await fetch('/api/me');
const data = await res.json();
setUser(data); // <-- ensure defined, i.e. user object or null value
} catch (error) {
// handler error, set error state, etc...
setUser(null); // <-- set to null for no user
}
};
useEffect(() => {
if (user === undefined) {
getUser();
}
}, [user]);
if (user === undefined) {
return null; // <-- or loading indicator, spinner, etc
}
// No either redirect to log user in or render context provider and app
return user
? <Navigate to="/login" replace />
: <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
useEffect runs after your JSX is rendered, so as your code is made, on a page refresh this if (!user) that calls navigate('/login') will always pass, as before the useEffect does its work, user is null, that inital value you gave to useState. Yet it's not redirecting because navigate does not work inside JSX, it should be replaced with Navigate the component.
Also, in getUser, you have this if (user) juste after setUser(data), that wouldn't work well as user won't get updated immediately, as updating a state is an asynchronous task which takes effect after a re-redner .
To fix your problems you can add a checking state, return some loader while the user is being verified. Also you can optimise a little bit your code overall, like getting ride of that gotUser state:
export const UserContext = createContext();
const Auth = ({ children }) => {
const [user, setUser] = useState(null);
const [checking, setChecking] = useState(true);
const getUser = async () => {
try {
const res = await fetch("/api/me");
const data = await res.json();
setUser(data);
} catch (error) {
setUser(null);
} finally {
setChecking(false);
}
};
useEffect(() => {
if (!user) {
getUser();
}
}, [user]);
if (checking) {
return <p>Checking...</p>;
}
if (!user) {
return <Navigate to="/login" replace />
}
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
export default Auth;

Create a new fetch request on button click - SWR / React

I am building a trivia app using the OpenTBD API.
I use SWR to make my API requests and get the questions and answers I want to display when my component renders
API call:
import useSWR from "swr";
export function useFetch(category, amount, difficulty) {
const url = `https://opentdb.com/api.php?amount=${amount}&category=${category}&difficulty=${difficulty}&type=multiple`;
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data, error } = useSWR(url, fetcher, {
revalidateOnFocus: false,
});
return {
data: data,
isLoading: !error && !data,
isError: error,
};
}
Component :
function Quiz() {
const { category, color } = useSelector((state) => state.start);
const { amount, difficulty } = useSelector((state) => state.settings);
const gameEnded = useSelector((state) => state.end.isEnded);
const dispatch = useDispatch();
const { data, isLoading, isError } = useFetch(category, amount, difficulty);
if (isLoading) {
return <FlowerSpinner color={color} />;
}
if (isError) {
return <div>An error has occured.</div>;
}
dispatch(updateQuestions(createQuestions(data.results)));
return (
<>
<Questions />
{gameEnded ? <PlayAgainBtn /> : <VerifyButton />}
</>
);
}
export default Quiz;
This works fine but now, when the game is over, I have a "Play Again" button that's displayed and I'd like to add an on click to create a new fetch in order to get a new set of questions.
I can't call the useFetch hook within a function like doing :
<button onClick={() => useFetch(newArgs)} />
so I tried to use mutate but I think because I am using the same url (the amount, category and difficulty doesn't change) when I call mutate, no new data is fetched
You could try conditional fetching which has worked for me before. I can't remember if it invalidates the cache of SWR but I recall it being the correct answer over mutate. My thought is that when you select the "Play Again" button, you could have a state such as
const [playAgain, setPlayAgain] = useState(false)
This gets passed to useFetch and you can structure useSWR like so:
const { data, error } = useSWR(playAgain ? url : null, fetcher, {
revalidateOnFocus: false,
});
You'll need to come up with a solution for the first playthrough but that very well could be another state to handle that.

React Native trigger useEffect from changes in AsyncStorage

So I'm working on a react native authentication screen. I'm storing the token in AsyncStorage (yea I know it's not the best solution).
So what's happening is when I log in, the token is stored, but the getItem on my Authentication.js screen is not being triggered, and the profile screen is not being called.
If I log in and then manually refresh the app, I am redirected to the profile screen.
Login.js
function Login({navigation}) {
const [signIn, {data}] = useMutation(USER_SIGNIN_MUTATION);
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
function handleLogIn() {
signIn({
variables: {
email: userName,
password: password,
},
});
}
useEffect(() => {
if (data != null) {
setToken();
}
});
const setToken = async () => {
try {
console.log('before');
await AsyncStorage.setItem('token', data.signIn.token);
console.log('after');
} catch (error) {
console.log(error);
}
};
return(
...
)
}
Authentication.js
function Authentication() {
const [localToken, setLocalToken] = useState(false);
useEffect(() => {
const fetchUser = async () => {
try {
console.log('before get');
const userData = await AsyncStorage.getItem('token');
if (userData !== null) {
setLocalToken(true);
}
} catch (error) {
console.log(error);
}
};
fetchUser();
}, [localToken]);
console.log(`auth screen - ${localToken}`);
return (
<NavigationContainer>
{localToken === true ? <ProfileStack /> : <AuthStack />}
</NavigationContainer>
);
}
export default Authentication;
also same happens with the logout function when fired. (the function runs, but I need to refresh the app to get back to the login screen)
Profile.js
function Profile({navigation}) {
function signOut() {
logOut();
}
const logOut = async () => {
try {
console.log('before clear');
await AsyncStorage.removeItem('token');
console.log('after clear');
} catch (error) {
console.log(error);
}
};
return (
...
)
}
I'm grateful for any insight on this.
useEffect(() => {
const fetchUser = async () => {
try {
console.log('before get');
const userData = await AsyncStorage.getItem('token');
if (userData !== null) {
setLocalToken(true);
}
} catch (error) {
console.log(error);
}
};
fetchUser();
}, [localToken]);
Here you added the localToken variable in the dependency array of the useEffect. So you are basically saying: run this effect only if the localToken variable changes. But you change that from within the effect only. So try to remove it and keep the dependency as []. This way the effect will run when the component is rendered.
About the fact that you have to refresh the page, it is important to understand why this happens.
<NavigationContainer>
{localToken === true ? <ProfileStack /> : <AuthStack />}
</NavigationContainer>
Here you are rendering ProfileStack or AuthStack based on the localToken value. When you logout, you remove the token from the AsyncStorage but this is not enough. You actually need to trigger a rerender in the Authentication component so the localToken is reevaluated. Basically, when you logout you also need to set setLocalToken(false). So you need to access setLocalToken function from the Profile component. You can pass this function as a prop or better you can use Context API

apollo's useQuery data does not update after client.resetStore()

I am having an issue with useQuery from #apollo/client
I have a Navbar component
const Navbar = () => {
const { loading, data, error } = useQuery(GET_CURRENT_USER);
console.log('navbar', data)
return <ReactStuff />
}
And I also have a UserProfile component which allows the user to logout via a button. When the user presses the button, this code is ran:
const {
getCurrentUser,
changePassword,
changePasswordData,
logoutUser,
} = useUsers();
const logout = () => {
logoutUser();
localStorage.removeItem("authToken");
props.history.push("/");
};
useUsers is a custom hook which houses the resetStore function:
const useUser = () => {
const { client, loading, error, data, refetch } = useQuery(GET_CURRENT_USER);
const logoutUser = () => {
console.log("firing logout user");
client
.resetStore()
.then((data) => {
console.log("here in reset store success");
})
.catch((err) => {
console.log("here in error");
}); // causes uncaught error when logging out.
};
return {
// other useUser functions
logoutUser
}
}
Now I can't for the life of me figure out why the Navbar component, does not get updated when logout is pressed.
I have a withAuth higher-order component which does the exact same query, and this works absolutely fine. The user is sent to the /login page, and If I was to console.log(data) it is updated as undefined - as expected.
import { GET_CURRENT_USER } from "../graphql/queries/user";
/**
* Using this HOC
* we can check to see if the user is authed
*/
const withAuth = (Component) => (props) => {
const history = useHistory();
const { loading, data, error } = useQuery(GET_CURRENT_USER);
if (error) console.warn(error);
if (loading) return null;
if (data && data.user) {
return <Component {...props} />;
}
if (!data) {
history.push("/login");
return "null";
}
return null;
};
For some reason, this useQuery inside Navbar is holding onto this stale data for some reason and I can't figure out how to make it work correctly.
Update 1:
I've changed the logoutUser function to use clearStore() and the same thing happens, the Navbar is not updated, but I am redirected to /login and withAuth is working as intended.
const logoutUser = () => {
client
.clearStore()
.then((data) => console.log(data)) // logs []
.catch((err) => console.log(err)); // no error because no refetch
};
You're not waiting for the store to reset, probably the redirection and storage clean up happen before the reset completes, try doing that once it has finished
const logout = () => {
logoutUser(() => {
localStorage.removeItem('authToken');
props.history.push('/');
});
};
const logoutUser = onLogout => {
console.log('firing logout user');
client
.resetStore()
.then(data => {
onLogout();
})
.catch(err => {
console.log('here in error');
}); // causes uncaught error when logging out.
};
Check: do you have only ONE ApolloClient instance?
In some cases, if you configuring ApolloClient in the custom class or file you can implicitly create multiple apolloClient instances, for example by 'import' statement. In that case you clearing only one of the caches you made.

How to reload current page in ReactJS?

How to reload current page in ReactJS? in case of javascript we can write window.location.reload();
How to do the same in Reactjs? I'm able to add new data by UI. But without refreshing, I'm not able to see the list. I want it so that whenever I'm adding some data, it refreshes by itself.
onAddBucket() {
let self = this;
let getToken = localStorage.getItem('myToken');
var apiBaseUrl = "...";
let input = {
"name" : this.state.fields["bucket_name"]
}
axios.defaults.headers.common['Authorization'] = getToken;
axios.post(apiBaseUrl+'...',input)
.then(function (response) {
if(response.data.status == 200){
let result = self.state.buckets.concat(response.data.buckets)
}else{
alert(response.data.message);
}
})
.catch(function (error) {
console.log(error);
});
}
use this might help
window.location.reload();
You can use window.location.reload(); in your componentDidMount() lifecycle method. If you are using react-router, it has a refresh method to do that.
Edit: If you want to do that after a data update, you might be looking to a re-render not a reload and you can do that by using this.setState(). Here is a basic example of it to fire a re-render after data is fetched.
import React from 'react'
const ROOT_URL = 'https://jsonplaceholder.typicode.com';
const url = `${ROOT_URL}/users`;
class MyComponent extends React.Component {
state = {
users: null
}
componentDidMount() {
fetch(url)
.then(response => response.json())
.then(users => this.setState({users: users}));
}
render() {
const {users} = this.state;
if (users) {
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
)
} else {
return (<h1>Loading ...</h1>)
}
}
}
export default MyComponent;
Since React eventually boils down to plain old JavaScript, you can really place it anywhere! For instance, you could place it in a `componentDidMount()' function in a React class.
For your edit, you may want to try something like this:
class Component extends React.Component {
constructor(props) {
super(props);
this.onAddBucket = this.onAddBucket.bind(this);
}
componentWillMount() {
this.setState({
buckets: {},
})
}
componentDidMount() {
this.onAddBucket();
}
onAddBucket() {
let self = this;
let getToken = localStorage.getItem('myToken');
var apiBaseUrl = "...";
let input = {
"name" : this.state.fields["bucket_name"]
}
axios.defaults.headers.common['Authorization'] = getToken;
axios.post(apiBaseUrl+'...',input)
.then(function (response) {
if (response.data.status == 200) {
this.setState({
buckets: this.state.buckets.concat(response.data.buckets),
});
} else {
alert(response.data.message);
}
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
{this.state.bucket}
);
}
}
You can use useNavigate and navigate to the same url you are on. For example, instead of window.location.reload(), you can say navigate("/...your current url....")
window.location.reload() is not the best option everytime. It works on localhost, but for example on when you deploy it to the internet by using services such as "Netlify", it can can cause "not found url" error
Creating some extra state and tracking them for re-rendering your page might unnecessarily complicate your code.
And using useEffect() to re-render your page, again, will unnecesarily complicate your code.
This is my code .This works for me
componentDidMount(){
axios.get('http://localhost:5000/supplier').then(
response => {
console.log(response)
this.setState({suppliers:response.data.data})
}
)
.catch(error => {
console.log(error)
})
}
componentDidUpdate(){
this.componentDidMount();
}
window.location.reload(); I think this thing is not good for react js
use useHistory method in react
import {useHistory} from 'react-router-dom'
const history = useHistory()
history.go(0) // it will refresh particullar page
or use useEffect method
const [data, setData] = useState([])
useEffect(()=>{
setData(reponseApidata)},[data])
//in useEffect dependcy you mention particullar state for you store reposnse data

Categories

Resources