ReactJS - Wait third party script response before render - javascript

I implemented J-Toker in my frontend app using React and flow (with a Rails API backend), in a similar way to this project react-flux-jwt-authentication-sample
The dashboard should only be accessed by logged in user. In order to do this, J-Toker calls my Rails API and return the loggedIn User (or not if no one is loggedIn...) that is accessible using Auth.user.
So I have my Dashboard component wrapped in an AuthenticatedComponent:
Dashobard.jsx
export default AuthenticatedComponent(Dashobard);
Here is my AuthenticatedComponent.jsx:
export default (ComposedComponent) => {
return class AuthenticatedComponent extends React.Component {
static willTransitionTo(transition) {
if (!LoginStore.isLoggedIn()) {
transition.redirect('/login', {}, {'nextPath' : transition.path});
}
}
...
}
Finally here is my LoginStore.jsx code:
import Auth from 'j-toker';
class LoginStore extends BaseStore {
...
isLoggedIn() {
return Auth.user.signedIn;
}
}
Everything works great except when I manually refresh the page. In this case, the Auth.user.signedIn return undefined even if the user is logged in and is redirected to the login page. I found that the problem comes from the fact that Auth.user is not yet loaded when LoginStore.isLoggedIn() is called because in this case Auth.user returns an empty object and if I wait a little (tested with a basic setTimeout) it returns the loggedIn user.
I am new to React.JS (using it for just a week now) and I'm not sure how to deal with this. I read A LOT of articles and cannot understand yet how React.JS is meant to be used, espacially with third party plugins (I also have a lot of problems using animation plugins but it's another story...).
Could someone help me on this?
It would be much appreciated :)

This happens because when you pass the configuration to J-Toker, the library will check for a stored token. In case it finds one it will validate the token with you API. This is why calling configure, it will return you a jquery Deferred object, which will be resolved after the validation has been done or rejected if there is no stored token. If the validation passes in you API the user object will be returned and set in Auth.user.
By following your example project you can fix this by rendering the component after the Deferred has been resolved.
var promise = Auth.configure({
apiUrl: 'your-api-host'
});
promise.always(function() {
router.run(function (Handler) {
React.render(<Handler />, document.getElementById('content'));
});
});

Related

How to set the initial screen depending on first launch or not?

