Component won't change with React router dom - reactjs

I have an "Header" component and two more components that I want to show on it each at a different condition.
renderUserHeader = () =>{
if(sessionStorage.getItem('user-auth')){
var tokenToSend = {token: sessionStorage.getItem('user-auth')}
var regJSONED = JSON.stringify(tokenToSend)
fetch('http://localhost:4000/users/token', {
method: 'POST',
headers:{
"Content-Type": "application/json"
},
body: regJSONED,
}).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.text();
})
.then(data => {
let JsonedUserName = JSON.parse(data)
this.setStateUserName(JsonedUserName.name)
})
return <UserHeader name ={this.state.userName}/>
} else if(!sessionStorage.getItem('user-auth')){
return <Link to="/login"> <LoginLink /> </Link>
}
}
As you can see component "UserHeader" will show only if 'user-auth' is exist in session storage, and component "LoginLink" will be showed if 'user-auth' doesn't exist.
Every time I delete 'user-auth' from the session-storage "LoginLink" is immediately being showed instead of "UserHeader", but every time "user-auth" is being created at the storage-session I must refresh the page so "UserHeader" can be showed up instead of "LoginLink".
How do I make "UserHeader" being shown immediately when "user-auth" is created?
Did I do something wrong in my code?

You can do setInterval to check if session storage data changed or not, better was doing this is using useEffect from React Hooks https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Related

React fetch data and set state accordingly doesn't work

In the code below the settings variable is not being set. I did check with console.log after the
setSettings and also I verified the component using the variable is receiving the default {} value. The fetch per se is right, I tried in the browser's console and I see the json in the Network tab.
I need the fetch be done once, at the first rendering.
Could you please take a look? I've spent lots of time on this and I'd like to know what I'm doing wrong. Thanks!
const [settings, setSettings] = React.useState({});
function fetchSettings() {
fetch("MYAPIENDPOINT/settings", {
method: "GET",
credentials: 'include',
accept: 'application/json',
})
.then(response => {
if (response.ok) {
return response.json();
}
throw response;
})
.then((data) => {
setSettings(data);
})
.catch(error => {console.log(error);});
}
React.useEffect(
fetchSettings(),
[settings]
);
EDIT to show the component using the settings variable
...
<div id="Grid" style={{ height: "700px" }}>
{settings && <GenericGrid settings={settings} />}
</div>
useEffect accepts 2 arguments: callback and dependency array.
The right syntax is:
useEffect(() => {
function fetchSettings(){
...
}
fetchSettings();
}, [])
If you want the callback to trigger only once, leave the dependency array empty.
Also better define the fetchSettings function inside the callback, so it does not recreate on each render.

After submitting my React form, is there an easier way of passing an object to the page I want to redirect to?

I'm using React 16.13.0. I have the following handler for submitting my React form to its endpoint ...
const handleFormSubmit = (e) => {
...
fetch(REACT_APP_PROXY + "/coops/", {
method: "POST",
body: JSON.stringify(NC),
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw response;
}
})
.then((data) => {
const result = data;
window.location.href = "/" + result.id + "/people";
}).catch(err => {
err.text().then( errorMessage => {
setErrors({ errors: errorMessage });
});
});
};
I was wondering if there is a more "React" way of redirecting to the next page. Right now, I'm doing
window.location.href = "/" + result.id + "/people";
which seems kind of hacky. Also, I'm not able to pass an object to the next page, which I have on the page executing the submit (The "data" object is what ideally I'd like to pass to the next page). Thus, when the redirect page loads, I'm forced to execute another fetch call to retrieve that object again.
React, Passing objects between page loads / location changes:
you can use the localStorage/sessionStorage as described in the accepted answer here.
or you use React Context or Redux to store the object in global state that is shared across the application
React, Routing & Passing objects:
First of all, nothing is wrong with updating window.location. It is a valid way to update the current page. Leaving it as is and using sessionStorage to pass your object will most likely be the fastest way to solve your problem. That being said, there are me more optimized solutions out there that feel more like react: Try React Router for instance. It also lets you pass objects between routes by passing them down as props to the other pages.
react-router-dom hooks example:
import { useHistory } from "react-router-dom";
const myComponent = () => {
const history = useHistory();
...
.then((data) => {
const result = data;
// access state via this.props.location.state.result
history.push({
pathname: "/" + result.id + "/people",
state: {result}
});
}).catch(err => {
err.text().then( errorMessage => {
setErrors({ errors: errorMessage });
});
});
}
Find more information about react-router-dom here.

