Change content in div based on logic using React - reactjs

I'm failing to understand how to update the content inside my render function based on logic from inside of a function. Do I have to return a whole new render function in order to do so? If so, that seems counter intuitive with React's framework of state & props and such...
Here's what I've tried:
tick() {
this.setState(prevState => ({
minutes: prevState.seconds + 1,
}));
if(this.state.minutes > this.state.targetGoal){
console.log("NONONONONO");
return (<div>SOMETHING NEW</div>); //update content inside render()
}
}
async componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div style={divStyle}>
<div>Your Second Count: {this.state.seconds}</div>
<habitlab-logo-v2></habitlab-logo-v2>
<br/>
<close-tab-button></close-tab-button>
</div>
);
}
}

There are a few issues.
As #Jayce444 has pointed out, you need to change a state to trigger render to re-render.
So create a new flag (say isOvertime) to trigger the render to fire.
tick() {
this.setState(
prevState => ({
seconds: prevState.seconds + 1
}),
() => {
if (this.state.seconds > this.state.targetGoal) {
console.log("NONONONONO");
// return <div>SOMETHING NEW</div>; //update content inside render()
this.setState({ isOvertime: true });
}
}
);
}
And in the render, you show a component depending on the isOvertime.
render() {
const { isOvertime, seconds } = this.state;
return (
<div>
{isOvertime ? (
<div>Time Over Man!</div>
) : (
<div>Your Second Count: {seconds}</div>
)}
<input
type="number"
value={this.state.targetGoal}
onChange={e => this.setState({ targetGoal: e.target.value })}
/>
</div>
);
}
Here is the full source. (demo availabe on CodeSandBox).
Output
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
interval: 0,
seconds: 0,
targetGoal: 4,
isOvertime: false
};
tick() {
this.setState(
prevState => ({
seconds: prevState.seconds + 1
}),
() => {
if (this.state.seconds > this.state.targetGoal) {
console.log("NONONONONO");
// return <div>SOMETHING NEW</div>; //update content inside render()
this.setState({ isOvertime: true });
}
}
);
}
componentDidMount() {
const interval = setInterval(() => this.tick(), 1000);
this.setState({ interval });
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { isOvertime, seconds } = this.state;
return (
<div>
{isOvertime ? (
<div>Time Over Man!</div>
) : (
<div>Your Second Count: {seconds}</div>
)}
<input
type="number"
value={this.state.targetGoal}
onChange={e => this.setState({ targetGoal: e.target.value })}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

function component vs function - the sense of using function components

My problem is I do not really understand if using function components instead function is good idea in below example:
first program without function components
class App extends React.Component {
state = {
check: false,
isFormSubmitted: false
}
handleChangeChecked = () => {
this.setState({
check: !this.state.check,
isFormSubmitted: false
})
return (true)
}
displayMsg = () => {
if (this.state.isFormSubmitted == true) {
if (this.state.check == true)
return (<p>You are allowed to watch this film!</p>)
else return (<p>You are not allowed to watch this film.</p>)
} else return (null)
}
handleFormSubmit = (e) => {
e.preventDefault()
this.setState({
isFormSubmitted: true
})
}
render() {
return (
<React.Fragment>
<h1>Film</h1>
<form onSubmit={this.handleFormSubmit}>
<input type="checkbox" onChange={this.handleChangeChecked} checked={this.state.check} />
<label>I have got 16 years old</label>
<button>Buy ticket</button>
</form>
{this.displayMsg()}
</React.Fragment>
)
}
}
ReactDOM.render(< App />, document.getElementById('root'));
second program with function components:
const PositiveMessage = () => <p>Mozesz obejrzeć film, zapraszam</p>;
const NegativeMessage = () => <p>Nie możesz obejrzeć tego filmu !</p>;
class TicketShop extends React.Component {
state = {
isConfirmed: false,
isFormSubmitted: false
}
handleCheckboxChange = () => {
this.setState({
isConfirmed: !this.state.isConfirmed,
isFormSubmitted: false
})
}
displayMessage = () => {
if (this.state.isFormSubmitted) {
if (this.state.isConfirmed) { return <PositiveMessage /> }
else { return <NegativeMessage /> }
} else { return null }
}
handleFormSubmit = (e) => {
e.preventDefault()
if (!this.state.isFormSubmitted) {
this.setState({
isFormSubmitted: !this.state.isFormSubmitted
})
}
}
render() {
return (
<>
<h1>Kup bilet na horror roku !</h1>
<form onSubmit={this.handleFormSubmit}>
<input type="checkbox" id="age" onChange={this.handleCheckboxChange} checked={this.state.isConfirmed} />
<label htmlFor="age">Mam conajmniej 16 lat</label>
<br />
<button type="submit">Kup bilet</button>
</form>
{this.displayMessage()}
</>
)
}
}
ReactDOM.render(<TicketShop />, document.getElementById('root'))
i made two programs with and without function components and i dont see the difference of working.
In user point of view both programs works without any difference.

React state not updating accordingly

I have a search component where the search result updates according to the search input, where if there is data returned from the API, it is rendered as a book grid, if there is no data, a message is displayed, and if the search input is empty, nothing is rendered.
My problem is that when query state updates the searchResult state does update but when I delete the search input so fast (make the search input empty), query becomes updates as an empty string but searchResult does not update according to it. What could be the problem?
Here is the code to the search component: (Note: I tried the componentDidUpdate() method and the setState() callback function but nothing worked)
import React, { Component } from "react";
// import "React Router" components
import { Link } from "react-router-dom";
// import custom components
import Book from "./Book";
// import required API
import * as BooksAPI from "../BooksAPI";
export default class BookSearch extends Component {
state = {
query: "",
searchResult: [],
};
handleInputChange = (query) => {
this.setState(
{
query: query.trim(),
},
() => {
if (query) {
console.log(query);
BooksAPI.search(query).then((books) => {
this.setState({ searchResult: books });
});
} else {
this.setState({ searchResult: [] });
}
}
);
};
// componentDidUpdate(currentProps, currentState) {
// if (currentState.query !== this.state.query && this.state.query) {
// BooksAPI.search(this.state.query).then((books) => {
// this.setState({ searchResult: books });
// });
// } else if (currentState.query !== this.state.query && !this.state.query) {
// this.setState({ searchResult: [] });
// }
// }
render() {
const { query, searchResult } = this.state;
const { updateBookShelves } = this.props;
return (
<div className="search-books">
<div className="search-books-bar">
<Link to="/" className="close-search">
Close
</Link>
<div className="search-books-input-wrapper">
<input
type="text"
placeholder="Search by title or author"
value={query}
onChange={(event) => this.handleInputChange(event.target.value)}
/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
{ searchResult.error ?
<p>No results matching your search</p>
: searchResult.map((book) => (
<Book
key={book.id}
book={book}
updateBookShelves={updateBookShelves}
/>
))
)
) )}
</ol>
</div>
</div>
);
}
}
I am not 100% sure about this solution since it is using setState inside callback of other setState but you can give it a try.
I think you can probably need to use setTimeout before calling api for data and before checking if query exist or not we can set timeout to null so it will not call unwanted api calls.
handleInputChange = query => {
this.setState(
{
query: query.trim()
},
() => {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
if (query) {
this.timeout = setTimeout(() => {
BooksAPI.search(query).then(books => {
this.setState({ searchResult: books });
});
}, 800);
} else {
this.setState({ searchResult: [] });
}
}
);
};

