I need to update state in render() function in reactjs. How? - reactjs

What are the solutions for this problem?
<Form.Field>
<label> </label>
<MockMutation mutation={DO_LOGIN}>
{(doLogin, { loading, error, data }) => {
if (!loading && data.loggedInState == "LOGGED_IN") {
// this.setState({goodLogin: true})
// I need to update state here to redirect to other page
// How can I do it with out the annoying warning???
}
return (
<Button
primary
className="login-btn full-width"
disabled={loading}
onClick={e => {
console.log("on click btn clicked");
e.preventDefault();
doLogin({
variables: {
employeeId: this.state.employeeId,
password: this.state.password
}
});
}}
>
<span style={loading ? { display: "none" } : {}}>Login</span>
<span style={loading ? {} : { display: "none" }}>Loading...</span>
</Button>
);
}}
</MockMutation>
</Form.Field>

If you are using react-router.v4 you can use Redirect component to do make a redirect.
if (!loading && data.loggedInState == "LOGGED_IN") {
// this.setState({goodLogin: true})
// I need to update state here to redirect to other page
// How can I do it with out the annoying warning???
return <Redirect to="/some/path" />
}
If you don't use react-router-4 then it is fairly easy to implement such component anyway:
class Redirect extends React.Component {
componentDidMount(){
const { history, to } = this.props;
history.push(to);
}
render() {
return null
}
}
export default withRouter(Redirect);

Related

react-bootstrap spinner does not render

