React - timed action does not load specific component - javascript

What I want: when the timer hits 0 seconds, the app mounts one component and hides others.
What happens: nothing.
I'm working on React single page app. I'm having a problem with the behavior of the timer when it hits 0. I want it to hide the Questions and Timer components and show just the Results component. Right now, the logic is in timerZero, but I did try putting it in startTimer and/or clickStart, but none of those combinations worked.
I've also noticed that if you select answers after the timer hits 0, it will continue console logging "Time's up!" on every selection. Hitting submit after 0 seconds will still take you to the resultsDiv with the correct scores but does not hide the timer as instructed.
Repo: https://github.com/irene-rojas/pixar-react
App
import React, { Component } from 'react';
import './App.css';
import Timer from "./Timer";
import Questions from "./Questions/Questions.js";
import Results from "../src/Results";
class App extends Component {
state = {
totalTrue: 0,
totalFalse: 0,
showTimer: true,
showQuestions: false,
showResults: false,
}
clickStart = (event) => {
event.preventDefault();
console.log("start button clicked");
this.setState(
{showQuestions: true}
)
}
// submit button
handleFormSubmit = (event) => {
event.preventDefault();
console.log("submit button clicked");
this.setState(
{showResults: true,
showQuestions: false,
showTimer: false}
// timer still appears in resultsDiv
)
};
timerZero = () => {
if (this.state.timer === 0) {
this.setState(
{showResults: true,
showQuestions: false,
showTimer: false}
)
}
// nothing happens >:(
};
callbackHandlerFunction = ( selectedOption ) => {
const answerValue = selectedOption.value;
if (answerValue === true) {
this.setState({totalTrue: this.state.totalTrue + 1}, () => {
console.log(`New TotalTrue: ${this.state.totalTrue}`);
});
};
if (answerValue === false) {
this.setState({totalFalse: this.state.totalFalse + 1}, () => {
console.log(`New TotalFalse: ${this.state.totalFalse}`);
});
};
}
render() {
return (
<div className="parallax">
<div className="App">
<div className="wrapper">
<div className="headerDiv">
<h1>Pixar Trivia!</h1>
</div>
<div className="timerDiv">
<Timer
handleTimerClick={this.clickStart}
timeOut={this.timerZero}
/>
</div>
{this.state.showQuestions &&
<div className="questionSection">
<Questions
handleClickInParent={this.callbackHandlerFunction}
/>
<div>
<button onClick={this.handleFormSubmit}>Submit</button>
</div>
</div>
}
{this.state.showResults &&
<div className="resultsDiv">
<Results
totalTrue={this.state.totalTrue}
totalFalse={this.state.totalFalse}
/>
</div>
}
</div>
</div>
</div>
);
}
}
export default App;
Timer
import React, { Component } from 'react';
class Timer extends Component {
state = {
timer: 10
};
startTimer = (event) => {
this.timer = setInterval(() => this.setState({
timer: this.state.timer - 1}), 1000);
// onClick, load Questions
this.props.handleTimerClick(event);
};
stopTimer = () => {
clearInterval(this.timer);
console.log("Time's up!");
this.props.timeOut();
};
render() {
return (
<div className="Timer">
<div>{this.state.timer} seconds</div>
<button onClick={this.startTimer}>Start!</button>
{this.state.timer === 0 && this.stopTimer()}
</div>
);
}
}
export default Timer;

