Conditional rendering not working after a timeout delay in ReactJS - reactjs

I'm building a simple website with ReactJS (using the Gatsby framework) and trying to handle some conditional rendering using states and React hooks. What I'm trying to accomplish is to basically have a welcome message display for 4 seconds and then render different content.
Here's the code for the render state change (Note: there are two different newGame states because one renders if it's the first time the user sees this screen, and the other renders if they're returning to the site through cookies).
useEffect(() => {
if ((welcomeScreen === true && phase === 'newGame') || (welcomeScreen === true && phase === 'newGamePlus')) {
const timer = setTimeout(() => {
setPhase('gameStart');
setWelcomeStatus(false);
}, 4000);
return () => clearTimeout(timer);
}
}, [phase, welcomeScreen]);
And here is my state management chain. This may be very unorthodox/clunky since I'm quite new to this, so please feel free to correct my mistakes or guide me towards a better path.
if (phase === 'index') {
return (
<Layout>
<div className="mainContainer">
<h1>Enter your name~</h1>
<form onSubmit={handleClick}>
<input
className="nameInput"
onChange={handleChange}
type="text"
placeholder="What's your name?"
value={name}
/>
<button type="submit" className="nameButton">Submit</button>
{ showErrorMessage ? <ErrorText /> : null }
</form>
</div>
</Layout>
)
} else if (phase === 'newGamePlus' && welcomeScreen === true) {
return (
<Layout>
<div className="mainContainer">
<h1>Welcome back, {cookies.get('userName')}~</h1>
</div>
</Layout>
) }
else if (phase === 'newGame' && welcomeScreen === true) {
return (
<Layout>
<div className="mainContainer">
<h1>Welcome, {savedName}~</h1>
</div>
</Layout>
)
} else if (phase === 'gameStart' && welcomeScreen === false) {
return (
<Layout>
<div className="mainContainer">
<h1>Time to play!</h1>
</div>
</Layout>
)
};
The intended effect is to simply have the welcome screen display the content in the final 'gameStart' phase. At the moment, there is a 4 second delay, and then the site crashes with a 'no render' error message from React. I'd greatly appreciate some insight into what I'm doing terribly wrong here, I'm sure it's probably multiple things!
Thanks!

When using setTimeout(), state updates are not batched. In your codes, setPhase('gameStart') will trigger a state change, so useEffect will be executed again, and setWelcomeStatus(false) will be skipped. You will always end up with phase === 'gameStart' && welcomeScreen === true.
Possible solution:
use only one setState in useEffect, so you need to change the variable phase and welcomeScreen to an object variable instead of two separate variables.

Related

Rendered fewer hooks than expected. This may be caused by an accidental early return statement. Conditional rendering component based on state [duplicate]

This question already has an answer here:
React hooks: call component as function vs render as element
(1 answer)
Closed 1 year ago.
I'm new to React Hooks, I'm facing this error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement. when I tried to do a conditional rendering in my function component.
Here's my component:
export const MyComponent= () => {
const [page, SetPage] = useState("");
return (
<div>
{
page === "" && <>
<div onClick={() => SetPage("Page1")}>Show Page 1</div>
<div onClick={() => SetPage("Page2")}>Show Page 2</div>
<div onClick={() => SetPage("Page3")}>Show Page 3</div>
</>
}
{
page === "Page1" && Page1()
}
{
page === "Page2" && Page2()
}
{
page === "Page3" && Page3()
}
</div>
);
};
When I click on "Show Page 1", I expect it to display the Page1() functional component. However I get this error instead: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
I'm sure it's a stupid beginner mistake, and I tried to look through similar errors and solutions online but no luck. Any help is appreciated.
Assuming Page1, Page2 and Page3 are React components, instead of:
{
page === "Page1" && Page1()
}
{
page === "Page2" && Page2()
}
{
page === "Page3" && Page3()
}
Try:
{
page === "Page1" && <Page1 />
}
{
page === "Page2" && <Page2 />
}
{
page === "Page3" && <Page3 />
}

How to display popups with time delay in react.js?

