Next.js data loading Issue - reactjs

I face an issue with Next.JS and fetching initial props to my pages. I am doing an application with several (7-8) pages. One the left menu, once a an icon is clicked, the router is pushing the user to the proper page. Then if the user is logged in, the page and child components load. Everything works, as I am trying to cover loading time with a loading component, something like this:
render() {
if (this.props.data !== undefined) {
return (<Provider store={store}><Main info={this.props.data} path={this.props.url.pathname} user={this.props.user} token={this.props.token} /></Provider>)
} else {
return (<Loading />)
}}
The designed goal is to kick the Loading Component when the data is fetching. Not after. I was trying to do it with React lifecycle hooks, but it seems that `
static async getInitialProps({req})
Comes before everything. Have a look on the entire code
export default class Component extends React.Component {
static async getInitialProps({req}) {
const authProps = await getAuthProps(req,
'url')
return authProps;
}
componentDidMount() {
if (this.props.data === undefined) {
Router.push('/login')
}
}
render() {
if (this.props.data !== undefined) {
return (<Provider store={store}><Main info={this.props.data} path={this.props.url.pathname} user={this.props.user} token={this.props.token} /></Provider>)
} else {
return (<Loading />)
}}}

getInitalProps is being called when the Page component is rendered. You should not use it in any other components since it won't work.
That is not part of React Lifecycle but Next's implementation. I believe what you wanna do is just fetch data and measure its loading time. If so, then using a componentDidMount might be enough.

Related

React Higher Order Component With Redux causes infinite loop

I'm trying to show loading while data is fetching from API and I want a higher-order component to achieve this. But my code causes an infinite loop
//home.tsx file
export class Home extends Component<Props, any> {
componentDidMount(): void {
this.props.getCharacters();
}
render() {
return (
<div></div>
)
}
}
const mapStateToProps = (state: AppState): StateProps => {
return {
loading:state.marvel.loading,
data: getResultsSelector(state),
};
};
const mapDispatchToProps: DispatchProps = {
getCharacters,
};
export default connect(mapStateToProps, mapDispatchToProps)(WithLoading(Home));
//hoc.tsx file
function WithLoading(Component:any) {
return function WihLoadingComponent({ loading, ...props }:any) {
console.log(loading)
if (!loading) return <Component {...props} />;
return <p>Hold on, fetching data might take some time.</p>;
};
}
export default WithLoading;
How can I fix this issue ?
I assume that getCharacters() is the function that fetches data from the API.
The Component is mounted depending on loading, but loading is set when the Component is mounted.
if (!loading) return <Component />; mounts the component.
componentDidMount invokes getCharacters()
getCharacters() sets loading = true
if (!loading) return <Component />; does not return the Component anymore.
when loading is done: loading = false
if (!loading) return <Component />; mounts the component again.
continue at 2.
The loading and mounting must be made independent of each other.
A. Either you should not invoke the loading inside the Component,
B. or you should not mount the Component conditionally.
A: separate the loading from the Component
As you probably want to create a generic HOC that shows some arbitrary Component only when loaded === true, you might prefer A., which means you have to design the Components so that they do not change loading themselves.
There might be ways to do that, e.g.:
componentDidMount(): void {
if( !this.props.dataHasAlreadyBeFetched ){
this.props.getCharacters();
}
}
but I think that would be bad style, and it seems to me that in your case it makes sense to separate it, because apparently the only reason your Component is mounted the first time is to invoke getCharacters, which unmounts the Component.
B: not un-mount the Component
The Component is always mounted (without if( !loading)), and itself has to be designed to render any content only if loading === true (and otherwise null). Of course, loading has to be passed to the Component as well.

React-Router <Link> to load a page, even if we're already on that page

I use react router in my website. I have homepage and contact.
When I am on homepage and I click on the homepage again. How can I reload the page like Facebook
issue here: https://github.com/ReactTraining/react-router/issues/1982
After looking at the issue, I can realize that you may use force update:
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
this.forceUpdate();
}
}
You could set up an event handler on the 'home' Link, that will fetch the data you want to be updated.
class Nav extends React.Component {
constructor () {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick () {
// code that fetches Home Page data
}
render () {
return (
<Link to='your/path' onClick={this.handleClick}>Home</Link>
)
}
}
This might be a common problem and I was looking for a decent solution to have in my toolbet for next time. React-Router provides some mechanisms to know when an user tries to visit any page even the one they are already.
Reading the location.key hash, it's the perfect approach as it changes every-time the user try to navigate between any page.
componentDidUpdate (prevProps) {
if (prevProps.location.key !== this.props.location.key) {
this.setState({
isFormSubmitted: false,
})
}
}
Reference: A location object is never mutated so you can use it in the lifecycle hooks to determine when navigation happens

React Isomorphic Rendering - handle window resize event

I would like to set the state of a component based on the current size of the browser window. The server-side rendering has been used (React+Redux). I was thinking about using the Redux store as a glue - just to update the store on resize.
Is there any other/better solution that doesn't involve Redux.
Thanks.
class FocalImage extends Component {
// won't work - the backend rendering is used
// componentDidMount() {
// window.addEventListener(...);
//}
//componentWillUnmount() {
// window.removeEventListener('resize' ....);
//}
onresize(e) {
//
}
render() {
const {src, className, nativeWidth, nativeHeight} = this.props;
return (
<div className={cn(className, s.focalImage)}>
<div className={s.imageWrapper}>
<img src={src} className={_compare_ratios_ ? s.tall : s.wide}/>
</div>
</div>
);
}
}
I have a resize helper component that I can pass a function to, which looks like this:
class ResizeHelper extends React.Component {
static propTypes = {
onWindowResize: PropTypes.func,
};
constructor() {
super();
this.handleResize = this.handleResize.bind(this);
}
componentDidMount() {
if (this.props.onWindowResize) {
window.addEventListener('resize', this.handleResize);
}
}
componentWillUnmount() {
if (this.props.onWindowResize) {
window.removeEventListener('resize', this.handleResize);
}
}
handleResize(event) {
if ('function' === typeof this.props.onWindowResize) {
// we want this to fire immediately the first time but wait to fire again
// that way when you hit a break it happens fast and only lags if you hit another break immediately
if (!this.resizeTimer) {
this.props.onWindowResize(event);
this.resizeTimer = setTimeout(() => {
this.resizeTimer = false;
}, 250); // this debounce rate could be passed as a prop
}
}
}
render() {
return (<div />);
}
}
Then any component that needs to do something on resize can use it like this:
<ResizeHelper onWindowResize={this.handleResize} />
You also may need to call the passed function once on componentDidMount to set up the UI. Since componentDidMount and componentWillUnmount never get called on the server this works perfectly in my isomorphic App.
My solution is to handle resize event on the top-most level and pass it down to my top-most component, you can see full code here, but the gist is:
let prevBrowserWidth
//re-renders only if container size changed, good place to debounce
let renderApp = function() {
const browserWidth = window.document.body.offsetWidth
//saves re-render if nothing changed
if (browserWidth === prevBrowserWidth) {
return
}
prevBrowserWidth = browserWidth
render(<App browserWidth={browserWidth} />, document.getElementById('root'))
}
//subscribing to resize event
window.addEventListener('resize', renderApp)
It obviously works without Redux (while I still use Redux) and I figured it would be as easy to do same with Redux. The advantage of this solution, compared to one with a component is that your react components stay completely agnostic of this and work with browser width as with any other props passed down. So it's a localized place to handle a side-effect. The disadvantage is that it only gives you a property and not event itself, so you can't really rely on it to trigger something that is outside of render function.
Besides that you can workaround you server-side rendering issue by using something like:
import ExecutionEnvironment from 'exenv'
//...
componentWillMount() {
if (ExecutionEnvironment.canUseDOM) {
window.addEventListener(...);
}
}

componentWillUnmount() not being called when refreshing the current page

I've been having this problem where my code in the componentDidMount() method wasn't firing properly when refreshing the current page (and subsequently, the component). However, it works perfectly fine just navigating and routing through my website by clicking links. Refresh the current page? Not a chance.
I found out that the problem is that componentWillUnmount() doesn't trigger when I refresh the page and triggers fine clicking links and navigating my website/app.
The triggering of the componentWillUnmount() is crucial for my app, since the data that I load and process in the componentDidMount() method is very important in displaying information to users.
I need the componentWillUnmount() to be called when refreshing the page because in my componentWillMount() function (which needs to re-render after every refresh) I do some simple filtering and store that variable in a state value, which needs to be present in the logos state variable in order for the rest of the component to work. This does not change or receive new values at any time during the component's life cycle.
componentWillMount(){
if(dataReady.get(true)){
let logos = this.props.questions[0].data.logos.length > 0 ? this.props.questions[0].data.logos.filter((item) => {
if(item.logo === true && item.location !== ""){
return item;
}
}) : [];
this.setState({logos: logos});
}
};
Cliffs:
I do DB filtering in componentWillMount()method
Need it to be present in the component after refresh
But I have a problem where the componentWillUnmount() doesn't trigger when the page is refreshed
Need help
Please
When the page refreshes react doesn't have the chance to unmount the components as normal. Use the window.onbeforeunload event to set a handler for refresh (read the comments in the code):
class Demo extends React.Component {
constructor(props, context) {
super(props, context);
this.componentCleanup = this.componentCleanup.bind(this);
}
componentCleanup() { // this will hold the cleanup code
// whatever you want to do when the component is unmounted or page refreshes
}
componentWillMount(){
if(dataReady.get(true)){
let logos = this.props.questions[0].data.logos.length > 0 ? this.props.questions[0].data.logos.filter((item) => {
if(item.logo === true && item.location !== ""){
return item;
}
}) : [];
this.setState({ logos });
}
}
componentDidMount(){
window.addEventListener('beforeunload', this.componentCleanup);
}
componentWillUnmount() {
this.componentCleanup();
window.removeEventListener('beforeunload', this.componentCleanup); // remove the event handler for normal unmounting
}
}
useWindowUnloadEffect Hook
I've extracted the code to a reusable hook based on useEffect:
// The hook
const { useEffect, useRef, useState } = React
const useWindowUnloadEffect = (handler, callOnCleanup) => {
const cb = useRef()
cb.current = handler
useEffect(() => {
const handler = () => cb.current()
window.addEventListener('beforeunload', handler)
return () => {
if(callOnCleanup) handler()
window.removeEventListener('beforeunload', handler)
}
}, [callOnCleanup])
}
// Usage example
const Child = () => {
useWindowUnloadEffect(() => console.log('unloaded'), true)
return <div>example</div>
}
const Demo = () => {
const [show, changeShow] = useState(true)
return (
<div>
<button onClick={() => changeShow(!show)}>{show ? 'hide' : 'show'}</button>
{show ? <Child /> : null}
</div>
)
}
ReactDOM.render(
<Demo />,
root
)
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
I also run into this problem and realised that I needed to make sure that at least 2 components will always gracefully unmount. So I finally did a High Order Component that ensures the wrapped component is always unmounted
import React, {Component} from 'react'
// this high order component will ensure that the Wrapped Component
// will always be unmounted, even if React does not have the time to
// call componentWillUnmount function
export default function withGracefulUnmount(WrappedComponent) {
return class extends Component {
constructor(props){
super(props);
this.state = { mounted: false };
this.componentGracefulUnmount = this.componentGracefulUnmount.bind(this)
}
componentGracefulUnmount(){
this.setState({mounted: false});
window.removeEventListener('beforeunload', this.componentGracefulUnmount);
}
componentWillMount(){
this.setState({mounted: true})
}
componentDidMount(){
// make sure the componentWillUnmount of the wrapped instance is executed even if React
// does not have the time to unmount properly. we achieve that by
// * hooking on beforeunload for normal page browsing
// * hooking on turbolinks:before-render for turbolinks page browsing
window.addEventListener('beforeunload', this.componentGracefulUnmount);
}
componentWillUnmount(){
this.componentGracefulUnmount()
}
render(){
let { mounted } = this.state;
if (mounted) {
return <WrappedComponent {...this.props} />
} else {
return null // force the unmount
}
}
}
}
Note: If like me, you are using turbolinks and rails, you might wanna hook on both beforeunload and turbolinks:before-render events.
I see that this question has over a thousand views, so I'll explain how I solved this problem:
To solve this particular problem, the most sensible way is to create an upper level component that loads your subscription or database, so that you load the required data before passing it to your child component, which would completely remove the need to use componentWillMount(). Also, you can do the computations in the upper level component and just pass them down as props to use in your receiving component
For example:
class UpperLevelComponent extends React.Component {
render() {
if(this.props.isReady) {
return(<ChildComponent {...props}/>)
}
}
}
export default createContainer(() => {
const data = Meteor.subscribe("myData");
const isReady = data.ready();
return {
isReady,
data: MyData.find.fetch()
}
})
In the example above, I use Meteor's reactive container to get my MongoDB data and wait for it to completely finish subscribing before I render the child component, passing it any props I want. If you load all your data in the higher level component, you won't have to rely on the componentWillMount() method to trigger after every refresh. The data will be ready in the upper level component, so you can use it however you want in the child component.

Prevent react-native-router-flux from rendering all components

I'm using React-Native-Router-Flux for routing my app. The issue is that it seems like when a redux state changes, ALL the components under the Router gets rerendered, not just the "current" component.
So lets say I have 2 components under the Router: Register and Login and both share the same authenticationReducer. Whenever an authentication event (such as user registration or signin) fails, I want to display error Alerts.
The problem is that when an error is fired from one of the components, two Alerts show up at the same time, one from each component. I assumed when I am currently on the Register scene, only the error alert would show from the Register component.
However, it seems like both components rerender whenever the redux state changes, and I see 2 alerts (In the below example, both 'Error from REGISTER' and 'Error from SIGNIN').
Here are the components:
main.ios.js
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Router>
<Scene key='root'>
<Scene key='register' component={Register} type='replace'>
<Scene key='signin' component={SignIn} type='replace'>
</Scene>
</Router>
</Provider>
);
}
}
Register.js
class Register extends Component {
render() {
const { loading, error } = this.props;
if (!loading && error) {
Alert.alert('Error from REGISTER');
}
return <View>...</View>;
}
}
const mapStateToProps = (state) => {
return {
loading: state.get("authenticationReducer").get("loading"),
error: state.get("authenticationReducer").get("error"),
};
};
export default connect(mapStateToProps)(Register);
SignIn.js
class SignIn extends Component {
render() {
const { loading, error } = this.props;
if (!loading && error) {
Alert.alert('Error from SIGNIN');
}
return <View>...</View>;
}
}
const mapStateToProps = (state) => {
return {
loading: state.get("authenticationReducer").get("loading"),
error: state.get("authenticationReducer").get("error"),
};
};
export default connect(mapStateToProps)(SignIn);
How do I change this so that only the REGISTER error message shows when I am currently on the Register Scene, and vice versa?
Thanks
Because of the way react-native-router-flux works, all previous pages are still "open" and mounted. I am not totally sure if this solution will work, because of this weird quirk.
Pretty strict (and easy) rule to follow with React: No side-effects in render. Right now you are actually doing a side-effect there, namely, the Alert.alert(). Render can be called once, twice, whatever many times before actually rendering. This will, now, cause the alert to come up multiple times as well!
Try putting it in a componentDidUpdate, and compare it to the previous props to make sure it only happens once:
componentDidUpdate(prevProps) {
if (this.props.error && this.props.error !== prevProps.error) {
// Your alert code
}
}
I am not totally convinced that this will actually work, as the component will still update because it is kept in memory by react-native-router-flux, but it will at least have less quirks.
I solved this by creating an ErrorContainer to watch for errors and connected it to a component that uses react-native-simple-modal to render a single error modal throughout the app.
This approach is nice because you only need error logic and components defined once. The react-native-simple-modal component is awesomely simple to use too. I have an errors store that's an array that I can push errors to from anywhere. In the containers mapStateToProps I just grab the first error in the array (FIFO), so multiple error modals just "stack up", as you close one another will open if present.
container:
const mapStateToProps = (
state ) => {
return {
error: state.errors.length > 0 ? state.errors[0] : false
};
};
reducer:
export default function errors (state = [], action) {
switch (action.type) {
case actionTypes.ERRORS.PUSH:
return state.concat({
type: action.errorType,
message: action.message,
});
case actionTypes.ERRORS.POP:
return state.slice(1);
case actionTypes.ERRORS.FLUSH:
return [];
default:
return state;
}
}

Resources