How to fetch data properly from route params? - javascript

I have one 'main' route with current data presented and another route with 'favorites'
when user click on a favorite component im sending him to the main route and need to present the new data fetched from an api and updating via redux.
So im using the code below in the main component , but what i get is that the previous data is presented for a few second and only then it changes :
async componentWillMount(){
let {cityName} = this.props.match.params
if (cityName) return await this.props.onDataLoad(cityName)
if (!dailyForecasts) await
this.props.onDataLoad(this.state.defaultCity)
}
How can i avoid this behavior ?

Related

Routing to page A or Page B based on API response

I am stuck on an issue. Let's say I have a home page. From this home page, I want to route to either page A or page B.
I have a list of items on the home page and what I need to do is when I click on any item in the list, it makes a GET API call, and based on one field in the response which is a boolean, I either need to redirect to page A or page B.
Basically, I need to call an API on the click of the item and get the response before it is routed to either Page A or Page B.
Many thanks in advance
if you're using Next.JS, use useRouter prop to achieve this.
for example
import {useRouter} from "next/router";
export default function Page() {
const router = useRouter()
async function route() {
let res = await apiFunctionCall();
if (res) {
await router.replace({
pathname: '/page1'
})
} else {
await router.replace({
pathname: 'page2'
})
}
}
}
The closest solution I could get is to create a Lazy loaded component which calls the API and then based on the response, routes to either page A or Page B.
Before routing to the correct page based on the data that has been returned, the data first needs to be stored in a variable. The data will be stored in the res variable. The if else statement will then route to either page A or B based on the data.
<code>const Response = res.data;
if(Response.type === 'A'){
this.props.history.push('/page-a');
} else {
this.props.history.push('/page-b');
}
</code>

Next.js: getStaticPaths for dynamic route with catching all routes [[...slug]].js

I read the docs of dynamic routes but they didn't explain much about how dynamic routes will work with "catching all routes".
My folder structure for this route is:
└──pages
└──catalog
└──[[...slug]].js
Here's my code:
export default function Catalog(props) {
return (
<Product product={props.product} />
)
}
export async function getStaticProps({ params }) {
const productSlug = params.slug[params.slug.length-1];
const data = await getSingleProduct(productSlug)
return {
props: {
product: data.product,
},
revalidate: 30
}
}
My API is WP and I have product pages URI like this /catalog/category/sub-category/product/
So if I go to the URL /catalog/category/sub-category/product/ it works fine with the code I shared below because I have const productSlug = params.slug[params.slug.length-1]; which will get my slug which I can pass to the API and use the product data just fine.
But I want to work with categories too, so if I go to /catalog/category/sub-category/ it should load the category page, and if I go to /catalog/category/ it should load up that category page.
Even this will work with the code I have because I'm getting the last element of params array which is the product slug, but that's NOT always the case. Sometimes the product is without any sub-category so the URI would be /catalog/category/product which means I can't fix it to the third element of the array and use the other two as category slugs.
The params gives me an array without any key or anything and I can't seem to figure out how to achieve this in next.js
Any help is appreciated!

Is it a good approach to use firebase once method when retrieving all posts on ionViewWillEnter?