I have three custom popups and I am able to display them one after another with time delay.
But there is one scenario I am not able to resolve.There are 3 popups -
1)Welcome Video Popup 2)Profile Popup 3) Custom Notification Popup
I have a custom notification popup modal and this comes at the last before the two popups.
Now I want if there is no WelcomeVideo Popup and Profile popup and user hasn't allow or blocked notifications, my custom Notification Popup should show.
My Custom popup will only show if state setNotificationPopup is true.
Here is the code --
const handleCloseWelcomeMessagePopup = () => {
const firstTimeCheckIn = localStorage.getItem('firstTimeCheckIn');
if (firstTimeCheckIn !== 'true' && isShowWelcomeMessage) {
setTimeout(() => {
setNotificationPopup(true);
}, 5000);
}
};
const handleCloseProfilePopup = () => {
setTimeout(() => {
setNotificationPopup(true);
}, 5000);
setShowProfilePopup(false);
};
useEffect(() => {
const firstTimeCheckIn = localStorage.getItem('firstTimeCheckIn');
if (isAuthenticated && !isShowWelcomeMessage && isShowProfilePopup === false) {
if (firstTimeCheckIn !== null && firstTimeCheckIn !== true) {
setNotificationPopup(true);
}
}
}, []);
return (
{(welcomeMessage || welcomeMessageVideo) && (
<PopupModal
id="welcome-message"
headerText={
welcomeMessage && (
<p
className="fr-view welcome-header-text"
dangerouslySetInnerHTML={{
__html: welcomeMessage,
}}
/>
)
}
showModal={isShowWelcomeMessage}
onCloseFunc={() => {
handleCloseWelcomeMessagePopup();
}}
>
)}
{isShowProfilePopup && (
<PopupModal id="profile-popup" showModal={isShowProfilePopup} onCloseFunc={() => handleCloseProfilePopup()}>
<div>{<ProfileDetails params={params} isFirstTime hideProfilePopup={handleCloseProfilePopup} />}</div>
</PopupModal>
)}
{window?.Notification?.permission === 'default' && (
<PopupModal
id="otpPopup"
custClassName={'notification-popup'}
showModal={notificationPopup}
headerText={
<>
<div className="d-flex">
<Image
className=
'img-responsive notification-logo',
src={`${IMAGE_URL}${popupImage}`}
/>
<div>
<p className="notification-title">Test !</p>
<p className="notification-body">{`Allow notifications so you don't miss announcement or direct messages!`}</p>
</div>
</div>
</>
}
backdrop="static"
modelFooter={
<div className="notification-footer">
<Button className="btn btn-link" variant="secondary" onClick={closeNotification}>
Close
</Button>
<Button className="btn btn-primary" onClick={askNotificationPermission}>
Allow
</Button>
</div>
}
/>
)}
)
Note - The issue is if there is Profile Popup then my custom Notification Popup shows immediately after profile Popup and also after 5 sec.
Let's first understand how setTimeout works.
Javascript is a single-threaded language but not a browser. The browser is running three threads at a time: Js Engine, UI thread, and the timer thread. This timer thread actually controls the setTimeout.
Now when we are calling setTimeout, a timer thread starts the countdown, but our actual callback function does not come into execution before the other functions in the stack are not finished. So if there are other time-consuming functions being executed when time up, the callback of setTimeout will not finish in time.
Hence in your code also the code you wrote is correct the problem is with the use of setTimeout. Once we correct that our code will be executed as the way we want.
I hope this piece of information helped you.
For further understanding the concept of setTimeout please refer to the following link :-
https://www.javascripttutorial.net/javascript-bom/javascript-settimeout/
Maybe you should refactor you code as below.
there should be an array that contains all pop-ups that should be shown, in order.
Once this array is defined, your component will iterate through the array, showing popups.
Below is example
export const SomeComponent =(showPopup1,showPopup2,showPopup3)=> {
const popupsToShow = []
showPupup1 && popupsToShow.append(1)
showPupup2 && popupsToShow.append(2)
showPupup3 && popupsToShow.append(3)
useEffect(()=>{
for (i,e in popupsToShow){
setTimeout(() => {
showPopup(e)
}, i * 1000);
}
},[])
}

How to conditionally render a prop initialized as an empty object?