React: triggering method inside HOC component

What I want to do, is create a HOC that has a method that can be triggered by whatever Parent Component is using that HOC to wrap.
For this HOC, I'm trying to fade out the HOC and any components inside it:
HOC:
export function fadeOutWrapper(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
showElement: true,
removeElement: false,
};
}
_triggerFade = () => {
this._fadeOut(this.props.time).then(time => this._removeElement(time));
}
_fadeOut = time => {
let _this = this;
return new Promise((resolve, reject) => {
_this.setState({
showElement: false
});
setTimeout(() => {
resolve(time);
}, time);
});
};
_removeElement = time => {
let _this = this;
setTimeout(() => {
_this.setState({
removeElement: true
});
}, time + 500);
};
render() {
return this.state.removeElement ? null : (
<div
className={
this.state.showElement
? "cfd-container"
: "cfd-container cfd-fadeout"
}
>
<WrappedComponent {...this.props} />
</div>
);
}
};
}
How this component is being used in parent component:
import ComponentToBeFaded from '...';
import { fadeOutWrapper } from '...';
const WrappedComponent = fadeOutWrapper(ComponentToBeFaded);
class ParentComponent extends Component {
const...
super...
handleChildClick = () => {
// ? how to trigger the HOC _triggerFade method?
// WrappedComponent._triggerFade()
}
render() {
return (
<WrappedComponent time={1000} handleClick={this.handleChildClick} {...other props component needs} />
)
}
}
What I want to be able to do is call a method that is inside the HOC, can't seem to check for a change in props inside the HOC... only inside the HOC's render()
Need to keep writing more to meet the submission quota. Any thoughts on how to do this is appreciated. Hope your day is going well!
You don't need showElement in local state of the wrapped component because it's not controlled by that component. Pass it as props and use componentDidUpdate to start fading out.
const { Component, useState, useCallback } = React;
const Button = ({ onClick }) => (
<button onClick={onClick}>Remove</button>
);
function App() {
const [show, setShow] = useState(true);
const onClick = useCallback(() => setShow(s => !s), []);
return (
<WrappedButton
time={1000}
onClick={onClick}
showElement={show}
/>
);
}
function fadeOutWrapper(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
removeElement: false,
fadeout: false,
};
}
componentDidUpdate(prevProps) {
if (
this.props.showElement !== prevProps.showElement &&
!this.props.showElement
) {
this._triggerFade();
}
}
_triggerFade = () => {
this._fadeOut(this.props.time).then(() =>
this._removeElement()
);
};
_fadeOut = time => {
this.setState({ fadeout: true });
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, time);
});
};
_removeElement = time => {
this.setState({
removeElement: true,
});
};
render() {
return this.state.removeElement ? null : (
<div>
{JSON.stringify(this.state)}
<WrappedComponent {...this.props} />
</div>
);
}
};
}
const WrappedButton = fadeOutWrapper(Button);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to setState to answer from APi and use map