I am retrieving all posts (news) from firebase using the once method and showing it on the home tab (the first tab when the app is launched) :
get_all_posts(){
this.posts = [];
firebase.database().ref('/posts/').once('value').then(snapshot => {
.... //rest of the code
}
}
This will be fired in the ionViewWillEnter():
ionViewWillEnter(){
this.get_all_posts();
}
In this case, get_all_posts method will be fired everytime the "home" tab is pressed right? which will get all posts again from the DB or for the entire session (from opening the app till closing the app running on the phone)the news retrieved from the first time only will be displayed?
The news retrieved at first time with once() will fetch all the data from the reference you want to show, this will fetch the data just once and then detach the listener to the reference. When you press again your home button it will fetch again the data that belongs to that reference and will pull it from the database, if there is new content it will be displayed, if not, the first fetched data will be shown.
From your question
Is it a good approach to use firebase once method when retrieving all
posts on ionViewWillEnter?
Yes, in my opinion, it is a good practice to just ask for the data once and then display it to the user, because if you use on() you will be always listening for new data and your home screen might be updated with new content before the user can see the first fetched news.

Fetch data only once to React App. Never fetch again after page reloads

I'm wondering if it's possible to fetch data only once to a running React app.
The goal that I want to achieve is this:
I have an app where I'm fetching user data from JSONPLACEHOLDER API, then passing this data to the state of this component and assigning to a usersArray variable.
During app is running there are some actions proceeded like deleting data from this usersArray.
WHAT IS HAPPENING:
After the page reloads, the main component is mounting once again and the data is fetched once again.
EXPECTATION :
I want to fetch this data only once and forever. Is it possible somehow to achieve this thing in React or I'm doing something wrong ?
You could put the data in localStorage and always check if there is some data there before doing the request.
Example
class App extends React.Component {
state = { users: [] };
componentDidMount() {
let users = localStorage.getItem("users");
if (users) {
users = JSON.parse(users);
this.setState({ users });
} else {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => {
this.setState({ users });
localStorage.setItem("users", JSON.stringify(users));
});
}
}
handleClick = index => {
this.setState(
prevState => {
const users = [...prevState.users];
users.splice(index, 1);
return { users };
},
() => {
localStorage.setItem("users", JSON.stringify(this.state.users));
}
);
};
render() {
return (
<div>
{this.state.users.map((user, index) => (
<div key={user.id}>
<span>{user.name}</span>
<button onClick={() => this.handleClick(index)}>Remove</button>
</div>
))}
</div>
);
}
}
You can use session storage if you want the data to be retrieved once PER SESSION or local storage if you want to have better control of the data's "expiration". set an arbitrary value in the fetch's callback that you'll check before fetching another time every time the app loads. The window.sessionStorage API is simple to use :
sessionStorage = sessionStorage || window.sessionStorage
// Save data to sessionStorage
sessionStorage.setItem('key', 'value');
// Get saved data from sessionStorage
var data = sessionStorage.getItem('key');
// Remove saved data from sessionStorage
sessionStorage.removeItem('key');
// Remove all saved data from sessionStorage
sessionStorage.clear();
Same syntax is used for window.localStorage
This is absolutely right :-
After the page reloads, the main component is mounting once again and the data is fetched once again.
As from my understanding , when you delete and then refresh then deleted data comes back.
This is off course going to happen as everything is getting stored in memory.
Solution :- You must use database , when you save data you do it in db , when you delete data , you delete it from db & when you fetch data then you fetch from db.
So any operation like update , delete will work fine now.
The question is WHY you expecting sth different?
It's only fake api, real would work as expected. If you're correctly sending change/delete requests - they will be memorized and refresh will read updated data (if not cached).
If it's a SPA (Single Page App) there is no need for refresh - it shouldn't happen. Maybe sth went wrong with router?

How to Handle Post Request in Isomorphic React + React Router Application