My application has a user input a query to make a request to the Pokemon API and the response is rendered. Prior to the user making a request, it shows No Pokemon yet, please submit a Pokemon.
I initialized pokemonCharacter as an empty object being passed to the PokemonInfo component. However, my conditional render logic is not working.
Please let me know how to solve this. https://codesandbox.io/s/pokedex-5j1jf
The following are my failed attempts.
Attempt #1
let myDiv;
if (pokemonCharacter && pokemonCharacter.length > 0) {
myDiv = <div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
} else {
myDiv = <p>No Pokemon yet, please submit a Pokemon!</p>
}
return ({myDiv})
Attempt #2
{(pokemonCharacter && pokemonCharacter.length > 0) ?
<div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
: <p>No Pokemon yet, please submit a Pokemon!</p>}
Attempt #3
const list = pokemonCharacter => {
if (!pokemonCharacter) {
return null;
}
if (!pokemonCharacter.length) {
return <p>No Pokemon yet, please submit a Pokemon!</p>
} else {
return (
<div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
)
}
}
return (
{list}
);
Where you are checking the loading status, just check if the status is false or not then display the component.
{ loading
? <p>Loading...</p>
: <PokemonInfo pokemonCharacter={pokemonCharacter} />
}
I solved this by checking if pokemonCharacter.name is truthy in the PokemonInfo component, as opposed to just checking if pokemonCharacter the object as a whole is truthy.
{pokemonCharacter.name ?
<div>
<p>{pokemonCharacter.name}</p>
<img src={pokemonCharacter.sprites.front_default} alt="pokemon" />
</div>
: <p>No Pokemon yet, please submit a Pokemon!</p>}

React - onClick event not working correctly

I'm attempting to pass an onClick function as a prop to a child component in React. However, nothing is being logged to the console when the button is clicked. For now I'm just trying to console log to make sure the event is actually firing.
Any Ideas?
class App extends React.Component {
togglePallets = (pallet) => {
console.log('test');
}
render() {
return (
<div className="mainWrapper">
<div className="mainContainer">
<div>
<img src="images/picture-of-me.jpg" alt="Me"></img>
</div>
</div>
<SideBar toggle={this.togglePallets} showPallets={[this.state.showAboutPallet, this.state.showLanguagesPallet,
this.state.showProjectsPallet, this.state.showContactPallet]}/>
{this.state.showAboutPallet && <AboutPallet />}
{this.state.showAboutPallet && <LanguagesPallet />}
{this.state.showAboutPallet && <ProjectsPallet />}
{this.state.showAboutPallet && <ContactPallet />}
</div>
);
}
}
function SideBar(props) {
return (
<div className="sideBarContainer">
<Button icon={faUser} showAboutPallet={props.showPallets[0]} onClick={props.toggle}/>
</div>
);
}
What you have written is correct. But we can try it in another way using an arrow function.
onClick={(e) => props.toggle(e,data)}
And, make relevant changes in toggle function, so it may support multiple arguments.
Change your togglePallets to any of the below
togglePallets() {
console.log("test");
};
If you want to access event then
togglePallets(event) {
console.log("test");
};
Or
togglePallets=event =>{
console.log("teeventst");
};

React conditional components mount multiple times

I'm not using react router and simply have some condition to show and hide certain components.
return (<div className={classes.content}>
{ Navigation[this.state.view] && Navigation[this.state.view].selected === 'colors' && <Colors /> }
{ Navigation[this.state.view] && Navigation[this.state.view].selected === 'typography' && <Typography /> }
{ Navigation[this.state.view] && Navigation[this.state.view].selected === 'decoration' && <Decoration /> }
{ Navigation[this.state.view] && Navigation[this.state.view].selected === 'footer' && <Footer /> }
</div>)
This will call componentDidMount inside each component with the number of times that those conditions are met. Can you pinpoint what's wrong with the way I'm showing and hiding the components.
One way to solve your problem could be to store the map to your pages in your class or in any variable outside of your render. The component will need to be wrapped into functions to not be mounted when initializing your variable.
This can be done in your constructor for example :
this.pages = {
colors: () => <Colors/>,
typography: () => <Typography/>,
decoration: () => <Decoration/>,
footer: () => <Footer/>
}
Now, in your render, just take out the navigation page name you wish to load, extract the right one from your JSON object and call the function to render the correct element without loading the others :
const nav = Navigation[this.state.view]
return (
<div className={classes.content}>
{nav && this.pages[nav.selected]()}
</div>
)
I think it's because of that you are creating a react element every time those conditions are met. So I believe you can fix that by creating those components once, and returning them as needed.
So you need something like this:
class Something extends Component {
constructor() {
this.pages = {
colors: <Colors />,
typography: <Typography />
}
}
render() {
return (
<div className={classes.content}>
{ Navigation[this.state.view] && Navigation[this.state.view].selected === 'colors' && this.pages.colors }
{ Navigation[this.state.view] && Navigation[this.state.view].selected === 'typography' && this.pages.typography }
</div>
)
}
}
(You can also implement some lazy-loading behavior to createElements as needed and returning the previously created one if is available)

Resources