Attempting to use react-boostrap Spinner in a React component. A typical scenario where the component displays a Spinner until response data is received from a backend API call.
There is a flag - isDataFetched which is initially false and gets set in componentDidMount() only when the response data is received. Conditional rendering allows disabling the data table until response is received and instead tried displaying Spinner. However, the spinner simply does not show.
Bumped react-bootstrap version to v1.14.0 after realizing that Spinner was introduced only later. The issue did not resolve though.
import React, { Component } from 'react';
import { Spinner } from 'react-bootstrap';
import DataService from '../service/DataService';
class ProjectListComponent extends Component {
constructor(props) {
super(props);
this.state = {
tech: 'cb',
formState: {
isDataFetched: false
}
}
this.refreshProjects = this.refreshProjects.bind(this);
}
componentDidMount() {
this.refreshProjects();
}
refreshProjects(tech) {
if (!tech) {
tech = 'cb';
}
DataService.findProjectsByTech(tech)
.then(
response => {
this.setState({
formState: {
...this.state.formState,
isDataFetched: true
},
tech: tech,
projects: response.data
});
}
).catch(exp => {
//some exception handling
});
}
render() {
return (
<div className="container">
<h4>Projects List</h4>
{this.state.formState.isDataFetched && this.state.formState.isDataFetched === false && (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)}
{this.state.formState.isDataFetched && this.state.formState.isDataFetched === true && (
<p>Data Table</p>
)}
</div>
)
}
}
export default ProjectListComponent
Theres a lot of errors in your code, your only setting the flag after the response and this condition this.state.formState.isDataFetched && this.state.formState.isDataFetched === false && can never pass
try this:
class ProjectListComponent extends Component {
constructor(props) {
super(props);
this.state = {
tech: 'cb',
formState: {
isDataFetched: false
}
}
this.refreshProjects = this.refreshProjects.bind(this);
}
componentDidMount() {
this.refreshProjects();
}
refreshProjects(tech) {
if (!tech) {
tech = 'cb';
}
this.setState({
formState: {
...this.state.formState,
isDataFetched: true
})
DataService.findProjectsByTech(tech)
.then(
response => {
this.setState({
tech: tech,
projects: response.data
});
}
).catch(exp => {
//some exception handling
});
this.setState({
formState: {
...this.state.formState,
isDataFetched: false
})
}
render() {
return (
<div className="container">
<h4>Projects List</h4>
{this.state.formState.isDataFetched &&
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)}
{!this.state.formState.isDataFetched && this.state.projects && (
<p>Data Table</p>
)}
</div>
)
}
}
export default ProjectListComponent
ALSO, I recommend changing the flag to a more accurate name like isDataFatching
Fixing the logic in the conditional rendering part as pointed by #roy-b helped achieve the desired output.
{!this.state.formState.isDataFetched && (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)}
{this.state.formState.isDataFetched && (
<p>Data Table</p>
)}

React Component Doesn't Update After First Button Click

My code generates an input field that allows a user to enter a value to search for. Then when they click the Submit button, it causes displayMap to be true, so that when the MapDisplay component renders, it will trigger an API search via the Map component and return values that are then displayed on the map.
The problem is that this process only works once. When I click the button again, it does do something, I confirmed that it is getting the new value in the input box, but I can't seem to figure out how to get the map to be rendered again.
I've tried setting other variables in the this.setState to try to get it to know that it needs to render the component again, but I guess I'm missing something, because nothing works.
I'm fairly new to React, so any help you can offer would be greatly appreciated.
This is the MainSearchBar.js, where most of the work as described above is happening:
import Map from './newMap.js';
function MapDisplay(props) {
if (props.displayMap) {
return <Map toSearch = {props.searchTerm}></Map>;
} else {
return "";
}
}
class MainSearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
displayMap: false,
value: '',
searchTerm: '',
isOpened: false
};
//this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = () => {
this.setState({
displayMap: true,
isOpened: !this.state.isOpened,
searchTerm: this.state.value
});
console.log(this.state.value);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
<MapDisplay displayMap={displayMap} searchTerm={this.state.value} />
</div>
)
}
}
export default MainSearchBar;
This is where MainSearchBar is being called from
import Top20Box from '../components/getTop20Comp2.js';
import Header from '../components/Header.js';
import MainIntro from '../components/MainIntro.js';
import MainSearchBar from '../components/MainSearchBar.js';
import MainCTA from '../components/MainCTA.js';
import Footer from '../components/Footer.js';
export default class Home extends Component {
state = {
}
render () {
return (
<React.Fragment>
<Header>
</Header>
<MainIntro />
<MainSearchBar />
<div className="top20-text">
Top 20 trending hashtags
</div>
<Top20Box />
<MainCTA />
<Footer />
</React.Fragment>
)
}
}
And this is the Map component itself, in case you need it:
import React from 'react';
import ReactMapGL, {Marker, Popup} from 'react-map-gl';
import axios from 'axios';
//for the loading animation function
import FadeIn from "react-fade-in";
import Lottie from "react-lottie";
import * as loadingData from "../assets/loading.json";
var locationCoordinates = [];
var locationToSearch = "";
var returnedKeywordSearch = [];
var newArray = [];
const defaultOptions = {
loop: true,
autoplay: true,
animationData: loadingData.default,
rendererSettings: {
preserveAspectRatio: "xMidYMid slice"
}
};
export default class Map extends React.Component {
//sets components for the map, how big the box is and where the map is centered when it opens
state = {
viewport: {
width: "75vw",
height: "50vh",
latitude: 40.4168,
longitude: 3.7038,
zoom: .5
},
tweetSpots: null, //data from the API
selectedSpot: null,
done: undefined, //for loading function
};
async componentDidMount() {
//searches the api for the hashtag that the user entered
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
//is triggered when a marker on the map is hovered over
setSelectedSpot = object => {
this.setState({
selectedSpot: object
});
};
//creates markers that display on the map, using location latitude and longitude
loadMarkers = () => {
return this.state.tweetSpots.map((item,index) => {
return (
<Marker
key={index}
latitude={item.coordinates[1]}
longitude={item.coordinates[0]}
>
<img class="mapMarker"
onMouseOver={() => {
this.setSelectedSpot(item);
}}
src="/images/yellow7_dot.png" alt="" />
</Marker>
);
});
};
//closes popup when close is clicked
closePopup = () => {
this.setState({
selectedSpot: null
});
};
//renders map component and loading animation
render() {
return (
<div className="App">
<div className="map">
{!this.state.done ? (
<FadeIn>
<div class="d-flex justify-content-center align-items-center">
<Lottie options={defaultOptions} width={400} />
</div>
</FadeIn>
) : (
<ReactMapGL {...this.state.viewport} mapStyle="mapbox://styles/mapbox/outdoors-v11"
onViewportChange={(viewport => this.setState({viewport}))}
mapboxApiAccessToken="pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg">
{this.loadMarkers()}
{this.state.selectedSpot !== null ? (
<Popup
key={this.state.selectedSpot.id}
tipSize={5}
latitude={this.state.selectedSpot.coordinates[1]}
longitude={this.state.selectedSpot.coordinates[0]}
closeButton={true}
closeOnClick={false}
onClose={this.closePopup}
>
<div className="mapPopup">
<div className="header"> Tweet </div>
<div className="content">
{" "}
<p>
<b>Name:</b> {this.state.selectedSpot.name}
</p>
<p>
<b>Tweet:</b> {this.state.selectedSpot.text}</p>
<p>View Tweet in Twitter
</p>
</div>
</div>
</Popup>
) : null}
</ReactMapGL>
)}
</div>
</div>
);
}
}
Update: 4/28, per the answer I received, I update the render of the MainSearchBar.js to look like this:
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {this.searchTerm}></Map>}
</div>
)
}
When you click the button again, the state of MainSearchBar.js updates but the functional component MapDisplay does not and thus the Map does not update as well.
There are many ways to resolve this. Looking at the code, it looks like MapDisplay doesn't do much so you can consider replacing it with conditional rendering.
MainSearchBar.js
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {props.searchTerm}></Map>}
</div>
)
}
Then in your Map component, add a componentDidUpdate lifecycle method to detect updates to the prop which does the same thing as componentDidMount when the props are updated.
async componentDidMount(prevProps) {
if (props.toSearch != prevProps.toSearch) {
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
}
#wxker Thanks for all your help! You certainly got me pointed in the right direction.
I changed render in MainSearchBar.js back to what it was originally.
And I added a ComponentDidUpdate to the Map component, as follows below:
async componentDidUpdate(prevProps) {
//searches the api for the hashtag that the user entered
if (this.props.toSearch !== prevProps.toSearch) {
and then the rest was the same as the original componentDidMount.

React API call in componentDidMount, and componentWillReceiveProps (order confusion)

I'm fairly new to React and Redux. I'm having this issue where the ratings sometimes don't show up when I refresh the page (please see screenshot). I think it's because sometimes the user from Redux comes into componentWillReceiveProps before loadTeamsData executes, but I don't know why that would make ratings not show up. (Also, I feel like my code is crap... Any critic is appreciated!)
Home.js
export class Home extends React.Component {
state = {
upVote: null,
downVote: null,
clickedTeam: "",
teams: teams
};
// When component mounted, add in thumbUp & thumbDown properties to each team
componentDidMount() {
const { user } = this.props.auth;
console.log("didmount");
this.loadTeamsData();
// Stores user voting info to state when coming from a different page
if (user) {
if (!(Object.entries(user).length === 0)) {
console.log("user", user, user.upVote, user.downVote);
this.setState({
upVote: user.upVote,
downVote: user.downVote
});
}
}
}
// Loads teams thumbUp, thumbDown data to state
loadTeamsData = () => {
axios.get("/api/teams/").then(res => {
console.log("data", res.data);
this.setState({
teams: this.state.teams.map(team => {
res.data.map(vote => {
if (vote.id === team.id) {
team.thumbUp = vote.thumbUp;
team.thumbDown = vote.thumbDown;
}
return vote;
});
return team;
})
});
});
};
// When props from Redux come in, set the state
UNSAFE_componentWillReceiveProps(nextProps) {
const { user } = nextProps.auth;
if (user !== this.props.auth.user && user) {
console.log("willreceiveprops", `\n`, this.props.auth.user, user);
this.setState({
upVote: user.upVote,
downVote: user.downVote
});
}
}
// Handle click on thumbs
onClickHandler = (id, e) => {
const { alert } = this.props;
const up = e.target.classList.contains("up");
if (this.props.auth.isAuthenticated) {
if (up && this.state.upVote === "") {
if (id === this.state.downVote) {
alert.error("You cannot up vote and down vote the same team!");
} else {
this.props.update_up(id);
this.setState(prevState => {
return {
teams: prevState.teams.map(team => {
if (id === team.id) {
team.thumbUp = team.thumbUp + 1;
team.votedUpColor = { color: "#1E95E0" };
}
return team;
}),
clickedTeam: id,
upVote: id
};
});
alert.show(`You Up Voted ${id}`);
}
} else if (!up && this.state.downVote === "") {
if (id === this.state.upVote) {
alert.error("You cannot up vote and down vote the same team!");
} else {
this.props.update_down(id);
this.setState(prevState => {
return {
teams: prevState.teams.map(team => {
if (id === team.id) {
team.thumbDown = team.thumbDown + 1;
team.votedDownColor = { color: "#F8004C" };
}
return team;
}),
clickedTeam: id,
downVote: id
};
});
alert.show(`You Down Voted ${id}`);
}
} else {
alert.show("You have already voted.");
}
} else {
alert.show("Please log in first!");
this.props.history.push(`/login`);
}
};
// When user votes, update the db before updating the state
UNSAFE_componentWillUpdate(newProps, newState) {
newState.teams.map(team => {
if (team.id === newState.clickedTeam) {
axios.put(`/api/teams/${newState.clickedTeam}/`, {
id: team.id,
thumbUp: team.thumbUp,
thumbDown: team.thumbDown
});
}
});
}
render() {
// Welcome header message when user logs in
console.log("render", this.state.teams[0].thumbUp);
const { isAuthenticated, user } = this.props.auth;
const { upVote, downVote } = this.state;
const welcome_header = (
<div className="welcome-header">
<h4 style={{ textAlign: "left" }} className="welcome-header-line">
Welcome, {user && user.username}!
</h4>
<h4 style={{ textAlign: "left" }} className="welcome-header-line">
<span>
Your Vote:{" "}
<i className="far fa-thumbs-up up" style={{ color: "#1E95E0" }}></i>
<span style={{ textTransform: "capitalize" }}>{upVote}</span>
</span>{" "}
<span>
<i
className="far fa-thumbs-down down"
style={{ color: "#F8004C" }}
></i>
<span style={{ textTransform: "capitalize" }}>{downVote}</span>
</span>
</h4>
</div>
);
return (
<div className="home">
<div className="home-container">
{isAuthenticated && welcome_header}
<h2>Who Is Your NBA Champion This Year?</h2>
<Teams
upVote={this.state.upVote}
downVote={this.state.downVote}
teams={this.state.teams}
onClickHandler={this.onClickHandler}
/>
</div>
</div>
);
}
}
Home.propTypes = {
update_up: PropTypes.func.isRequired,
update_down: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps, { update_up, update_down })(
withAlert()(withRouter(Home))
);
When ratings show up
When ratings don't show up
I'm console logging this.state.teams[0] and this.state.teams[0].thumbUp in the render method.
Even during the first render, both thumbUp and thumbDown show in this.state.teams[0], but this.state.teams[0].thumbUp appears to be undefined.
I happened to fix the issue. The issue was actually in the Rating.js file, sorry that I didn't post that file cuz I thought the issue had to be in Home.js.
Before, in Rating.js file, I originally brought in user from redux which caused the issue (I believe it didn't make Rating re-render when in Home.js the axios get call happened after componentWillReceiveProps).
After, instead of bringing in user from redux, I passed user to Rating.js as a prop from Home.js.
Even though it works fine now, I still don't know what exactly the issue was... I'd much appreciate it if someone could enlighten me! Also, please critique my code (i.e. where I can improve)! THANK YOU!
Rating.js (Before)
import React from "react";
import "./Rating.css";
import PropTypes from "prop-types";
import { connect } from "react-redux";
const Rating = props => {
const { thumbUp, thumbDown, id, votedUpColor, votedDownColor } = props.team;
const { upVote, downVote, onClickHandler } = props;
const { user } = props.auth;
let thumbUpColor =
user && id === upVote ? { color: "#1E95E0" } : votedUpColor;
let thumbDownColor =
user && id === downVote ? { color: "#F8004C" } : votedDownColor;
console.log(id, thumbUp, thumbDown);
return (
<div className="rating" key={id}>
<button
className="thumb-up up"
style={thumbUpColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-up up"></i>
<span style={{ userSelect: "none" }} className="up">
{thumbUp}
</span>
</button>
<button
className="thumb-down down"
style={thumbDownColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-down down"></i>
<span style={{ userSelect: "none" }} className="down">
{thumbDown}
</span>
</button>
</div>
);
};
Rating.propTypes = {
team: PropTypes.object.isRequired,
onClickHandler: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(Rating);
Rating.js (After)
import React from "react";
import "./Rating.css";
import PropTypes from "prop-types";
// import { connect } from "react-redux";
const Rating = props => {
const { thumbUp, thumbDown, id, votedUpColor, votedDownColor } = props.team;
const { upVote, downVote, onClickHandler, user } = props;
// const { user } = props.auth;
let thumbUpColor =
user && id === upVote ? { color: "#1E95E0" } : votedUpColor;
let thumbDownColor =
user && id === downVote ? { color: "#F8004C" } : votedDownColor;
console.log(id, thumbUp, thumbDown);
return (
<div className="rating" key={id}>
<button
className="thumb-up up"
style={thumbUpColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-up up"></i>
<span style={{ userSelect: "none" }} className="up">
{thumbUp}
</span>
</button>
<button
className="thumb-down down"
style={thumbDownColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-down down"></i>
<span style={{ userSelect: "none" }} className="down">
{thumbDown}
</span>
</button>
</div>
);
};
Rating.propTypes = {
team: PropTypes.object.isRequired,
onClickHandler: PropTypes.func.isRequired
// auth: PropTypes.object.isRequired
};
// const mapStateToProps = state => ({
// auth: state.auth
// });
export default Rating;

hide and show external components in react

I am newbie in react and I have some trouble that I'd like to solve.
I would like to know how can I show and hide react components before and after to do a rest call.
I have the follow component:
class Loading {
render(){
return (
<div >
<Modal isOpen={true} centered >
<ModalHeader>Loading...</ModalHeader>
<ModalBody >
<div align='center' className="mt-2 mb-2">
<Spinner style={{ width: '4rem', height: '4rem' }} color="primary" />
</div>
</ModalBody>
</Modal>
</div>
)
}
}export default Loading;
And I would like to show this component in other module before to call a rest api and hide this component after the data come. The ideia is some like this:
class List extends Component {
constructor(props) {
super(props)
this.state = {
show : false
}
}
// HERE I WOULD LIKE TO SHOW THE LOADING COMPONENT
callRestApi = () => {
axiosAuth.get(url, getConfig())
.then(response => {
console.log(response.data)
this.setState({
eventos: response.data
})
}).catch(error => {
console.log(error);
return null
});
// HERE I WOULD LIKE TO HIDE THE LOADING COMPONENT
}
render() {
return(
<div>
<Button className="mr-2" color="primary" size="sm" onClick={this.callRestApi}>List All</Button>
</div>
How can I do it?
You can create state that dictates whether the loading spinner is visible or not. And append one last .then in the promise chain to modify it.
class List extends Component {
constructor(props) {
super(props)
this.state = {
show : false,
loaderVisible: true
}
}
// HERE I WOULD LIKE TO SHOW THE LOADING COMPONENT
callRestApi = () => {
axiosAuth.get(url, getConfig())
.then(response => {
console.log(response.data)
this.setState({
eventos: response.data
})
}).catch(error => {
console.log(error);
return null
}).then(() => {
this.setState({loaderVisible: false });
});
}
render() {
return(
<div>
{
this.state.loaderVisible? <Loading /> : ''
}
<Button className="mr-2" color="primary" size="sm" onClick={this.callRestApi}>List All</Button>
</div>
Then utilize ternary syntax on the spinner to determine visibility.
We use state to implement this. Here is the pseudo code.
class List extends Component {
state = { loading: false }
callRestApi = async () => {
this.setState({ loading: true });
await fetch(...);
this.setState({ loading: false });
}
render() {
<div>
{this.state.loading && <Loading />}
<button onClick={this.callRestApi}>List All</button>
</div>
}
}

conditional rendering of components based on state change

When a user enters a search item, if the data is available, then <Pictures /> is displayed. If the data is not present then <NoResultsFound /> is displayed.By default <NoResultsFound /> state is false and <Pictures /> is true because when the page loads the list of pictures are present. I tried to switch the state like this: this.setState({uisNoResultsFound: true}) and this.setState({uisPictures: false}) throws syntax error. I want this conditional rendering of the UI states within app.js. How to do this?
App.js:
class App extends Component {
constructor(props) {
super(props);
this.state = {
uisSearchBarItems: true,
uisNoResultsFound: false,
uisPictures: true,
dsPictures: []
};
}
componentDidMount() {
unsplash.search.collections("frog", 1, 60)
.then(toJson)
.then(json => {
this.setState({ dsPictures:json.results });
})
}
enteredDatahandler = (ctp) => {
unsplash.search.collections(ctp, 1, 60)
.then(toJson)
.then(json => {
this.setState({ dsPictures:json.results })
})
//******** conditional rendering ***********
if(this.state.dsPictures.length === 0){
return (
this.setState({uisNoResultsFound: true})
this.setState({uisPictures: false})
)
}
else{
this.setState({uisNoResultsFound: false})
this.setState({uisPictures: true})
}
//***********************************
}
render() {
return (
<div className="App">
<SearchBarItems ctpEnteredData={this.enteredDatahandler}/>
<NoResultsFound />
<Pictures ptcEnteredData={this.state.dsPictures}/>
</div>
);
}
}
export default App;
searchbaritems.js
class SearchBarItems extends Component {
enterKeyHandler = (event) => {
if (event.key === 'Enter'){
event.preventDefault();
this.props.ctpEnteredData(this.search.value)
}
}
render() {
return (
<div>
<form autoComplete="off" ref={(el) => this.myFormRef = el}>
<input
type="text"
name="search"
ref={input => this.search = input}
onKeyPress={this.enterKeyHandler}/>
</form>
</div>
)
}
}
Use a ternary expression inside your render method.
{this.state.dsPictures.length === 0 ? <NoResultsFound /> : <Pictures ptcEnteredData={this.state.dsPictures}/> }
In your render function you are returning both components, you need to either have if statements or you can do what #Barazu did - which is the most cleanest code.
Github gist: https://gist.github.com/tintinmovie/ed5b4782fa98c3482b561ea3243f98ea
render() {
if (this.state.uisNoResultsFound === true) {
return(
<div className="App">
<SearchBarItems ctpEnteredData={this.enteredDatahandler}/>
<NoResultsFound />
</div>
);
}
else if (this.state.uisPictures === true) {
return(
<div className="App">
<SearchBarItems ctpEnteredData={this.enteredDatahandler}/>
<Pictures ptcEnteredData={this.state.dsPictures}/>
</div>
);
}
}

Resources