I want to build Isomorphic react + react-router application and after a few days googling, now I can achieve isomorphic application that only handles GET request.
Here's what I've done so far:
Server use react-router to handle all request
react-router will call fetchData functions that resides in each React View that matches the route.
Set the data fetched before into props of the React View and render it into string
Inject the string and data fetched before as global variable window.__STATE__ into HTML and deliver the HTML to the client
We have successfully render React App from the server
When the client finished loading our React App javascript, it will try to render. But we pass the state from window.__STATE__ as the props of our React App, and React will not re-render because the state is the same
The problem is it will not work with POST/PUT/DELETE/WHATEVER request. When handling GET request, react-router have information about params and query. For example if we have a route: /user/:uid and client request this url: /user/1?foo=bar, then params would be: {uid: 1} and query would be {foo: 'bar'}
react-router then can pass it down to fetchData function so it will know to fetch user with uid of 1 and do whatever with foo query.
While in POST request, react-router doesn't know about the POST parameters. On Server, of course we could pass the POST parameters to fetchData function, but what about the Client? It doesn't know what the POST parameters are.
Is there a way that the server could tell the Client about the POST parameters? Below is an example of my Login View. I want when user submit the form, the server will render error message on error, or redirect it to dashboard on success.
fetchData.js
import whenKeys from 'when/keys';
export default (authToken, routerState) => {
var promises = routerState.routes.filter((match) => {
return match.handler.fetchData;
}).reduce((promises, match) => {
promises[match.name] = match.handler.fetchData(authToken, routerState.params, routerState.query);
return promises;
}, {});
return whenKeys.all(promises);
}
server.js
...
app.use((req, res) => {
const router = Router.create({
routes,
location: req.originalUrl,
onError: next,
onAbort: (abortReason) => {
next(abortReason);
}
});
router.run((Handler, state) => {
fetchData(authToken, state).then((data) => {
// render matched react View and generate the HTML
// ...
})
});
});
...
login.jsx
import React from 'react';
import DocumentTitle from 'react-document-title';
import api from './api';
export default class Login extends React.Component {
constructor(props) {
super(props);
// how to fill this state with POST parameters on error?
// how to redirect on success?
// and remember that this file will be called both from server and client
this.state = {
error: '',
username: '',
password: ''
};
}
// I saw some people use this function, but it'll only work if
// the form's method is GET
static willTransitionTo(transition, params, query) {
// if only we could read POST parameters here
// we could do something like this
transition.wait(
api.post('/doLogin', postParams).then((data) => {
transition.redirect(`/dashboard`);
});
);
}
render() {
return (
<DocumentTitle title="Login">
<div className="alert alert-danger">{this.state.error}</div>
<form method="post">
<input type="text" name="username" value={this.state.username} onChange={this._onFieldChange('username')} placeholder="Username" /><br />
<input type="password" name="password" value={this.state.password} onChange={this._onFieldChange('password')} placeholder="Password" /><br />
<button type="submit">Login</button>
</form>
</DocumentTitle>
);
}
_onFieldChange(name) {
var self = this;
return (e) => {
e.preventDefault();
var nextState = {};
nextState[name] = e.target.value;
self.setState(nextState);
}
}
}
Getting "POST" data on the client
On the client side, you get POST data by extracting values from your form inputs in a way which corresponds to what you would have received on the server had the form been submitted normally.
Using POST data
So now you have your POST data, but you still have the problem that there's no way to feed the POST data into your transition hooks in React Router 0.13.x and earlier. I created a pull request for this feature which has now been closed because it was included as part of the rewrite for the upcoming v1.0 release.
The gist of it is that locations now have a state object for squireling away any extra data you need about the current request/transition (the two are analagous) being handled:
On the server, you're dealing with one request at a time, so you create a static Location with data from req.body
On the client you pass the state object (containing extracted form data) to transitionTo().
Now your transition hook is capable of receiving the same form data in both environments. If things go well, great! If things don't go well, you need a way to pass errors and re-render the form again. New state object to the rescue again! Use transition.redirect() and pass both input data and errors and you now have everything you need to render on both sides.
I'm not going into more specific detail right now because v1.0 is still in beta and v0.13.x doesn't have the necessary API to do this anyway, but I have a repository which uses the pull request above to implement this workflow with 0.13.x which you could look at in the meantime:
isomorphic-lab - the README gives an overview of how things fit together.
Here are some rough flow diagrams of the process, too:
Server POST with errors and redisplay
Client POST with errors and redisplay
I've also created a few reusable modules related to this scenario:
get-form-data gets data from a form's inputs in the format it would have been POSTed in.
react-auto-form provides <AutoForm>, which you can use instead of <form> to receive all the data from a form's inputs as an argument to its onSubmit handler
react-router-form, which is to <form> what React Router's <Link> is to <a> - it handles triggering a transition to the given action, passing method and body (form data) state - this will be updated for v1.0 soon.

Categories