I found out what was wrong with your code, I'm just going to break up where the mistakes are.
App.js
// ...
/*
you were trying to read this.state.timer
which is not decalred in this component
*/
timerZero = () => this.setState(
{showResults: true,
showQuestions: false,
showTimer: false}
)
// ...
render() {
{/* ... */}
{this.state.showTimer && (
<div className="timerDiv">
<Timer
handleTimerClick={this.clickStart}
timeOut={this.timerZero}
/>
</div>
{/* ... */
)}
Timer.js
// ...
/*
I added `shouldComponentUpdate` lifecycle
with this, we stop the `Timer` component for rendering
and call `stopTimer` (instead of doing it inside the render method)
*/
shouldComponentUpdate() {
console.log(this.state.timer);
if (this.state.timer <= 0) {
this.stopTimer();
return false;
}
return true;
};
/*
Also added the a componentWillUnmount method for good practice
here if the component is unmounted the timer won't be running forever.
*/
componentWillUnmount() {
clearInterval(this.timer);
};
render() {
return (
<div className="Timer">
<div>{this.state.timer} seconds</div>
<button onClick={this.startTimer}>Start!</button>
{/* delete the call to `this.stopTimer` */}
</div>
);
}
Also as an extra, I recommend you to ignore the node_modules folder in a .gitignore file to make your projects leaner. all your project dependencies are listed in yarn.lock or package-lock.json so when I download your repo I get to download the same dependencies you are using.
cheers!!!

Related

my countdown clock beings counting down one turn after it should

I have a working countdown clock except for the fact that it begins counting down one 'turn' after it should. I have a button which starts the game and starts the clock. I press it and it retrieves and shows my random number (say 5) and displays my random number of inputs (5). But the countdown says 0. which is (20 * null) of the loading page (this.state loads as null). I press start again, and there is a new random number but the countdown begins counting down from (5 * 20) instead of the new random number. I am a bit at a loss. Any help would be greatly appreciated.
The whole code
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
let timer = null
class StartButton extends React.Component {
render(props) {
return (
<button onClick={this.props.onClick}>{this.props.name}</button>
)
}
}
class Header extends React.Component {
constructor() {
super();
this.state = {
ranNum: null,
restart: false,
timer: null
};
}
handleClick() {
clearTimeout(timer);
this.clearForm();
this.generateRanNum();
this.generateInputs();
// this.changeToRestartText()
this.countdownClock();
}
generateRanNum = () => {
let ranNum = Math.floor(Math.random() * 20);
this.setState({
ranNum: ranNum,
})
}
clearForm = () => {
if(this.state.restart === true){
const inputLi = <Input />;
inputLi.map(element => {
element.remove()
});
const subButton = <SubmitButton />
subButton.remove()
}
}
countdownClock = async () => {
const startingNum = this.state.ranNum * 20;
for(let i = startingNum; i >= 0; i--) {
await new Promise(resolve => {
timer = setTimeout(() => {
this.setState({
timer: i
})
resolve()
}, 1000)
});
}
}
generateInputs = () => {
const inputs = []
for(let i = 1; i <= this.state.ranNum; i++){
inputs.push(
<Input type='text' className='textInputs' />
)
}
return inputs;
}
generateSubButton = () => {
return (
<SubmitButton name='Submit Button' />
)
}
render() {
return (
<div>
<header>
<div className="headerContainer">
<div id="countdown">
<Countdown name='Countdown: ' countdown={this.state.timer} />
</div>
<div className="flex-item-header">
<StartButton
name={!this.state.restart ? 'Start Button' : 'Restart Button'}
onClick={() => this.handleClick()}
/>
</div>
<div>
<DisplayCount name='Word Count: ' count={this.state.ranNum} />
</div>
</div>
</header>
<section>
<div className="flex-main-item">
<ul>
{this.generateInputs()}
</ul>
{this.generateSubButton()}
</div>
</section>
</div>
)
}
}
class SubmitButton extends React.Component {
render(props) {
return (
<button name={this.props.name} onClick={this.props.onClick}>
</button>
)
}
}
class DisplayCount extends React.Component {
render() {
return (
<p>
{this.props.name}
<span>{this.props.count}</span>
</p>
)
}
}
class Countdown extends React.Component {
render(props) {
return (
<p>
{this.props.name}
<span>{this.props.countdown}</span>
</p>
)
}
}
You didn't post your code for generateRanNum but unless it's setting the state, then the first time you run this.countdownClock(), state.ranNum is still null from the initalization of the component. You need to set that state first before you start counting down.
Moral of story: Setting component state in React is async.
You set ranNum state in generateRanNum() then you read this.state.ranNum in countdownClock(), expecting it to be up-to-date. Well, it’s not because setState() is async.
I guess you’re aware of the concept of component lifecycle. Any call to setState() will cause a round of call to lifecycle hook methods. But these calls are buffered and batch applied for performance reason, thus async.
this.state is not updated until componentWillUpdate() of "next round" is called. Yet you access it prematurely in "this round". Thus the countdown lags behind one turn, cus it indeed reads the old value.
Quick fix to your code:
const ranNum = this.generateRanNun();
this.countdownClock(ranNum);
Cheers.

Add mp3 to React

I added an mp3 to the src folder in a project bootrstrapped with Create React App. I added a component for the audio file, Audio.js, which I'd like to play conditionally based on whether a prop playAlarm is true or not.
The parent App.js passes the prop to child Timer.js, which renders Audio.js.
The Audio.js file is giving me a 'React' is defined but never used error, and I'm not sure why.
Audio.js:
import React, { Component } from 'react';
class Audio extends Component {
constructor() {
super();
this.url = "./chime.mp3";
this.audio = new Audio(this.url);
}
render() {
return (
this.audio
);
}
}
export default Audio;
In Timer.js, Audio is rendered like this: {props.playAlarm ? <Audio /> : null}
When I tested playing the audio, when playAlarm is set to true I get Uncaught RangeError: Maximum call stack size exceeded at the line with super() in Audio.js.
App.js:
import React, { Component } from 'react';
import Timer from './Timer';
class App extends Component {
// ES6 class property/class field syntax allows you to remove constructor when just being used to initialize state
state = {
sessionDuration: 5, // TODO: change back to 1500 when testing done
breakDuration: 3, // TODO: change back to 300 when testing done
sessionTimeRemaining: 5, // TODO: change back to 1500 when testing done
breakTimeRemaining: 3, // TODO: change back to 300 when testing done
isSession: true,
timerOn: false,
sessionNumber: 0,
playAlarm: false
}
// Using property initializer syntax to avoid need to bind, since arrow functions don't create their own this context and use value of enclosing context instead. transform-class-properties Babel plugin necessary to use this syntax (included in Create React App). Refer to https://itnext.io/property-initializers-what-why-and-how-to-use-it-5615210474a3 for more details
// DURATION CHANGES
decreaseBreakDuration = () => {
// Conditional statement prevents decrease when break is at 1 minute
if (this.state.breakDuration === 60) {
return undefined;
} else {
this.setState({
breakDuration: this.state.breakDuration - 60
});
}
}
increaseBreakDuration = () => {
this.setState({
breakDuration: this.state.breakDuration + 60
});
}
decreaseSessionDuration = () => {
// Conditional statement prevents decrease when session is at 5 minutes
if (this.state.sessionDuration === 300) {
return undefined;
} else {
this.setState({
sessionDuration: this.state.sessionDuration - 60,
sessionTimeRemaining: this.state.sessionTimeRemaining - 60
});
}
}
increaseSessionDuration = () => {
this.setState({
sessionDuration: this.state.sessionDuration + 60,
sessionTimeRemaining: this.state.sessionTimeRemaining + 60
});
}
manageBreak = () => {
this.setState({
playAlarm: false
});
this.time = setInterval(() => {
this.setState({
breakTimeRemaining: this.state.breakTimeRemaining - 1
});
if (this.state.breakTimeRemaining === 0) {
this.handleBreakComplete();
}
}, 1000);
}
manageSession = () => {
this.setState({
playAlarm: false
});
// Every 1,000 ms (1 second), subtract 1 (a single second) from displayed sessionTimeRemaining. Assigned to this.time (scoped to entire class) in order to pass it to clearInterval() when pause button is clicked
this.time = setInterval(() => {
this.setState({
sessionTimeRemaining: this.state.sessionTimeRemaining - 1
});
if (this.state.sessionTimeRemaining === 0) {
this.handleSessionComplete();
}
}, 1000);
}
handleSessionComplete = () => {
clearInterval(this.time);
this.setState({
playAlarm: true,
sessionNumber: this.state.sessionNumber + 1
})
if (this.state.sessionNumber === 4) {
this.handlePomodoroCycleDone();
} else {
this.setState({
timerOn: false,
sessionTimeRemaining: this.state.sessionDuration,
breakTimeRemaining: this.state.breakDuration,
isSession: !this.state.isSession,
});
}
}
handlePomodoroCycleDone = () => {
// TODO: Display message in modal
console.log('Great work! You finished a pomodoro cycle (four sessions). Time to relax.')
// Change back to default values
this.setState({
isSession: true,
timerOn: false,
sessionDuration: 5, // TODO: change back to 1500
breakDuration: 3, // TODO: change back to 300 when testing done
sessionTimeRemaining: 5, // TODO: change back to 1500
});
}
handleBreakComplete = () => {
clearInterval(this.time);
this.setState({
timerOn: false,
sessionTimeRemaining: this.state.sessionDuration,
breakTimeRemaining: this.state.breakDuration,
isSession: !this.state.isSession,
playAlarm: true
});
}
// PLAY, PAUSE, RESTART BUTTONS
startTimer = () => {
this.setState({
timerOn: true,
});
if (this.state.isSession) {
this.manageSession();
} else {
this.manageBreak();
}
}
pauseTimer = () => {
// Stops setInterval's calling its (setState) callback every 1000 ms
clearInterval(this.time);
this.setState({
timerOn: false
});
}
resetTimer = () => {
// Stops setInterval's calling its (setState) callback every 1000 ms
// TODO: Display 4 unchecked circle icons again
clearInterval(this.time);
this.setState({
timerOn: false,
sessionDuration: 5, // TODO: change back to 1500
breakDuration: 3, // TODO: change back to 300 when testing done
sessionTimeRemaining: 5, // TODO: change back to 1500
breakTimeRemaining: 3, // TODO: change back to 300 when testing done
sessionNumber: 0
});
}
render() {
return (
<Timer
breakDuration={this.state.breakDuration}
sessionDuration={this.state.sessionDuration}
decreaseBreakDuration={this.decreaseBreakDuration}
increaseBreakDuration={this.increaseBreakDuration}
decreaseSessionDuration={this.decreaseSessionDuration}
increaseSessionDuration={this.increaseSessionDuration}
sessionTimeRemaining={this.state.sessionTimeRemaining}
breakTimeRemaining={this.state.breakTimeRemaining}
timerOn={this.state.timerOn}
sessionNumber={this.state.sessionNumber}
isSession={this.state.isSession}
startTimer={this.startTimer}
pauseTimer={this.pauseTimer}
resetTimer={this.resetTimer}
playAlarm={this.state.playAlarm}
/>
);
};
}
export default App;
Also here's Timer.js:
import Audio from './Audio';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faPlay } from '#fortawesome/free-solid-svg-icons';
import { faPause } from '#fortawesome/free-solid-svg-icons';
import { faUndo } from '#fortawesome/free-solid-svg-icons';
import React from 'react';
import PomodoroIcons from './PomodoroIcons';
import DurationControls from './DurationControls';
const TimeFormat = require('hh-mm-ss');
const Timer = props => (
<div className="timer">
<DurationControls
breakDuration={props.breakDuration}
sessionDuration={props.sessionDuration}
increaseBreakDuration={props.increaseBreakDuration}
decreaseBreakDuration={props.decreaseBreakDuration}
increaseSessionDuration={props.increaseSessionDuration}
decreaseSessionDuration={props.decreaseSessionDuration}
/>
{/* TIME REMAINING */}
<p className="time-remaining">
{props.isSession ? TimeFormat.fromS(props.sessionTimeRemaining) : TimeFormat.fromS(props.breakTimeRemaining)}
</p>
{/* PLAY, PAUSE, RESTART BUTTONS */}
<div className="bottom-btns">
<div className={props.timerOn ? 'hidden' : ''}>
<FontAwesomeIcon
role="button"
onClick={props.startTimer}
icon={faPlay}
className="btn bottom-btn"
size="4x"
/>
</div>
<div className={props.timerOn === false ? 'hidden' : ''}>
<FontAwesomeIcon
role="button"
onClick={props.pauseTimer}
icon={faPause}
className="btn bottom-btn"
size="4x"
/>
</div>
<FontAwesomeIcon
role="button"
onClick={props.resetTimer}
icon={faUndo}
className="btn bottom-btn"
size="4x"
/>
</div> {/* End bottom-btns */}
<PomodoroIcons sessionNumber={props.sessionNumber} />
{props.playAlarm ? <Audio /> : null}
</div>
);
export default Timer;
i dont follow everything going on here.. but at a glance this is an issue:
class Audio extends Component {
constructor() {
super();
this.url = "./chime.mp3";
this.audio = new Audio(this.url);
}
render() {
return (
this.audio
);
}
}
the call stack exceeded error is because you're entering into an infinite loop. You instantiate Audio inside of Audio which will make another Audio object and so on into infinity.

React function component setTimeout - Multiple render calls and rerenders (recommended approach) (fires multiple times)

I have a Notification component that should close itself after a few seconds and call the onClose prop:
function Notification(props) {
console.log("Notification function component called");
setTimeout(() => {
props.onClose();
}, 4000);
return (
<div>
{props.children}
<button onClick={props.onClose}>Close</button>
</div>
);
}
In my App, I have a state that holds notifications object and I map through them.
class App extends React.Component {
constructor() {
super();
this.pushNotification = this.pushNotification.bind(this);
}
state = {
notifications: {}
};
pushNotification() {
const id = uuid();
const newNotifications = { ...this.state.notifications };
const date = new Date();
newNotifications[id] = {
id,
date: JSON.stringify(date)
};
this.setState({
notifications: newNotifications
});
}
removeNotification(id) {
console.log("removeNotification");
const newNotifications = { ...this.state.notifications };
delete newNotifications[id];
this.setState({
notifications: newNotifications
});
}
render() {
return (
<div className="App">
<button onClick={this.pushNotification}>Push notification</button>
{Object.keys(this.state.notifications).map(
(notificationIndexKey, index) => {
return (
<Notification
originalKey={JSON.stringify(index)}
key={notificationIndexKey}
onClose={() => {
console.log("Notfication fired on close");
this.removeNotification(notificationIndexKey);
}}
>
Notification{" "}
{this.state.notifications[notificationIndexKey].date}
</Notification>
);
}
)}
</div>
);
}
}
I've noticed that if I push multiple notifications in my state, the setTimout is initialized multiple times (which makes sense since render it's called every time the state is updated)
My question is, how would you recommend optimizing this so that the timeout to be invoked only once.
One method that I've tried is to create an array with items that I've removed and check before I call the prop.
Sandbox here: https://codesandbox.io/s/6y3my2y2jr
You should apply that side-effect when the component has mounted.
Currently your code will do this on render.
The render function can be called multiple times.
This code should reflect the correct changes.
class Notification extends React.Component {
componentDidMount() {
setTimeout(this.props.onClose, 4000);
}
render() {
return (
<div>
{props.children}
<button onClick={props.onClose}>Close</button>
</div>
);
}
}
You can do this by keeping a class property say notificationTimer initially set to null and can modify your Notification functions as:
function Notification(props) {
console.log("Notification function component called");
if (!this.notificationTimer)
this.notificationTimer = setTimeout(() => {
props.onClose();
}, 4000);
}
return (
<div>
{props.children}
<button onClick={props.onClose}>Close</button>
</div>
);
}
And in your close function you can do something like this:
onClose() {
// Your code.
if (this.notificationTimer) {
clearTimeout(this.notificationTimer);
this.notificationTimer = null;
}
}
This will not let you create multiple timers.