Im trying to create recipes searcher. In App.js I receive query from search input from another component and I want to setState to answer from APi. Console.log from callback in setState shows updated state but the state is not updated. I need setState updaed so I can use map on it and display list of recipes in render. It gives me error map is not a function because this.state.recipesList is still empty. Anyone can help me ?
class App extends Component {
state = {
query: "",
recipesList: []
};
getQuery = query => {
const key = "2889f0d3f51281eea62fa6726e16991e";
const URL = `https://www.food2fork.com/api/search?key=${key}&q=${query}`;
fetch(URL)
.then(res => res.json())
.then(res => {
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
});
console.log(this.state.recipesList);
};
render() {
const test = this.state.recipesList.map(item => {
return (
<div className="recispesList">
<h1>{item.title}</h1>
</div>
);
});
return (
<div className="App">
<Search query={this.getQuery} />
<div className="contentWrapper">{}</div>
</div>
);
}
}
Search component:
class Search extends Component {
state = {
searchValue: ""
};
handleChange = val => {
let searchValue = val.target.value;
this.setState({
searchValue
});
};
handleSubmit = e => {
e.preventDefault();
this.setState({
searchValue: ""
});
this.props.query(this.state.searchValue);
};
render() {
return (
<div className="searchWrapper">
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.searchValue} />
<button />
</form>
</div>
);
}
}
export default Search;
It seems that instead of directly assigning the whole response to recipesList:
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
you need to get recipes array first via res.recipes:
this.setState(
{
recipesList: res.recipes
},
() => {
console.log(this.state.recipesList);
}
);

React passing props to components

I am trying to pass in props to a component which works while using componentWillReceiveProps, but once the counter is done it calls clearInterval(this.intervalId);However once I change the input again the counter does not get initiated again. How can i pass the updated props back to the component?
Component code;
class Stopwatch extends Component {
constructor(props) {
super(props);
this.state = {
currentCount: this.props.counter,
hours: 0,
minutes: 0,
seconds: 0
}
}
componentWillMount() {
this.timer(this.props.counter);
}
timer() {
this.setState({
currentCount: this.state.currentCount - 1
})
const seconds = Math.floor(this.state.currentCount % 60);
const minutes = Math.floor((this.state.currentCount/60) % 60);
const hours = Math.floor((this.state.currentCount/3600) % 3600);
this.setState({hours, minutes, seconds});
if (this.state.currentCount < 1) {
clearInterval(this.intervalId);
}
}
componentDidMount() {
this.intervalId = setInterval(this.timer.bind(this), 1000);
}
leading0(num) {
return num < 10 ? '0' + num : num;
}
componentWillReceiveProps(nextProps){
if(nextProps.counter !== this.props.counter){
this.setState ({currentCount: nextProps.counter})
}
}
render() {
return (
<div>
<div>Hours {this.leading0(this.state.hours)}</div>
<div>Minutes {this.leading0(this.state.minutes)}</div>
<div>Seconds {this.leading0(this.state.seconds)}</div>
</div>
)
Main Code;
class App extends Component {
constructor(props) {
super(props);
this.state = {
deadline: 'December 25, 2018',
newDeadline: '',
counter: 75,
newCounter: ''
};
}
changeDeadline() {
this.setState({deadline: this.state.newDeadline});
}
changeNumber(e) {
this.setState({counter: this.state.newCounter});
}
render() {
return (
<div className='App'>
<div className='App-title'>Countdown to {this.state.deadline}</div>
<Clock
deadline={this.state.deadline}
/>
<Form inline>
<FormControl
className="Deadline-input"
placeholder='New Date'
onChange={event => this.setState({newDeadline: event.target.value})}
/>
<Button onClick={() => this.changeDeadline()}>Submit</Button>
</Form>
<div>Stopwatch From { this.state.counter } Seconds</div>
<Stopwatch
counter={this.state.counter}
/>
<Form inline>
<FormControl
className="Deadline-input"
placeholder='New Number'
onChange={event => this.setState({newCounter: event.target.value})}
/>
<Button onClick={() => this.changeNumber()}>Submit</Button>
</Form>
</div>
)
}
Thanks in Advance
componentDidMount function calls once, if you want to reset counter on props change, you should do it in componentWillReceiveProps function
class Stopwatch extends Component {
// ...
resetInterval() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
this.intervalId = setInterval(this.timer.bind(this), 1000);
}
componentWillReceiveProps(nextProps){
if(nextProps.counter !== this.props.counter){
this.setState ({currentCount: nextProps.counter})
// reset interval
this.resetInterval()
}
}
//...
}

Resources