React Router on refresh with API call - javascript

I am currently working on a weather app constructed with react-create-app that works on React Router to show data for 5 different days. Each day (presented in a box) is a Link that redirects the second component to it specifically so that it can show more detailed data for the specific day.
I have a function that generates the data for each tile:
generateTileData() {
const weatherData = this.state.data;
if (!weatherData) return null;
let days = [];
const newData = [...weatherData].filter(day => {
let dateFromAPI = moment.unix(day.dt).date();
if (days.indexOf(dateFromAPI) > -1) {
return false;
} else {
days.push(dateFromAPI);
return true;
}
});
// console.log(days)
return newData.map((day, item) => {
const dateId = day.dt;
return (
<Link to={`/w/${dateId}`}>
<WeatherTile key={day.dt} index={item} {...day} date={day.dt_txt} />
</Link>
);
});
}
That then is being rendered:
<HashRouter>
<React.Fragment>
<div className="columns is-gapless tiles">
{this.generateTileData()}
</div>
{weatherData && <Route exact path="/w/:dateId" render={({ match }) => <Weather data={weatherData} day={[...weatherData].find(day => day.dt == match.params.dateId)} />} />}
</React.Fragment>
</HashRouter>
The problem is that as I am fetching the data (through a form) only after the user inputs it, when I accidentally refresh the page, the application does not know where the data comes from and thus gives me a nice error that it cannot read the property main of undefined (as in my Weather component:
const Weather = ({ day }) => {
console.log(day);
const data = day;
return (
<div className="info">
<p>{data.main.temp}</p>
<p>{data.main.humidity}</p>
</div>
)
}
Is there any way that I can either prevent the user from refreshing or by default redirecting the user on refresh to the main page (that would be path="/")?

There are a few ways I think you could go about getting around this issue. One way would be how #Pat Mellon described and to add a refresh state and redirect should it be true or false (however you want to build the logic).
You could also add in some default logic, which is more of a 'React' way of doing things. For instance, if there is no user info being seen by the app, simply output some example weather.
Up next would to be adding in url logic and sticking your user data into the url itself. Kind of like an API call, the app would look at the url and not the state to pull information to display. (Look at URI.js for a nice way of doing this)
Another way that might be a little more UX friendly is to persist the data from the form into the user's local storage. Using this method would allow you to maintain the data and use it even if the page is refreshed, closed, computer is restarted, etc. The only caveat is remembering to clear the user's local storage (and more importantly, the information that you saved and not that of other websites) if the user is to ever re-enter information into your form.
Lot's of fun ways to untangle this issue, and I'm sure plenty more than the four I've listed.

Related

How to render user list in React-Firebase

I'm still learning React and I'm trying to make a "design review app" where users signup as customers or designers and interact with each other.
I made the auth system and made sure that while signing up every user would get also some attributes in the firebase database.
Therefore, in my DB, I have a 'users/' path where every user is saved by uid.
Now I'm able to render a different dashboard if you're a customer or a designer.
In my customer dashboard, I just want to render a list of designers (and clicking on them go to their projects).
However, I'm having so many problems trying to get this stuff to work!
In the following code, I'm trying to fetch the users from the db and add their uid to an array.
Later I want to use this array and render the users with those uids.
import firebase from "firebase/app";
import "firebase/database";
export default function CustomerContent() {
const[designers, setDesigners] = useState([]);
function printUsers (){
var users = firebase.database().ref('/users/');
users.on('value', (snapshot)=>{
snapshot.forEach((user)=>{
console.log(user.key)
firebase.database().ref('/users/'+user.key).on('value', (snapshot)=>{
var role = snapshot.val().role
console.log(role)
if(role === 'designer'){
const newDesigners = [...designers, user.key];
setDesigners(newDesigners);
}
})
})
})
}
useEffect(() => {
printUsers();
console.log(designers);
}, [])
return (
<div>
designer list
</div>
)
}
Now the problem with this code is that:
it looks like it runs the printUsers functions two times when loading the page
the array is empty, however, if I link the function to a button(just to try it), it seems to add only 1 uid to the array, and always the same (I have no idea what's going on).
ps. the console.log(user.key) and the console.log(role) print the right user-role combination
It's not a stupid question. Here's what I'd change it to (of course you'd remove the console.logs later though). It's hard to know if this will work perfectly without having access to your database to run it, but based on my last react/firebase project, I believe it'll work.
The first thing was that you reference /users/, when you only need /users. I'm not sure if it makes a difference, but I did it the latter way and it worked for me.
Secondly, you're calling firebase more than you need to. You already have the information you need from the first time.
Third, and this is small, but I wouldn't call your function printUsers. You're doing more than just printing them- you're making a call to firebase (async) and you're setting the state, which are much larger things than just print some data to the console.
Lastly, I would store the entire object in your designers piece of state. Who knows what you'll want to display? Probably at least their name, then possibly their location, background, an icon, etc. You'll want all of that to be available in that array, and possibly you'll want to move that array into redux later if you're app is big enough.
I also added some JSX to the bottom that gives a simple output of what you could do with the designers array for the visual aspect of your app.
import firebase from 'firebase/app';
import 'firebase/database';
export default function CustomerContent() {
const [designers, setDesigners] = useState([]);
function printUsers() {
var users = firebase.database().ref('/users');
users.on('value', (snapshot) => {
snapshot.forEach((snap) => {
const userObject = snap.val();
console.log(userObject);
const role = userObject['role'];
console.log(role);
if (role === 'designer') {
const newDesigners = [...designers, userObject];
setDesigners(newDesigners);
}
});
});
}
useEffect(() => {
printUsers();
console.log(designers);
}, []);
return (
<div>
<h2>The designer are...</h2>
<ul>
{designers.map((designerObject) => {
return <li>{designerObject.name}</li>;
})}
</ul>
</div>
);
}

Is it possible to check if onSnapshot() has found changes before actually fetching?

When I am navigating to a page, I am passing some parameters such as "Discounts" and I am displaying that number in the render() but I am calling onSnapshot() right in the begin of navigating to that class. Now, I would like to display in the begin ONLY the value of the parameter and in case there are changes fetch it.
P.S. the reason behind this is that I am trying to reduce the number of fetches as much as possible.
check_amount_left() {
const getSelected = this.props.navigation.state.params;
var ref = db().collection('discounts').where("rest_id", "==", getSelected.rest_id)
ref.onSnapshot((querySnapshot => {
var amount = querySnapshot.docs.map(doc => doc.data().amount);
this.setState({
check_for_amount: amount.toString()
});
}));
}
Render method:
render(){
const getSelected = this.props.navigation.state.params;
return(
<View>
{getSelected.amount}
</View>
)
}
To know that something changed about data on the server, the server will need to tell the client about that change. In the case of Firestore the server always tells the client the entire new state of the document.
So the only way to reduce the amount of data that the server sends to the client, is to reduce the size of the document you're listening to.
But event then, it would not reduce the number of fetches, as the server will need to read the document to know that it has changed.

Can't render out single page data from list in a react application using router and WordPress API

I’m pretty new to this so apologies in advance if I'm being dumb. I’m building a react application on top of the WordPress rest API. I’m trying to do something pretty basic which is to create a component showing a list of pages, each with a link which takes the user to a new view showing the individual ‘page’ with all the data for that page.
I’m almost there but am having problems outputting the correct data on the individual pages.
The approach I’ve taken is to take the id from match.params and then match it up with the page id passed down through props using javascript ‘find’.
This kind of works. I can console log the data for the individual page out from inside the ‘getPage’ method in the PageSingle component if I call it in the render method but the moment I try to access any individual values such as the id I get the old Cannot read property 'id' of undefined.
Probably not very clearly explained so please see code below:
PageList.js
import React from 'react';
import { Link } from 'react-router-dom';
const PageList = (props) => {
const pages = props.pages.map( (page) => {
return (
<div key={page.id}>
<Link to={`/pagelist/${page.id}`}>{page.title.rendered}</Link>
</div>
);
});
return <div className="page-list">{pages}</div>
};
export default PageList;
PageSingle.js
import React from 'react';
class PageSingle extends React.Component {
getPage = (props) => {
let thePage = props.pages.find(page => page.id === Number(props.match.params.pageId) );
**console.log(thePage); // 1. this works
console.log(thePage.id); // 2. this leads to error**
return thePage;
};
render() {
this.getPage(this.props);
return (
<h4>PageSingle</h4>
)
}
};
export default PageSingle;
JSON shown in console when it works – I’ve removed some so as not to take up too much space but you get the idea
{
content: {rendered: "Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. ", protected: false}
date: "2019-09-30T13:38:47"
excerpt: {rendered: "Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text. Test Page 2 Text.", protected: false}
id: 14
link: "http://localhost/all_projects/wordpress/sites_main/my_projects/portfolio/wordpress/test-page-2/"
slug: "test-page-2"
status: "publish"
title: {rendered: "Test Page 2"}
type: "page"
__proto__: Object
}
The props are sent to page single using Browser Router. The routes themselves are defined in the App.js component and look like this. Not sure if this is relevant or not, probably not.:
Routes
<Route
path="/pagelist" exact
render={ (props) => <PageList {...props} pages={ this.state.pages } /> }
/>
<Route exact
path="/pagelist/:pageId"
render={(props) => <PageSingle {...props} pages={ this.state.pages } /> }
/>
Obviously, the end goal is to eventually display the relevant data via the render method but I actually need to access that data before I can do that.
It’s probably something really basic that I’m just not understanding.
Any pointers in the right direction would be much appreciated.
Thanks.
If you have the correct data in your console.log then I think this is because console.log executes after a small delay,
Try doing this and see if you get the property
setTimeout(function()
{
console.log(thePage.id)
}, 100);
usually keys are added after the console.log call
Hope it helps
Okay, for anyone interested, the answer lay in the fact that an array with an object inside of it was being returned in PageSingle.js.
The answer therefore lay in grabbin gthe correct page with an if statement and pushing the required values to an array and then accessing that with bracket notation:
let thisPage = [];
props.pages.map((page) => {
if (page.id === Number(props.match.params.pageId)) {
thisPage.push(page.id, page.title.rendered, page.content.rendered, page.featured_images);
}
return thisPage;
})
return (
<div>
<h4>{thisPage[1]}</h4>
<p>{thisPage[2]}</p>
</div>
)
Got to be a less convoluted way of doing this though so, any suggestions please let me know.

Redux/React-Router - Being Able to Send URL's with Parameters with Client Side Routing

I'm looking to be able to have Redux populate state correctly when given a search parameter url.
Currently my flow goes as follows:
Search ->
<Redirect to={{
pathname: '/search',
search: `?item-name=${this.props.searchQuery}`
}} />
Which then redirects it to a /search component = {SearchResults}
In SearchResults, it is able to get items which were queried from the server due to a Redux action dispatching a GET Request.
return axios.get(`/api/search?item-name=${itemName}`)
.then(response => {
let json = response.data;
dispatch(requestItemsSuccess(json));
}).catch(error => {
dispatch(requestItemsFailure(itemName));
});
And therefore that object is available in Search Results by the Redux state since I've connected it.
function mapStateToProps(state) {
return {
// Gets the list of fetched items
items: state.items.items
};
}
If I were to give a friend https://mywebsite.com/search?item-name=foo my "items" state would be empty because it did not go through all the actions being dispatched, and therefore technically did not grab the information from my db.
Is there anyway I can allow this item information to be requested on entering the above url while still keeping Redux/Routing stuff done client-side?
This is similar to saying that when you refresh the page https://mywebsite.com/search?item-name=foo your axios call doesn't get fired.
What you'll need to do is create something like this:
componentDidMount() {
const {items} = this.props; // assuming you can check whether items is populated
if (!items) {
this.props.dispatch(
makeSearchResultsAxiosCall(this.props.location.query.item-name)
)
}
// use this.props.location.query to access the query parameter
}
That should trigger the request if the page is refreshed or someone navigates directly to it.

How to find Popup state and refresh state in React-router?

I know this question kind a stupid But I am pretty confused with this site producthunt how they are doing this.When clicking the product list popup with react router is done like this..
But When I refresh that page it render like this..How this is done using React-router
My bet would be that they use the state property when pushing a page to give an indication to the component about how to render the page. More specifically, to indicate the component where it comes from. For example:
router.push({
pathname: '/posts/origami-studio-by-facebook',
state: { fromPosts: true }
})
And then you can read the router's state in the route's component to check what page to show.
const Post = (productName) => {
if(this.context.router.location.state.fromPosts) {
return <Posts productPopup{productName} />
// open the posts page with a popup for the product
} else {
return <PostPage productName={productName} />
}
}
So when you open the page in your browser, the state.fromPosts is not set and you get redirected to the PostPage. In the end, even if the route is the same, what you end up seing is completely different.

Categories