React reusing a component with different state

I'm pretty new to React and trying to write my first app to get a better understanding.
What I'm trying to build is a simple time tracking tool where the user can start and stop a work timer.
Here you can see the design I came up with:
If the user clicks on the "start" button the working time Timer component should update every second. If the user clicks then on the "take a break" button the timer should stop and instead the break time Timer component should start ticking.
I would like to reuse the Timer component for both working and break timer and just set different states.
I already managed to do this but I don't know if this is a nice way or if this can be improved and make it more generic?
My Tracker component looks like this:
class Tracker extends Component {
constructor(props) {
super(props);
this.state = {
workTime: 0,
breakTime: 0,
isRunning: false,
timerType: 'workTimer'
}
}
startTimer(type) {
this.setState({
isRunning: true,
timerType: type
});
this.timerInterval = setInterval(() => {
this.updateTimer()
}, 1000);
}
stopTimer() {
this.setState({
isRunning: false
});
clearInterval(this.timerInterval);
}
toggleBreak(type) {
this.setState({
timerType: type
});
if (!this.state.isRunning && this.state.timerType === 'breakTimer') {
this.startTimer('breakTimer');
} else if (this.state.isRunning && this.state.timerType === 'breakTimer') {
this.stopTimer();
this.startTimer('workTimer');
} else {
this.stopTimer();
this.startTimer('breakTimer');
}
}
updateTimer() {
let state = null;
if (this.state.timerType === 'workTimer') {
state = {
workTime: this.state.workTime + 1000
};
} else {
state = {
breakTime: this.state.breakTime + 1000
};
}
this.setState(state);
}
render() {
return (
<div className="tracker">
<Timer time={ this.state.workTime }/>
<Timer time={ this.state.breakTime }/>
<TimerControls
isRunning={ this.state.isRunning }
start={ () => this.startTimer('workTimer') }
stop={ () => this.stopTimer() }
toggleBreak={ () => this.toggleBreak('breakTimer') }
/>
</div>
);
}
}
Controls component:
class TimerControls extends Component {
constructor(props) {
super(props);
}
render() {
const {isRunning, start, stop, toggleBreak} = this.props;
return (
<div className="tracker__control">
<button onClick={ start } disabled={ isRunning }>Start</button>
<button onClick={ toggleBreak }>Break</button>
<button onClick={ stop } disabled={ !isRunning }>Stop</button>
</div>
);
}
}
Timer component:
class Timer extends Component {
constructor(props) {
super(props);
}
render() {
const { time } = this.props;
return (
<div className="tracker__timer">{ timeFormat(time) }</div>
);
}
}
Is there a way to get rid of the timerType conditions?