React redirect after login problem with using hooks incorrectly

I am working on a login page in react and after receiving the cookie I am using history.push('/dashboard'); to redirect to bring the user to the dashboard page but I keep getting the following error:
I don't have more than one react copy, I think I am breaking the rules of hooks but I don't quite understand what those are.
Here is my full onSubmit method:
onSubmit = (event) => {
event.preventDefault();
fetch('/api/authenticate', {
method: 'POST',
body: JSON.stringify(this.state),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => {
if (res.status === 200) {
const history = useHistory();
history.push('/dashboard');
} else {
const error = new Error(res.error);
throw error;
}
})
.catch(err => {
console.error(err);
alert('Error logging in please try again');
});
}
According to the official documentation - 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. By following this rule, you ensure that Hooks are called in the same order each time a component renders

How to handle the case where the axios response comes after the component is rendered?

I am creating a blog application in rest framework and reactjs. On the home page, under componentDidMount, I send an API call using axios to get all the articles and setState of articles to the return. As I have studied, axios works on the idea of promise such that the code doesnt proceed, if the API is not fetched for a particular component. Please tell me, if I am wrong.
Then, I send a GET call to get the writer's name, who wrote the article by the id. Though, I assumed that the axios works as a promise. But, it doesnt work that way. Now, I am not sure how to move ahead.
Here is a snippet. So, in mainBody.js, I make the api call as:
class MainBody extends Component {
state = {};
componentDidMount () {
this.get_all_articles();
};
get_writer_name (id) {
let authstr = 'Bearer ' + window.localStorage.token;
let writer_url = "http://localhost:8000/api/writer/" + id.toString() + "/";
axios.get(writer_url, { headers: { Authorization: authstr }})
.then(response => {
console.log(response.data['name'])
return response.data['name'];
})
.catch(err => {
console.log("Got error")
})
};
get_all_articles () {
let authstr = 'Bearer ' + window.localStorage.token;
axios.get("http://localhost:8000/api/articles/", { headers: { Authorization: authstr }})
.then(response => {
this.setState({articles: response.data});
})
.catch(err => {
console.log("Got error")
})
}
render () {
return (
{this.state.articles.map((article, key) =>
<ArticleView key={article.id} article={article} writer_name={this.get_writer_name(article.created_by)} />
)}
)
}
}
In articleview2, I print all the data that is present in each of the articles along with the writer's name.
My articleview class is:
class ArticleView extends Component {
state = {article: this.props.article};
componentDidMount() {
console.log(this.props.writer_name;
}
render () {
return (
<React.Fragment>
<h2>{article.title}</h2>
<p>{article.body}</p>
<span>{this.props.writer_name}</span>
</React.Fragment>
)
}
}
If you see closely, I wrote two console.log statements to get the writer names. Based on the order, first the console log present in articleview class runs, which is undefined, and thenafter the data is fetched from the API call and the console log runs which returns the correct writer name.
I wanted to know, where is the error? Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
I want to know where is the error.
When you are writing this.state.articles.map(), means you're using property map of the Array articles which may be undefined before the data is fetched that will cause you the error Cannot read property map of undefined.
Solution
Now, as the API request is asynchronous, means render method will not wait for the data to come. So what you can do is use a loader variable in the state, and set it to true as long as the request is being made, and when the response has come, make it false, and show the loader in render when this.state.loader is true, and show articles when it is false.
Or you can initialize this.state.articles with an empty array that won't cause you the error.
Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
It is extremely bad practice to make an API request in the loop. Even myself has been scolded on it once I did it in my company.
Solution
You have tell your backend engineer to provide you filter for including the writer's name in each object of the article. We use Loopback on our backend, which provides a filter for including the related model in each object internally.
Since your API calls have a lot of things in common, you should first set up an axios instance that re-uses those common features:
const api = axios.create({
baseURL: 'http://localhost:8000/api/',
headers: { Authorization: `Bearer ${localStorage.token}` }
});
Now since your MainBody needs to fetch the resources from the API asynchronously, there will be a short period where the data is not yet available. There are two ways you can handle this. Either the MainBody can be responsible for making all the calls, or it can be responsible for just making the call to get all the articles, then each of the ArticleView components can be responsible for getting the writer's name. I'll demonstrate the first approach below:
class MainBody extends Component {
state = { articles: null, error: null, isLoading: true };
async componentDidMount () {
try {
const response = await api.get('articles/');
const articles = await Promise.all(
response.data.map(async article => {
const response = await api.get(`writer/${article.created_by}/`);
return { ...article, writer_name: response.data.name };
})
);
this.setState({ articles, isLoading: false });
} catch (error) {
this.setState({ error, isLoading: false });
}
}
render () {
const { articles, error, isLoading } = this.state;
return isLoading ? 'Loading...' : error
? `Error ${error.message}`
: articles.map(article => (
<ArticleView
key={article.id}
article={article}
writer_name={article.writer_name}
/>
)
);
}
}

React Relay Modern redirecting to another page when receiving 401 error on network environment

I´m using JWT authentication inside my ReactJS RelayJS network environment. All the token retrieval and processing in server and client are fine. I´m using react router v4 for routing.
My problem is when I receive a Unauthorized message from server (status code 401). This happens if the user points to an application page after the token has expired, ie. What I need to do is to redirect to login page. This is the code I wish I could have:
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
const SERVER = 'http://localhost:3000/graphql';
const source = new RecordSource();
const store = new Store(source);
function fetchQuery(operation, variables, cacheConfig, uploadables) {
const token = localStorage.getItem('jwtToken');
return fetch(SERVER, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
Accept: 'application/json',
'Content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables
})
})
.then(response => {
// If not authorized, then move to default route
if (response.status === 401)
this.props.history.push('/login') <<=== THIS IS NOT POSSIBLE AS THERE IS NO this.history.push CONTEXT AT THIS POINT
else return response.json();
})
.catch(error => {
throw new Error(
'(environment): Error while fetching server data. Error: ' + error
);
});
}
const network = Network.create(fetchQuery);
const handlerProvider = null;
const environment = new Environment({
handlerProvider, // Can omit.
network,
store
});
export default environment;
Naturally calling this.props.history.push is not possible as the network environment is not a ReactJS component and therefore has no properties associated.
I´ve tried to throw an error at this point, like:
if (response.status === 401)
throw new Error('Unauthorized');
but I saw the error on the browser console, and this cannot be treated properly in the code.
All I wanna do is to redirect to login page in case of 401 error received, but I can´t find a proper way of doing it.
I am not using relay but a render prop. I experienced kind of the same issue. I was able to solve it using the window object.
if (response.statusText === "Unauthorized") {
window.location = `${window.location.protocol}//${window.location.host}/login`;
} else {
return response.json();
}
You can go with useEnvironment custom hook.
export const useEnvironment = () => {
const history = useHistory(); // <--- Any hook using context works here
const fetchQuery = (operation, variables) => {
return fetch(".../graphql", {...})
.then(response => {
//...
// history.push('/login');
//...
})
.catch(...);
};
return new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource())
});
};
// ... later in the code
const environment = useEnvironment();
Or you can create HOC or render-prop component if you are using class-components.
btw: this way you can also avoid usage of the localStorage which is slowing down performance.

Resources