I'm using AsyncStorage to detect whether it's first launch or not.
If the users launch the app for the first time, the app takes them to "TourScreen1" page. Or "Feed".
but below code is not working.
this returns object. It should return string.
Is there a way to make this code return string?
Or do I need to use another way?
const Initial = createAppContainer(
createSwitchNavigator(
{
Cover,
TourScreen1,
TourScreen2,
TourScreen3,
TourScreen4,
Register: RegisterToLogin,
Feed: TabNavigator
},
{
initialRouteName: AsyncStorage.getItem("isLaunched").then(value => {
if (value === null) {
return "TourScreen1";
} else return "Feed";
})
}
)
);
Since createSwitchNavigator is synchronous as suraj said, you cant give dynamic initialroute name, What i did is ive made a splashscreen as initial page and that page would just have A logo of your app, and in that page you could have :
class SplashScreen extends React.Component {
constructor(){
super();
}
componentDidMount(){
AsyncStorage.getItem("alreadyLaunched").then(value => {
if(value == null){
AsyncStorage.setItem('alreadyLaunched', true); // No need to wait for `setItem` to finish, although you might want to handle errors
this.props.navigation.navigate('FirstTimeScreen');
}
else{
this.props.navigation.navigate('UsualScreen');
}}) // Add some error handling, also you can simply do
}
render(){
return(
<Image /> // image of app
)
}
hope it helps. feel free for doubts
getItem returns a promise which is an object, createSwitchNavigator is synchronous and doesn't wait for the getItem's promise to be fulfilled or rejected to finish up being executed. So, the promise is being fed to your initialRoute which is an object which is causing the error.
Coming to dynamic initialRoute values, there is no way to dynamically set the initialRoute
https://reactnavigation.org/docs/en/limitations.html#dynamic-routes
At least till react-navigation#4.x there is no way in any navigators of React Navigation, so to achieve so what you can do is to dynamically create the navigator itself upon your data if you want to stick to the same React Navigation's version or prefer upgrading the navigation lib to React Navigation 5 which overcomes this limitation.
Tthe reference link mentioned above for the limitation being quoted in React Navigation's official doc also contains hyperlink for the new API overcoming this limitation.
Hope this helps. Cheers!

Retrieve the status from multiple axios requests called from multiple components

I have a sort of Dashboard in my application. In this dashboard I let the user put many widgets (each widget is a class component). Each widget renders different stuff, such as, charts, images, and text. When I display it, each widget make an axios call to retrieve data from backend. I need a way to be able to tell when all the requests have finished so I can get the HTML completely rendered (I'm going to export it using HiqPdf later).
I need each widget to be independent so I can use in other components. That's why each widget make its own axios call. Otherwise I think I could make many axios calls in a single component that is above my widgets and then I would pass all the data as props to each widget. However, no... the axios calls must stay inside each widget.
I've found many places talking about promises, but every example talks show how to do it in a single component.
The reason I'm working on it is because I have the need to export it using a library call HiqPdf. This library receives a HTML as string and exports to PDF. Therefore, I need to know when the dashboard has been completely loaded to let the application export it.
Think about an event-driven framework that persists the global state of your single page app and notify subscribers whenever there is a change in the state.
One of the famous frameworks is redux.
Another simple framework is mufa. These are some similar questions that leverages mufa:
https://stackoverflow.com/a/42124013/747579
Stop the communication between react components using mufa after a condition
For your case, it might be something like this:
const all_calls = [];
const {on, one, fire, unsub} = mufa;
axios.get('call1').then((data) => {
fire('call1_received', data);
})
axios.get('call2').then((data) => {
fire('call2_received', data);
});
one('call1_received', () => {
all_calls.push('call1_received');
if (all_calls.length === 2) {
alert('!!!! All calls have been received')
}
})
one('call2_received', () => {
all_calls.push('call2_received');
if (all_calls.length === 2) {
alert('!!!! All calls have been received')
}
})
Note, one will subscribe once only.. while on subscribe forever.

React - fetching the data before the render (best solution)

So, I have an app that user can choose several settings in. Like number format or a currency that will be displayed by default.
For that there is a special API endpoint /settings that I get the values from.
The whole app is rendered on the server side when the user reloads the page. I fire the settings fetch as soon as possible (in the componentWillMount of the top level component), but there is sometimes a blink/ delay.
Possible solutions:
First solution is to fetch the settings when the user loads the app for the first time, and save it in the localStorage if it's available. Downside is that the numbers/ currencies still can be different than those in the settings when the app is loaded for the first time.
Second solution would be to fetch the data before the application is rendered on the server side, and inject this data somewhere into script tag (like a window.userSettings = { ...settings }). It might extend the reload loading time a bit, but the settings will be as the user set them.
Is there any other solution to such a problem? What is the best way to do it?
I hope, this solution may help you.
step 1: Perform API call in componentWillMount and also, check for error. Assign two state one for currencies and one for error, if possible another one for loading
Step 2: Assign localStorage using your state in componnetDidMount
Step 3: Then, under your render method, check for both error state and localStorage
Sample snippet in given below
class App extends Component{
custructor(props){
super(props);
this.state={
currency_number: '',
error: false
}
}
componentWillMount(){
//perform fetch api and assign the state with the received items
// check for error, if so then setState({error: true})
}
ComponentDidMount(){
localStorage.setItem('currency', JSON.stringify(this.state.currency_number))
}
render(){
const currency = JSON.parse(localStorage.getItem('currency'))
if (this.state.error) {
return(
<div>
{ currency }
</div>
)
}
}
}

ReactJs - redirect url

I am new to reactjs - and if I hit an error page on a subpage - because the url is wrong - I am looking to find a way to redirect the page to another.
So de/dienstleistungen/isdfisf -- spelt wrong
redirect to de/dienstleistungen
I've tried to write a function to handle this but no luck
if (!lang.serviceIntro[0][service]) {
console.log('undefined', this)
this.context.router.push('/de/dienstleistungen')
}
Failed context type: Invalid context router of type object supplied to PlainHeader, expected function.
are you using react-router? and if used which version are you on it?
Check out https://reacttraining.com/react-router/web/api/history which is currently on v4. This is my code
class App extends Component {
componentDidMount() {
let token = ... // achieve token
if (!token) {
this.props.history.push('/login');
}
}
render() {
....
}
}
my code in main page checking if not authenticated will redirect to login page programmatically. Props this.props.history will be passed by react router through .
Check the docs above for more information. Hope it help
As the error message says, you should declare contextTypes.router as an object, router was a function in older versions
static contextTypes = {
router: PropTypes.object.isRequired
}

Initial data loading ReactJS

I would like to perform some initial data loading when my first route is rendered (for example, i want to load a list of news articles)
I made a component called News.js which renders the articles. The problem i'm experiencing with the FLUX model is where to load this initial data.
The service i have made to load the data is the following:
import request from 'superagent';
class NewsService {
fetchArticles(callback) {
request
.get('http://localhost/articles')
.accept('json')
.end(function(err, res){
console.log(err);
var result = JSON.parse(res.text);
callback(result);
})
}
}
export default new NewsService ();
This service has to be called somewhere. According to the ReactJS documentation i would perform this operation like this:
export default class News extends React.Component {
constructor() {
super();
this.state = {
_articles: []
}
}
componentDidMount() {
NewsService.fetchProjects(function(articles){
// load articles in the state
this.setState({_articles: _articles})
});
}
render() {
return (
<section>
<h1>Articles</h1>
<ul>
{this.state.articles.map((article) => {
<li>{article.title}</li>
})}
</ul>
</section>
)
}
}
Now my question is, isn't this against the flux principle? Shouldn't the data be called as an Action which then stores it's data in a store such as NewsStore?
Should an action be like the following:
var NewsActions = {
load: function() {
NewsService.fetchProjects(function(articles){
// store the articles in the NewsStore and dispatch afterwards
});
},
create: function(project) {
AppDispatcher.dispatch({
actionType: NewsConstants.ARTICLE_CREATE,
project: project
});
},
update: function(id, project) {
AppDispatcher.dispatch({
actionType: NewsConstants.ARTICLE_UPDATE,
id: id,
project: project
})
},
destroy: function() {
AppDispatcher.dispatch({
actionType: NewsConstants.ARTICLE_DESTROY,
id: id
})
}
};
export default NewsActions;
In the Chat-app example of reactjs they provide an API call example. However this API call is called on the application start up (in app.js) which is not applicable in my scenario as i would like to use routings. Would i load the data in the router then? (I am using react-router)
Any feedback regarding this matter or improvements of this code is more than welcome.
EDIT
isn't this against the flux principle?
Maybe, maybe not. Seems like Flux is pretty flexible. From what I understand, it's more of a framework of principles rather than a strict "protocol". It's hard to say, but it appears that both example you've given will work. Like you said, according to the docs, they recommend fetching the data in componentDidMount:
componentDidMount: function() {
$.get(this.props.source, function(result) {
// ...
However, in your example, you're simply moving that API call into a service, which can then interact with the store/dispatcher, etc., in order to be utilized across the entire application.
So what you've done is moved a nice chunk of your application logic to, essentially, its own module (well, a module that is a part of your dispatchers). It appears that it will meet your needs: it can be used across your app, and it can be pulled out or plugged back in as you see fit. I don't see anything wrong with it. Could it be against some principle of Flux? Maybe, maybe not. I doubt it matters, though.
ORIGINAL
I'm not well-versed in Flux architecture, but looking at one of Facebook's examples in their GitHub repo (specifically, Line 24 of TodoMVC):
function getTodoState() {
return {
allTodos: TodoStore.getAll(),
areAllComplete: TodoStore.areAllComplete()
};
}
Their example doesn't show how TodoStore interacts with the server, but it does look like for their initial state, they're simply querying the todos in the store, and then for changes, their listening for and emitting events.
So as far as getting the initial state, it looks like their example shows querying the store directly. Obviously since the time they made that example and now, there may have been changes, but it may be worth investigating some examples in the Flux repo to get an idea of how it was designed.

Categories