Which React Lifecycle Methods for controlling state of buttons (enabled/disabled)?

I have SearchBar Component like so:
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
page: 1,
prevButton: false,
nextButton: true,
};
And buttons like so:
<div>
<button
className="btn btn-secondary"
onClick={this.handlePrev}
disabled={!this.state.prevButton}
>
Prev Page
</button>
<span className="SearchBar-page-numbers">{this.state.page}</span>
<button
className="btn btn-secondary"
onClick={this.handleNext}
disabled={!this.state.nextButton}
>
Next Page
</button>
</div>
Now I want to add code that for every component update will check which page on the user is.
-
So if the user is on page number one (this.state.page === 1) this.state.prevButton should be always false, but for every other page this.state.prevButton should be always true.
this.state.nextButton should be false only when this.state.page === 10
I need navigation between page 1 to 10 only.
-
Which React Lifecycle Methods would be sufficient for that functionality?
I tried something like that but it is no good, it is unclear, messy and doesn't work...
componentDidUpdate(prevProps) {
if (this.props !== prevProps) {
if (this.state.page === 1) {
this.setState({ prevButton: false });
}
if (this.state.page !== 1) {
this.setState({ prevButton: true });
}
}
}
UPDATE:
If you see better way of doing this, please share you thoughts!
Full code of that component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
wakeUpHerokuServerFromSleep,
fetchRecipesAndPage,
loadRecipes,
showClickedInfo,
addLocalStorageToFavoritesList,
} from '../../actions/';
import './style.css';
import ButtonSearch from '../../components/ButtonSearch';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
page: 1,
prevButton: false,
nextButton: true,
};
this.handlePrev = this.handlePrev.bind(this);
this.handleNext = this.handleNext.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.props.wakeUpHerokuServerFromSleep();
const localStorageData = JSON.parse(
localStorage.getItem('lastSavedFavourites')
);
if (localStorageData) {
this.props.addLocalStorageToFavoritesList(localStorageData);
}
}
componentDidUpdate(prevProps, prevState) {
if (this.props !== prevProps) {
this.setState({ term: this.props.showClickedInfoFromStore });
this.checker(this.props);
}
const { page } = this.state;
if (prevState.page !== page) {
this.setState({ prevButton: page !== 1 });
}
}
// If some ingredient was manually selected
// go to page 1 of that ingredient
checker(properties) {
if (properties.manualSelectionFromStore) {
this.setState({ page: 1 });
}
}
// If input was changed go to page 1
handleInputChange(event) {
this.setState({ page: 1 });
this.setState({ term: event.target.value });
}
// After submit, go to page 1 and fetch data
handleSubmit(event) {
this.setState({ page: 1 });
if (this.state.term === '') {
event.preventDefault();
this.props.showClickedInfo('');
} else {
event.preventDefault();
this.props.fetchRecipesAndPage(this.state.term, 1);
this.props.showClickedInfo(this.state.term);
}
}
handlePrev() {
let newPage = this.state.page - 1;
if (newPage <= 0) {
newPage = 1;
}
this.setState({ page: newPage });
this.props.loadRecipes(newPage);
}
handleNext() {
let newPage = this.state.page + 1;
if (newPage >= 10) {
newPage = 10;
}
this.setState({ page: newPage });
this.props.loadRecipes(newPage);
}
buttonsView() {
// Show navigation buttons (prev, next):
// If there is an error coming from server
// OR
// If current search isn't null AND app has found some data and successfully fetched it
if (
this.props.error ||
(this.props.currentSearchFromStore !== null &&
this.props.checkIfSomeDataWasFound)
) {
return (
<div>
<button
className="btn btn-secondary"
onClick={this.handlePrev}
disabled={!this.state.prevButton}
>
Prev Page
</button>
<span className="SearchBar-page-numbers">{this.state.page}</span>
<button
className="btn btn-secondary"
onClick={this.handleNext}
disabled={!this.state.nextButton}
>
Next Page
</button>
</div>
);
}
// Esle return just <div />
return <div />;
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit} className="SearchBar-input-group">
<input
className="form-control"
placeholder={this.props.showClickedInfoFromStore}
value={this.state.term}
onChange={this.handleInputChange}
/>
<ButtonSearch className="btn btn-secondary submit">
Search
</ButtonSearch>
</form>
<div className="SearchBar-pagination-buttonsView">
{this.buttonsView()}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
error: state.error,
currentSearchFromStore: state.currentSearchTerm,
checkIfSomeDataWasFound: state.checkRecipesData,
showClickedInfoFromStore: state.showClickedInfo,
manualSelectionFromStore: state.manualSelection,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
wakeUpHerokuServerFromSleep,
fetchRecipesAndPage,
loadRecipes,
showClickedInfo,
addLocalStorageToFavoritesList,
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
Alternately, you could just disable the buttons based on page as follows:
const { page } = this.state;
<div>
<button
className="btn btn-secondary"
onClick={this.handlePrev}
disabled={page === 1}
>
Prev Page
</button>
<span className="SearchBar-page-numbers">{this.state.page}</span>
<button
className="btn btn-secondary"
onClick={this.handleNext}
disabled={page === x}
>
Next Page
</button>
</div>
I'm not sure how you are disabling the next button, but subsitute x above with the number of pages.
Regarding your 'which lifecycle method' question, I would suggest using componentWillReceiveProps if you're checking whether something should re-render based on props changes. You'd compare this.props to nextProps (pass in nextProps as the argument to componentWillReceiveProps) in that method, and perform an action based on that.
However, if you're just trying to determine whether to enable/disable buttons, you could use the following in the 'disabled' property of the buttons.
i.e. for 'previous' button
disabled={this.state.page === 1}
and for 'next' button
disabled={this.state.page === 10}

Categories

Resources