I've found myself at a bit of dead end here. I'll try to explain it as best as I can.
I'm using base username routing on my app, meaning that website.com/username will route to username's profile page. Everything works fine apart from one issue that I'll get to below.
This is how I'm doing it (very short version) :
This route looks for a potential username and renders the Distribution component.
<Route exact path="/([0-9a-z_]+)" component={Distribution} />
Distribution component then extracts the potential username from the pathname...
username = {
username: this.props.location.pathname.substring(1)
};
...And then fires that off to my API which checks to see if that username actually exists and belongs to a valid user. If it does it returns a user object, if not it returns an error.
if (username) {
axios
.post(API_URI + '/users/get/profile', JSON.stringify(username))
.then(res => {
this.setState({
ready: true,
data: res.data,
error: ''
});
})
.catch(err => {
this.setState({
ready: true,
data: '',
error: err.response
});
});
}
All of the above is happening inside componentWillMount.
I then pass the relevant state info as props to the relevant child component in the render :
render() {
if (this.state.ready) {
if (this.state.data) {
return <UserProfile profile={this.state.data} />;
}
if (this.state.error) {
return <Redirect to="notfound" />;
}
}
return null;
}
As I mentioned, this all works perfectly when moving between all of the other routes / components, but it fails when the Distribution component is called while already IN the Distribution component. For example, if you are already looking at a valid profile (which is at Distribution > UserProfile), and then try to view another profile, (or any other malformed username route that would throw an error), the API call isn't getting fired again so the state isn't being updated in the Distribution component.
I originally had it all set up with a Redux store but had the exact same problem. I wrongly thought that componentDidMount would be fired every single time the component is called for the first time, and I assumed that throwing a new url at it would cause that but it doesn't.
I've tried a bunch of different ways to make this work (componentWillReceiveProps etc) but I just can't figure it out. Everything I try throws depth errors.
Am I missing a magical piece of the puzzle here or just not seeing something really obvious?
Am I going about this entirely the wrong way?
You on the right path when you tried to use componentWillReceiveProps. I would do something like the following:
componentDidMount() {
this.refresh()
}
componentWillReceiveProps(prevProps) {
if(prevProps.location.pathname !== this.props.location.pathname) {
this.refresh(prevProps)
}
}
refresh = (props) => {
props = props || this.props
// get username from props
// ...
if (username) {
// fetch result from remote
}
}
Related
I'm trying to implement protected routes. The issue is, the navigate happens before the setSession has updated meaning the authContext is still false and the protected route component sends the user back to /sign-in
This is the handleSubmit function on my sign in form
const handleSubmit =
async (e) => {
e.preventDefault();
let { data, error } =
await auth.signIn({
email,
password
})
if (error)
setAuthError(error.message);
if (data)
navigate('/dashboard');
}
This is the signIn function on my context, called by the function above
async ({ email, password }) => {
let { data, error } =
await client.auth.signInWithPassword({
email,
password
})
if (data)
setSession(data.user)
return { data, error }
},
...and of course the protected route component is essentially
let { isSignedIn } = useContext(AuthContext);
return (
isSignedIn
? children
: <Navigate to="/sign-in" replace />
)
From looking around this seems to be the basic structure that protected route tutorials have: use a handler function to call the sign-in function on a context; context sets some state and returns; the handler function then navigates.
I'm using React-Router 6.8.0. Funnily enough, the sign-in/out button in the nav (which is not under the react router <Outlet/> seems to work)
I think you need to have a separate state for managing when the checking of the auth status is happening. At the the minute your logic doesn't cover that scenario, for example, something like -
let { isSignedIn, isAuthLoading } = useContext(AuthContext);
if(isAuthLoading) return <Loading />
return (
isSignedIn
? children
: <Navigate to="/sign-in" replace />
)
or whatever way you feel best suits your app structure, but essentially, you need to have some way of handling the states that represent when you're either checking if the user is authenticated, or during authentication, then you can use your existing private route logic when you know if the user is logged in or not.
From what I can tell this is due to "batching" - https://github.com/reactwg/react-18/discussions/21, the fix was to use flushSync around the setSession state call.
React applies the setSession call asynchronously, and in this case the state still hasn't been updated by the time the <ProtectedRoute/> component is hit (after we've navigated with navigate('/dashboard')), so isSignedIn is still false so we get sent back to /sign-in.
I'm guessing the majority of those "protected routes" tutorials are using React < 18, where there was no batching of setState calls outside of event handlers i.e inside promises which means they updated synchronously and thus the above pattern worked without the use of flushSync.
so I might have difficulty explaining this issue I am having, which I am not able to reproduce consistently. I have a React app on which I am using react-redux-firebase and that I thought I was successfully implementing to keep track of the user session.
My App.js file has the following bit or routing code as a sample (using react-router-dom):
<Route
path="/signin"
render={() => {
if (!isLoaded(this.props.auth)) {
return null;
} else if (!isEmpty(this.props.auth)) {
return <Redirect to="/posts" />;
}
return <Signin />;
}}
/>
This works correctly. I go to Signin component when user is not logged in or Posts when user is logged in. In Signin component I have this bit of logic that happens:
// sign the user in
this.props.firebase.login({
email: user.email,
password: user.password
}).then(response => {
// detect the user's geolocation on login and save
if (isLoaded(this.props.auth)) {
const navigator = new Navigator();
navigator.setGeoLocation(this.props.firebase);
}
// redirect user to home or somewhere
this.props.history.push('posts');
})
I am importing isLoaded like so:
import { firebaseConnect, isLoaded } from 'react-redux-firebase';
the condtional works fine, the user is logged in and then the isLoaded conditional happens - this is where the problem arises. With isLoaded true I would assume that the user and all the redux-firestore user properties are ready for use....but that is sometimes not the case. In navigator.setGeoLocation call I have this:
setGeoLocation(propsFirebase, cb) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
propsFirebase.auth().currentUser
.getIdToken(/* forceRefresh */ true)
.then((idToken) => {
...
});
}
)
}
}
At this point, propsFirebase.auth().currentUser is sometimes null (this originates in the parameter passed in navigator.setGeoLocation(this.props.firebase);). If I try the signin in all over again, then it works. I am not able to find any consistent way of reproducing.
I have noticed this in other components too. I am not sure if this is an issue that happens when my computer goes to sleep and I should restart the whole React process or what? has anyone seen similar issues? If so, what could I possibly be missing during checking user state in the routing?
If more code is necessary, let me know...
currentUser will be null with the user is not signed in. It will also be null when a page first loads, before the user's token has been loaded and a User object is available. You should use an auth state observer to get the User object if you want to act immediately after it is loaded asynchronously after page load.
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
You might also want to read for more detail: Why is my currentUser == null in Firebase Auth?
I am trying to print data from fetched JSON but somehow i am unable to do it.
interface IFetched {
fetchedData: any;
error: any;
}
export default class Testing100 extends React.Component<
ITesting100Props,
IFetched,
{}
> {
constructor(props: ITesting100Props) {
super(props);
this.state = {
error: null,
fetchedData: [],
};
}
public componentDidMount() {
fetch("https://api.randomuser.me/")
.then((res) => res.json())
.then(
(result) => {
this.setState({
fetchedData: result,
});
},
(error) => {
this.setState({
error,
});
}
);
}
public render(): React.ReactElement<ITesting100Props> {
console.log(this.state.fetchedData.results);
return (
<div>
<a>
{this.state.fetchedData.map(function (fetchedDataX) {
return fetchedDataX.results[0].name.first;
})}
</a>
</div>
);
}
}
With console log i am able to print data. But when i change console log from console.log(this.state.fetchedData.results); to console.log(this.state.fetchedData.results[0]); i get nothing. And even that console log is called twice as you can see in console output i dont know why.
But my goal is to print the first name of person into <a> element and I just don't know how. Hope somebody can help me with this. Thanks for your time.
Think about the state of the app before the fetch occurs - the fetchedData array is empty. Then when you fetch, you are converting it into an object, with a results field that is an array.
Your code needs to be able to handle both of these states. Don't try to use or log a field of something that you haven't first verified actually exists, or it will crash.
First step is to clean it up so you directly just update the array in the state -
Your map is not working because fetchedData has an inner results field - try this.setState({fetchedData: result.results});, and then console.log(this.state.fetchedData).
Also you might want to add some guards to the top of your render so that things don't crash when the fetchedData is empty or errored:
if (this.state.fetchedData === []) return <p>"Nothing Loaded"</p>;
if (this.state.error !== null) return <p>{this.state.error.message}</p>;
As for the double output to the console, that is because the render method get run first when the component is mounted, and you see the output where the fetchedData is empty, and then componentDidMount runs (which fetches the data and updates the state) and then React re-renders with the new state, causing the second console log.
Your console log that tries to access the .result[0] fails because it doesn't exist for this first pre-fetch state. Check (with an if) it is there before logging, or log the whole state obj.
i am looking to pass the json data that i received using the fetch API and use in the Useraccount component.
i have looked around and i can find a lot of material related to passing from child to parent and very few that mention from parent to child.
I have tried using this userinfo={credentailverify} and clearly it is not working for me, any suggestions please
Update3:
i have upload the small clip for the issue that i am facing for better understanding. i have tried to make the code very simple but still cant understand the reason why loginscreen is showing before showing the user account information.
youtube link showing issue
import Useraccount from "./Useraccount";
function Signin({ userinfo1, userinfo2 }) {
//userinfo1 is having customer account information
//userinfo2 is Boolean and showing if user is looged in or not if not then go to login page
return (
<div>
{userinfo2 ? (
<Useraccount userinfo={userinfo1} />
) : (
<SigninOptions />
)}
</div>
);
}
export default Signin;
Update2: i am also experience one strange thing as when i set setUserinfo and pass the new state into the child it does show the new state in child component there but here in main code if i try to console the userinfonew after its set it is showing me the initial state as empty array, is it some thing i am missing here!!
.then((data) => {
setUserinfo(data.data)
console.log(userinfonew)
}
-Note i can see if i run console.log(userinfonew) outside the Async function then it does show the updated status but not inside the async function, although i am updating the status inside the Async function. cant understand the reason behind it
Update1: initial problem is solved thanks and i have updated the code, now the only issue i am facing is the condition that i am using in the return statement is both getting executed i.e first for few seconds < SigninOptions /> component and then the correct one as per the logic < Useraccount userinfo={userinfonew} /> component not sure if there is a delay somewhere or code is runnig twice
function Signin() {
const [siginalready, setifsignedin] = useState(false);
const [userinfonew, setUserinfo] = useState([]);
useEffect(() => {
credentailverify();
}, []);
let url = "http://localhost:5000/api/verifyifloginalready";
let options = {
credentials: "include",
method: "POST",
};
let verifyifloginalready = new Request(url, options);
let credentailverify = async () => {
const x1 = await fetch(verifyifloginalready)
.then((res) => {
if (res.status == 400 || res.status == 401) {
return setifsignedin(false);
} else {
setifsignedin(true);
return res.json();
}
}).then((data)=>
{
// here the console is shoewing empty array
setUserinfo(data.data)
console.log(userinfonew)
})
.catch((err) => console.log("err"));
return x1;
};
return (
<div>
// here first <SigninOptions /> renders for a SECOND and then <Useraccount userinfo={userinfonew} />
{siginalready ? (
<Useraccount userinfo={userinfonew} />
) : (
<SigninOptions />
)}
</div>
);
}
export default Signin;
the below is the code at the user account,
import React, { useState, useEffect } from "react";
import "../App.css";
function Useraccount({ userinfo }) {
return <div>{ `The user email address is ${userinfo}`}</div>;
}
export default Useraccount;
and after the data is passed to the child component how can i use it, i have seen one place mentioned to use as this.props.userinfo but i am using React Hook so cant use this method to access.
Thanks in advance.
You need to access the props passed to child:
function Useraccount({ userinfo }) {
if (!userInfo) return <div />
return <div>{`The user email address is ${userinfo}`}</div>;
}
Also use a template string like I did above
My recommendation is to track the response of your response in the state, then pass that state value into the child component.
It can be helpful to think of an effect as happening in a different execution than your main code. Any data inside of there can only be communicated to your component through the functions that you pass in as the effect dependencies.
I have the following problem: I've written a 'detailview' component, that takes a PK from a list and uses it to fetch the right object from the API. As this takes some time it will display 'Loading' by default.
My render looks like this:
render() {
if (!this.store_item) {
return <h2>loading</h2>
}
return(
<div>
<h2>{this.state.store_item.results.title}</h2>
<p>{this.state.store_item.results.description}</p>
</div>
)
}
The 'store_item' is loaded when the component mounts:
componentDidMount() {
this.LoadApi();
}
The 'LoadApi' method has the following code:
LoadApi() {
const new_url = encodeURI(this.state.store_item_api + this.props.detail_pk);
fetch(new_url, {
credentials: 'include',
})
.then(res => res.json())
.then(result =>
this.setState({
store_item: result,
}));
}
However, when I run this code in my browser all I see is 'Loading'. Yet when I check the state in the chromium react extension I can see that 'store_item' has been set (I also verified that the api call went as planned in the network tab). As the state has been set (and is being set in 'LoadApi') my understanding was that this should trigger an re-render.
Does anyone know why, despite all this, the component keeps returning 'loading'?
You have typo, missed state keyword
if (!this.store_item) { ... }
^^^^^^^^^^^^^^^^^^
Should be
if (!this.state.store_item) { ... }
Because it's defined as component state instead of component static property.