I have a state in Redux called page and is changed whenever I click a URL to another page. This is to make my website just have one URL. Whenever I request for a new page, it only gets updated on the next click. For example, when I click Login, nothing happens. Then when I click Register, the Login page renders. Then when I click About, the Register page renders. Why is the page rendering 1 page slow? Is there any way to fix this? I want to make a fade animation to the page and then make the new page appear. Is this something to do with having to use nextProps? I've heard about it somewhere but I neither know what it is nor know how to use it.
import React, { Component } from 'react'
// Redux
import { connect } from "react-redux"
// Components
import Login from './routes/Login'
import About from './routes/About'
import Register from './routes/Register'
import Chat from './routes/Chat'
// PropTypes
import PropTypes from 'prop-types'
class Renderer extends Component {
state = {
rendered: <About /> // Default page to render
}
static propTypes = {
page: PropTypes.string.isRequired
}
componentDidUpdate(prevProps) {
const main = document.getElementsByTagName('main')[0] // Where the rendered page should be
if (this.props.page !== prevProps.page) { // If page state changes
main.style.animation = "fadeOut 0.5s forwards" // Fade old page
setTimeout(() => { // After faded
let returned
switch (this.props.page) {
case 'About':
returned = <About />
break
case 'Login':
returned = <Login />
break
case 'Register':
returned = <Register />
break
case 'Chat':
returned = <Chat />
break
default:
returned = <About />
}
this.state.rendered = returned
main.style.animation = "fadeIn 0.5s forwards" // Show new page
}, 500)
}
}
render() {
return this.state.rendered
}
}
const mapStateToProps = state => ({
page: state.page
})
export default connect(mapStateToProps)(Renderer)
Is there a way to render data in the componentDidUpdate() instead of putting it in the state then updating it? Because I think that is the main cause of the problem
I'm guessing that you should call this.setState and not this.state.rendered = returned. Furthermore the animation isn't going to be right this way. I think it works if you call the animation inside the setState callback, which should happen after the component is re-rendered ( see reactjs.org/docs/react-component.html#setstate )
Related
I have a rails (7.0.2) application and just installed React. I'm very new to react and can't seem to understand why it looks like my component is loading multiple times, the first time with an empty value for props and the second time with the correct values for props.
App.js:
import "./App.css";
import axios from "axios";
import Customers from "./components/customers";
import { useEffect, useState } from "react";
const API_URL = "http://localhost:3000/internal_api/v1/customers";
function getAPIData() {
return axios.get(API_URL).then((response) => response.data);
}
function App() {
const [customers, setCustomers] = useState([]);
useEffect(() => {
let mounted = true;
getAPIData().then((items) => {
if (mounted) {
setCustomers(items);
}
});
return () => (mounted = false);
}, []);
console.log('LOADED App.js');
return (
<div className="App">
<h1>Hello</h1>
<Customers customers={customers} />
</div>
);
}
export default App;
and customers.js:
import React from "react";
function Customers(props) {
console.log('LOADED customers.js');
return (
<div>
<h1>These customers are from the API</h1>
{props.customers.data.map((customer) => {
return (
<div key={customer.id}>
<h2>{customer.id}</h2>
</div>
);
})}
</div>
);
}
export default Customers;
When I remove this part of the code and reload the page, my props come through correctly when looking in console. Then, when I put the code back and save (without reloading), it displays correctly.
{props.customers.data.map((customer) => {
return (
<div key={customer.id}>
<h2>{customer.id}</h2>
</div>
);
However, as soon as I reload again, I get the same following error:
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
It seems as though the first time everything renders, props is empty. Then the second time, it is full with the data. I checked my rails app and it only hits the API once. What am I doing wrong?
More log outputs:
React component rendering multiple times?
React will render fast before completing the request in use Effect
so in first render customers array will be empty
when request is fulfilled, you are changing state, So react will re-render the component
Only component that uses state reloads when the state is changed this is required else UI will not update
failing when reloading the page? | Failed on Initial Load
Since in Initial render customers will have no data customers.data will be undefined so it will not have map
to bypass this error use props.customers?.data && props.customers.data?.map() addding question mark means expression will be evaluated if not undefined
Source - Optional_chaining
I use Link component for open pages without reload:
<Link href="/home"><a>Home</a></Link>
<Link href="/page"><a>Page</a></Link>
This in my home:
const loadedRef = useRef(false)
useEffect(() => {
if(!loadedRef.current){
console.log("run")
loadedRef.current = true;
}
}, []);
This work fine for first load.
If I click on page and click on home, useEffect run again!
I want only and only once load useEffect even click another pages and return to home
useRef creates a value for this specific instance of the home component. If the instance unmounts, then that value goes away. If you want to make a global variable that persists even after the component has unmounted, do something like this:
import React, { useEffect } from 'react';
// Deliberately making this *outside* the component
let loaded = false;
const Home = () => {
useEffect(() => {
loaded = true;
}, []);
// ...
}
export default Home;
It occurs because the feature "shallow" of next.js
https://nextjs.org/docs/routing/shallow-routing
The official documentation asks to listen to the variable in the query string that receives the request. Generally, this variable is the name of your page like [slug].js. You can inspect the Network tab (F12) to see what is variable used in the query string also.
The below example inspects the slug variable.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Current URL is '/'
function Page() {
const router = useRouter()
useEffect(() => {
// in my case the page is [slug].jsx
}, [router.query.slug])
}
export default Page
Here is my App.js that contains beside Header and the MainView
an AlertDialog. Alert dialog has two state controlled props:
msg={alertMsg} toggleClass={alertToggleClass}
The task of AlertDialog is to show if the className is "alertDlg" and disappear if it's "alertDlg alertDlgClosed". Testing it via the inspector manually (changing className) shows, that it works fine.
Therefore alertToggleClass is set to "alertDlg alertDlgClosed" when initializing, so that the alert dialog is hided by default.
Within the MainView Component (before render())
sendGlobalAlert("Test Alert Msg") gets called which is simply a callback to the showAlert(msg) method in App.js.
Now here goes the tricky part: calling setAlertToggleClass("alertDlg"); in showAlert(msg)-method shows the custom alert dialog as expected. However trying to disable it by calling setAlertToggleClass("alertDlg alertDlgClosed"); within the setTimeout creates an infinite loop to showAlert(msg)-method.
As far as I can see, there is no recursivity is in setTimeout(...).
I can't explain this behavior and would appreciate any helpful hints.
import './App.css';
import AlertDialog from './components/general/alert-dialog/AlertDialog';
import { Header } from './components/general/Header';
import MainView from './components/main/MainView';
import { useState } from "react";
function App() {
const [alertMsg,setAlertMsg] = useState("");
const [alertToggleClass,setAlertToggleClass] = useState("alertDlg alertDlgClosed");
function showAlert(msg){
console.log("Showing alert dialog");
setAlertMsg(msg); // set message
setAlertToggleClass("alertDlg"); // show alert dialog
setTimeout(function() {
if(alertToggleClass === "alertDlg" ){
setAlertToggleClass("alertDlg alertDlgClosed");
console.log("hide alert");
}
// setAlertToggleClass("alertDlg test");
},3500);
}
return (
<div className="container">
<Header/>
<MainView sendGlobalAlert={showAlert}/>
<AlertDialog msg={alertMsg} toggleClass={alertToggleClass} />
</div>
);
}
export default App;
The sendGlobalAlert("Test Alert Msg"); within the MainView was the issue here.
After this call, which is basically a callback to App.js's showAlert(msg) method, the props stored in state, that is used by AlertDialog, were changed. Due to an update to these props, AlertDialog rerendered and that again caused a rerender in App.js, which means that it elements had to rerender too. As it was including MainView, the rerender of App.js meant a rerender of MainView, which calls the sendGlobalAlert("Test Alert Msg"); and started the recursiveness.
This is how the MainView looks like:
const MainView = ({sendGlobalAlert}) => {
sendGlobalAlert("Test Alert Msg");
return (
<div className="main-view" >
<BillView/>
<InteractionView sendGlobalAlert={sendGlobalAlert}/>
</div>
)
}
I have form with a drag and drop component where I can upload images, after this I send these pictures with axios to my backend and save it on server side and then re-render it in a preview mode. My problem is, that if a user uploads some pictures and after that he switches to another page without submitting the form the added pictures are staying on my server side for no reason.
So the question: I want to check inside my component if a user is leaving, show a prompt and if he clicks on the OK button I want to delete all the added pictures from my backend before leaving. How can I detect this?
My simplified snippet:
function MyComp(props) {
const [Images,setImages] = useState([]) // in this array I store the recieved image's URL
const [isBlocking,setIsBlocking] = useState(true) // used for prompt
const handleSubmit = (event) => {
event.preventDefault();
setIsBlocking(false)
}
return(
<Grid container className={classes.mainGrid} direction="row" spacing={2}>
<Grid item xs={4} xl={4}>
<Prompt when={isBlocking} message="There are unsaved datas. Are you sure you want to leave?"/>
<form className={classes.form} onSubmit={handleSubmit}>
... somecode
</Grid>
</Grid>
)
}
export default MyComp
Thanks in advance
Inside React Function Component you can call the prompt when the user is trying to leave , i.e when the component is unmounting
In Class Component of React you can use React componentWillUnmount() and in Function Component You can use useEffect cleanup function
Code for Function Component
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
export default function Home(props) {
useEffect(() => {
return function cleanup() {
alert("unmounting");
//call api and execute your code here
};
}, []);
return (
<div>
<Link to="/home">
On Click I will unmount this component and go to /home
</Link>
</div>
</Link>
);
}
Code for Class Component
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
export default class Test extends Component {
componentWillUnmount() {
alert('unmounting component');
//call your code here
}
render() {
return (
<div>
<Link to='/home'>
On Click I will unmount this component and go to /home
</Link>
</div>
);
}
}
You can check this codesandbox if you want any ref
When you leave the page, the component method componentWillUnmount() fires. I don't recall how this behaves if you were to simply close the browser window as opposed to just navigating away, nor do I recall how you can escape it and stay on the component, but that should at least be a good starting point for you. Obviously you'd have to do a class extending React.Component for this one instead of a straight function.
How I can render another component when i logout on firebase.
I´m trying to re-render the page to print the LoginPage
This is my LoginPage that is render when I loggin with another form.
import React, { Component } from "react";
/*Importing firebase*/
import firebase from "firebase";
/*Importing pages*/
import LoginPage from "../login/LoginPage";
/*Importing components*/
import Header from "./containers/HandleHeader";
class IndexPage extends Component {
shouldComponentUpdate() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
this.forceUpdate();
return <LoginPage />;
}
});
}
render() {
return (
<div>
<Header />
</div>
);
}
}
export default IndexPage;
And this is my handleLogout that work when I click my logout button.
handleLogout = e => {
firebase
.auth()
.signOut()
.then(() => this.forceUpdate());
};
I want to make that when I logout I don´t need reload the page.
Usually the best way to do this is to maintain the logged-in state somewhere, then protect the entry points to any components that require authentication with logic like this:
render() {
const { loggedIn } = this.props;
if (!loggedIn) return <Redirect to="/login" />;
// Reset of component rendered below this point
}
Note that this logic can be in the component itself, some parent component, or some higher order component. The key is to have it somewhere that will prevent access to any protected component by redirecting in the render method before any protected information can be reached.
Redirecting is often achieved using some routing package like, say, react-router-dom, to navigate around. This means that when you log out, a user is implicitly always redirected because they can no longer access the protected components anymore.