React Modal with axios infinite loop - reactjs

The print statement in my if (this.props.currentNode.getAttribute("label").toLowerCase() === "data")
is being called infinite times which keeps on printing in modal and sending a post request until the modal is closed.
This does not happen when I put the post call in ComponentDidMount
Can anyone please explain why this is happening and what I can do to avoid this?
class ConfigurationModal extends React.Component {
constructor(props) {
super(props);
this.state = {
selectSource: [],
};
}
el = document.createElement("div");
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
jsonIsEmpty = (obj) => {
return obj === "[object Object]" ? true : false;
};
render() {
// The gray background
const backdropStyle = {
position: "fixed",
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: "rgba(0,0,0,0.3)",
padding: 50,
};
if (this.props.currentNode.getAttribute("label").toLowerCase() === "data") {
console.log("in modal")
http.post("configure_get_sources", {
headers: {
"content-type": "application/json",
},
})
.then((res) => {
this.setState({ selectSource: res.data });
});
var tempConfig = this.jsonIsEmpty(
this.props.currentNode.getAttribute("configuration")
)
? {}
: JSON.parse(this.props.currentNode.getAttribute("configuration"));
let renderedSources = this.state.selectSource.map((item, i) => {
return (
<option value={item} key={i} selected={item === tempConfig["Source"]}>
{item}
</option>
);
});
return ReactDOM.createPortal(
<div className="backdrop" style={backdropStyle}>
<Modal.Dialog>
<Modal.Header>
<Modal.Title>Configure Node</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="container">
<div className="row">
<label className="col-md-4">Data Source: </label>
<select className="col-md-7" id="dataSelect">
{renderedSources}
</select>
</div>
<div className="row col-md-3"></div>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.props.onClose}>
Close
</Button>
<Button variant="primary" onClick={this.props.saveModal}>
Save changes
</Button>
</Modal.Footer>
</Modal.Dialog>
</div>,
this.el
);
}
return "";
}
}
export default ConfigurationModal;

You're modifying component's state inside render, this causing an infinity loop. Because of react re-rendering component after each props or state change. https://reactjs.org/docs/faq-state.html

Related

Unable to pass props from parent to child and save it in state of child component

I'm trying to develop a website for fetching GitHub data, but I'm having problem in updating the component that shows data Formdata component. It doesn't seem to be updating form some reasons.
App:
export default class App extends Component {
constructor(props){
super(props);
this.state = {
uname:'',
udata:'',
};
this.handleInput = this.handleInput.bind(this);
this.getUser = this.getUser.bind(this);
}
getUser(){
fetch(`https://api.github.com/users/${this.state.uname}`)
.then(response => response.json())
.then(data => this.setState({udata:data}))
.catch(error => console.error(error));
}
handleInput(event){
this.setState({
uname:event.target.value
});
}
render() {
return (
<div>
<Header></Header>
<Form handleInput={this.handleInput} uname={this.state.uname} getUser={this.getUser}></Form>
<Formdata udata={this.state.udata}></Formdata>
</div>
)
}
}
Form:
export default function Form(props) {
const {getUser, handleInput, uname} = props;
return (
<div className="form">
<input className="textbar" placeholder="Search for username" value={uname} onChange={handleInput} name="uname"></input>
<button className="button" onClick={getUser} >Search</button>
</div>
)
}
Formdata:
export default class Formdata extends Component {
constructor(props){
super(props);
this.state = {
follower:'',
following:'',
public_repos:'',
visit_page:'',
avatar:''
}
this.updateUser = this.updateUser.bind(this);
};
componentDidMount(props){
this.updateUser();
}
updateUser(){
this.setState({follower:this.props.udata.followers});
this.setState({following:this.props.udata.following});
this.setState({public_repos:this.props.udata.public_repos});
this.setState({visit_page:this.props.udata.url});
this.setState({avatar:this.props.udata.avatar_url});
console.log(this.props.udata);
}
render() {
return (
<div>
<img className="imge" src= {this.state.avatar} alt=" "></img>
<div className="details">
<div className="compon">Followers: {this.state.followers}</div>
<div className="compon">Following: {this.state.following}</div>
<div className="compon">public repos" {this.state.public_repos}</div>
</div>
<div className="urls">Page:{this.state.visit_page}</div>
</div>
)
}
}
I can't figure out how to update component Formdata on clicking search button in Form component.
Full Working App: StackBlitz
import React, { Component, useEffect } from "react";
import "./style.css";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
uname: "",
udata: ""
};
this.handleInput = this.handleInput.bind(this);
this.getUser = this.getUser.bind(this);
}
getUser() {
fetch(`https://api.github.com/users/${this.state.uname}`)
.then(response => response.json())
.then(data =>
this.setState({ udata: data }, () => {
console.log(this.state.udata);
})
)
.catch(error => console.error(error));
}
handleInput(event) {
this.setState(
{
uname: event.target.value
},
() => {
console.log(this.state.uname);
}
);
}
render() {
return (
<div>
<Form
handleInput={this.handleInput}
uname={this.state.uname}
getUser={this.getUser}
/>
<Formdata udata={this.state.udata} />
</div>
);
}
}
const Form = props => {
const { getUser, handleInput, uname } = props;
return (
<div className="form">
<input
className="textbar"
placeholder="Search for username"
value={uname}
onChange={handleInput}
name="uname"
/>
<button className="button" onClick={getUser}>
Search
</button>
</div>
);
};
const Formdata = ({ udata }) => {
useEffect(() => {
console.log(JSON.stringify(udata.login));
}, [udata]);
return (
<div style={styles.card}>
{udata.login ? (
<div style={styles.cardImg}>
<div>
<img
style={styles.img}
className="imge"
src={udata?.avatar_url}
alt=" "
/>
</div>
<div className="details">
<div className="compon">Followers: {udata?.followers}</div>
<div className="compon">Following: {udata?.following}</div>
<div className="compon">Public repos: {udata?.public_repos}</div>
<div className="urls">Page: {udata?.url}</div>
</div>
</div>
) : (
<div>
<p>No Data Available</p>
</div>
)}
</div>
);
};
const styles = {
card: {
display: "flex",
flex: 1,
backgroundColor: "rgba(21,21,21,0.2)",
padding: 10,
marginTop: 10,
borderRadius: 5
},
cardImg: {
display: "flex",
flex: 1,
flexDirection: "row",
flexWrap: "wrap",
overflow: "hidden",
textOverflow: "ellipsis",
color: "rgba(0,0,0,0.7)"
},
img: {
marginRight: 10,
width: 100,
height: 100,
borderRadius: 10,
overflow: "hidden"
}
};
Do not copy props into state, use the props directly in your JSX:
div>
<img className="imge" src= {this.props.udata.avatar} alt=" "></img>
<div className="details">
<div className="compon">Followers: {this.props.udata.followers}</div>
<div className="compon">Following: {this.props.udata.following}</div>
<div className="compon">public repos" {this.props.udata.public_repos}</div>
</div>
<div className="urls">Page:{this.props.udata.visit_page}</div>
</div>
If you copy props into state, you are creating redundant copy of props and it is difficult to keep props and state in sync. And it is a React anti-pattern.
Just make sure this.props.udata is not undefined, it is ok if it is empty object {}. If it is undefined, put a check / conditional rendering.
anti-pattern-unconditionally-copying-props-to-state
Formdata.updateUser() isn't being called at any point. You probably just need to call it in componentDidMount():
export default class Formdata extends Component {
...
componentDidMount(props){
this.updateUser();
}
updateUser(){
this.setState({follower:this.props.udata.followers});
this.setState({following:this.props.udata.following});
this.setState({public_repos:this.props.udata.public_repos});
this.setState({visit_page:this.props.udata.url});
this.setState({avatar:this.props.udata.avatar_url});
console.log(this.props.udata);
}
...
}

if else statement in render?

I'm having some trouble with my code where I want a user to be taken to two specific pages after they click "Sign in" ONLY IF they haven't already gone through that process before. Those two pages, are below:
this.props.history.push('/decision-style');
this.props.history.push('/area-of-expertise');
How it works is, if they have already gone through the process of those two pages, I want them not to go through it again, and just to be redirected to our news page:
this.props.history.push('/news');
If they have gone through the process before, it will already have added their information in MongoDB in the documents "decisionStyle", and "role"
This is /area-of-expertise. Only want them to see this, and /decision-style if they haven't done this before, and therefore, their info isn't in Mongo
I thought I could maybe create an if... else statement in render to do something similar to what I am trying to achieve. However, that wouldn't work, so I have this code below:
class Login extends Component {
constructor(props) {
super(props);
this.state = {fact: null,
badge: null,
isLoaded: false,
error: null,
showRegistration: false,
userAuthFlow: false,
userData: {},
}
}
render() {
const self = this;
console.log(this.state.userData.decisionStyle);
// Create Registration Form
function RegistrationFormModal(props) {
return (
<Modal
{...props}
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Sign Up
<button className="closeBtn" onClick={self.handleCloseRegistration}></button>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Registration/>
</Modal.Body>
</Modal>
);
}
// Login page
const {error, isLoaded, fact} = this.state;
if (error) {
return (
<div>
Error: {error.messages}
</div>
);
} else if (!isLoaded) {
return (
<Spinner style={{'width': '200px', 'height': '200px', 'font-size': '50px'}} animation="border"/>
);
} else {
return (
<div id="Login">
<Fade top>
<h1 style={{'font-size': '50px'}}>CRCounter
</h1>
<p style={{'font-size': '32px'}}> {JSON.parse(JSON.stringify(fact.fact))}</p>
<p> - {JSON.parse(JSON.stringify(fact.author))} - </p>
</Fade>
<Fade bottom>
<div id="form">
<form>
</form>
<button className="confirmBtn" userAuthFlow={self.state.userData.role !== null && self.state.userData.decisionStyle !== null ? true : false}onClick = {this.handleClick}>Sign in</button>
<a id = "register" onClick={this.handleShowRegistration}>Don't have an account?</a>
<p id = "registerNote" > You won't be able to access most parts of the platform without an account! </p>
</div>
</Fade>
<RegistrationFormModal
show={this.state.showRegistration}
/>
</div>
);
}
}
The code below I created to be mainly responsible for trying to achieve what I want, but it's not working and not sure why as I am a bit of a React noob.. lol
<button className="confirmBtn" userAuthFlow={self.state.userData.role !== null && self.state.userData.decisionStyle !== null ? true : false}onClick = {this.handleClick}>Sign in</button>
The rest of the code (and updated code as well)...
/* eslint-disable require-jsdoc */
import React, {Component} from 'react';
import './Login.css';
import Fade from 'react-reveal/Fade';
import Spinner from 'react-bootstrap/Spinner';
import axios from 'axios';
import Modal from 'react-bootstrap/Modal';
import Registration from './Registration';
class Login extends Component {
constructor(props) {
super(props);
this.state = {fact: null,
badge: null,
error: null,
fact: null,
isLoaded: false,
showRegistration: false,
userAuthFlow: false,
userData: {},
}
}
handleClick = () => {
this.props.history.push('/decision-style');
}
// Registration form
handleShowRegistration = () => {
this.props.history.push('/news');
}
handleCloseRegistration = () => {
this.setState({showRegistration: false});
}
componentDidMount(sub) {
axios.get('/services/getuserdata', {
params: {ID: sub},
})
.then((response) => {
this.setState({userData: response.data});
});
// Get the facts that will be displayed under the CRCounter logo
function getFacts() {
return axios.get('/services/facts');
};
// Get the welcome badge for the user if they signded up successfully for the platform
function getBadge() {
return axios.get('/services/badge', {
params: {
name: 'welcome',
},
});
}
Promise.all([getFacts(), getBadge()])
.then((results) => {
const responseOne = results[0].data;
const responseTwo = results[1].data;
this.setState({
isLoaded: true,
fact: responseOne,
badge: responseTwo,
});
})
.catch((error) => {
this.setState({
isLoaded: true,
fact: {author: '', fact: ''}});
});
}
handleClick() {};
handleCloseRegistration() {};
handleShowRegisteration() {};
render() {
const { error, isLoaded, fact, showRegistration, userData } = this.state;
const flow = userData.role && userData.decisionStyle;
const parse = (str) => JSON.parse(JSON.stringify(str));
// Create Registration Form
const RegistrationFormModal = (props) => {
return (
<Modal
aria-labelledby="contained-modal-title-vcenter"
centered
{...props}
>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Sign Up
<button
className="closeBtn"
onClick={this.handleCloseRegistration}
>
Close Button
</button>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Registration />
</Modal.Body>
</Modal>
);
};
// Login page
if (error) {
return (
<div>
Error: {error.messages}
</div>
);
} else if (!isLoaded) {
return (
<Spinner style={{'width': '200px', 'height': '200px', 'font-size': '50px'}} animation="border"/>
);
} else {
return (
<div id="Login">
<Fade top>
<h1 style={{ 'font-size': '50px' }}>CRCounter</h1>
<p style={{ 'font-size': '32px' }}>{parse(fact.fact)}</p>
<p> - {parse(fact.author)} - </p>
</Fade>
<Fade bottom>
<div id="form">
<form>
</form>
<button
className="confirmBtn"
onClick={this.handleClick}
userAuthFlow={flow}
>
Sign in
</button>
<a
id="register"
onClick={this.handleShowRegistration}
>
Don't have an account?
</a>
<p id="registerNote" >
You won't be able to access most parts of the platform without an account!
</p>
</div>
</Fade>
<RegistrationFormModal show={showRegistration} />
</div>
);
}
}}
export default Login;
Rather than checking for falsy values, just check for truthy values instead.
Here, if we have role && decisionStyle then it is true, otherwise false.
The logical AND operator (&&) returns true if both operands are true and returns false otherwise.
self.state.userData.role && self.state.userData.decisionStyle
The logic in your condition is a little off. It should be something like userData.role && userData.decisionStyle;. I also took the liberty of destructuring some of your code, using arrow functions, removing self, and a couple of other things!
UPDATED
class Login extends Component {
constructor(props) {
super(props);
this.state = {
badge: null,
error: null,
fact: null,
isLoaded: false,
showRegistration: false,
userAuthFlow: false, // do you need this?
userData: {},
};
}
componentDidMount() {
// existing code here
}
handleClick = () => {
const { history } = this.props;
const flow = userData.role && userData.decisionStyle;
history.push(flow ? '/news' : '/decision-style');
}
handleShowRegistration = () => {
this.setState({ showRegistration: true });
}
handleCloseRegistration = () => {
this.setState({ showRegistration: false });
}
render() {
const { error, isLoaded, fact, showRegistration, userData } = this.state;
const parse = (str) => JSON.parse(JSON.stringify(str));
// Create Registration Form
const RegistrationFormModal = (props) => {
return (
<Modal
aria-labelledby="contained-modal-title-vcenter"
centered
{...props}
>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Sign Up
<button
className="closeBtn"
onClick={this.handleCloseRegistration}
>
Close Button
</button>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Registration />
</Modal.Body>
</Modal>
);
};
// Login page
if (error) return <div>Error: {error.messages}</div>;
if (!isLoaded) return (
return (
<Spinner
animation='border'
style={{
font-size: '50px',
height: '200px',
width: '200px',
}}
/>
);
);
return (
<div id="Login">
<Fade top>
<h1 style={{ 'font-size': '50px' }}>CRCounter</h1>
<p style={{ 'font-size': '32px' }}>{parse(fact.fact)}</p>
<p> - {parse(fact.author)} - </p>
</Fade>
<Fade bottom>
<div id="form">
<form>
</form>
<button
className="confirmBtn"
onClick={this.handleClick}
>
Sign in
</button>
<a
id="register"
onClick={this.handleShowRegistration}
>
Don't have an account?
</a>
<p id="registerNote" >
You won't be able to access most parts of the platform without an account!
</p>
</div>
</Fade>
<RegistrationFormModal show={showRegistration} />
</div>
);
}
}

conditional render disappears after a few seconds

Hi I am making a website front end (react) which talks to my back end (npm express/npm mssql)
I have made a page in which it uses a map function to render a separate component containing questions.
I have tried to implement a conditional render so that when question complete is set as true it then renders a new component but the new component will vanish after a second or so and will go back to its original state.
these are both in the same file so only one set of imports are required in this situation.
This is my display questions class using a .map to render the separate component.
import React from "react";
import { Link } from "react-router-dom";
import { Modal } from "react-bootstrap";
class DisplayQuestions extends React.Component {
constructor() {
super();
this.state = { questions: [], QuestionsAnswer: [], QuestionsSeverity: [] };
this.onSubmit = this.handleSubmit.bind(this);
}
// sets the questions form sql into state for questions
getItems() {
fetch("/user-questions")
.then(recordset => recordset.json())
.then(results => {
this.setState({ questions: results.recordset });
});
}
//when the component mounts make the sql questions the s
componentDidMount() {
this.setState({
questions: this.getItems()
});
}
handleSubmit(e) {
e.preventDefault();
const data = {
QuestionID: this.QuestionID,
QuestionsAnswer: this.state.QuestionsAnswer,
QuestionSeverity: this.state.QuestionsSeverity
};
try {
fetch("/Question-Response", {
method: "POST", // or 'PUT'
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json"
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
console.log("Success:", data);
})
.catch(error => {
console.error("Error:", error);
});
} catch (error) {}
}
refresh() {
window.location.reload();
}
render() {
var self = this;
console.log(this.state.questions);
return (
<div>
<h3 style={{ textAlign: "center" }}>
<u>Desk Assessment</u>
</h3>
<ul>
<button
disabled
className="btn btn-secondary"
style={{ float: "left " }}
>
Desk Assessment
</button>
<Link to="./user-history">
<button className="btn btn-secondary" style={{ float: "left " }}>
View History
</button>
</Link>
<br />
<br />
{this.state.questions &&
this.state.questions.map(function(questions, index) {
return (
<div >
<ul>
<WorkStations questions={questions}></WorkStations>
</ul>
</div>
);
})}
</ul>
</div>
);
}
}
export default DisplayQuestions;
This is my Questions component which is rendered
class WorkStations extends React.Component {
constructor(props) {
super(props);
console.log(props);
this.state = { ...props, show: false, QuestionAnswer: "", QuestionComplete: false };
this.QuestionDecline = this.QuestionDecline.bind(this);
this.QuestionOnChange = this.QuestionOnChange.bind(this);
this.OnCommit = this.OnCommit.bind(this);
}
QuestionDecline(e) {
e.preventDefault();
if (this.state.ShowInput) {
this.setState({ ShowInput: false });
alert(this.state.ShowInput);
} else if (!this.state.ShowInput) {
this.setState({ ShowInput: true });
alert(this.state.ShowInput);
}
}
QuestionOnChange(e) {
this.setState({ QuestionAnswer: e.target.value });
}
//////////////////////////////////////////////////////////// I believe its within here something is off
OnCommit(e) {
e.preventDefault();
alert(this.state.QuestionAnswer);
var today = new Date(),
date = `${today.getUTCFullYear()}-${today.getUTCMonth() +
1}-${today.getUTCDate()} ${today.getHours()}:${today.getMinutes()}:${today.getSeconds()}.${today.getMilliseconds()} `;
let User = window.localStorage.getItem("User")
const data = {
QuestionId: this.state.questions.QuestionId,
QuestionAnswer: this.state.QuestionAnswer,
date : date,
User
};
fetch("/question-response", {
method: "POST", // or 'PUT'
headers: {
Accept: "application/json,",
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(response => {
console.log("response before it is broken down " + response);
// return response.json();
});
if (this.state.QuestionComplete) {
this.setState({ QuestionComplete: false });
alert(this.state.QuestionComplete);
} else if (!this.state.QuestionComplete) {
this.setState({ QuestionComplete: true });
alert(this.state.QuestionComplete);
}
window.location.reload();
}
////////////////////////////////////////////////
render() {
if (!this.state.QuestionComplete ){
if (!this.state.ShowInput && !this.state.QuestionComplete) {
return (
<div className="jumbotron">
<button
onClick={this.QuestionDecline}
className="btn btn-danger"
style={{ float: "right" }}
>
Decline
</button> <button
onClick={this.deleteQuestion}
className="btn btn-primary"
style={{ float: "right" }}
>
Accept
</button>
<br />
<li> Question ID: {this.state.questions.QuestionId}</li>
<li> Question:{this.state.questions.Question}</li>
</div>
);
} else if(this.state.ShowInput && !this.state.QuestionComplete) {
return (
<div className = "jumbotron">
<li>Question Id: {this.state.questions.QuestionId}</li>
<li>
<textarea
placeholder= "How can this be improved ?"
style={{ width: "100%" }}
onChange={this.QuestionOnChange}
/>
</li>
<button
style={{ float: "right", padding: "2px" }}
className="btn btn-primary"
onClick={this.OnCommit}
>
Submit
</button>
<button
onClick={this.EditQuestion}
style={{ float: "right", padding: "2px" }}
className="btn btn-secondary"
>
Revert
</button>
<br />
</div>
);
}
}else if (this.state.QuestionComplete) {
return(<h3> <li>Question Id: {this.state.questions.QuestionId}</li></h3>)
}
}
}

Hide all div and show one div on clicking multiple button

I am trying to fit 3 component in a single page by hiding/showing on a div.But I am not really getting into how to do it.This is the first div.
<div>
<p>What is the type of your property?</p>
<button >Residence</button>
<button>Commercial</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
Only If i click the 'Commercial' or 'Next' button it would go into the second div and first div will hide.
<div>
<p>What is the type of your commercial property?</p>
<button>Office</button>
<button>Restaurant</button>
<button >Outlet</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
and lastly if i click 'restaurant' button from the first div and any button of the second div except the back button it will go into the third div and other div will hide.this is the third div.
<div>
<div className='slider' style={{ marginTop:'165px',marginLeft:'319px',width:'700px',backgroundColor:'EF5350'}} >
<Slider min={850} max={5000} value={value} onChangeStart={this.handleChangeStart}
onChange={this.handleChange}
onChangeComplete={this.handleChangeComplete}
/>
<div className='value'>{value} Squarefeet</div>
<div style={{marginTop:'86px'}}>
<span onChange={this.handleChange} onClick={() => this.saveValue()} >Next</span>
<span onChange={this.handleChange} onClick={() => this.saveValue()} >Next</span>
</div>
</div>
</div>
I tried to do it this way. But it will not work.
import React from 'react';
import Link from "next/link";
class Jh extends React.Component {
constructor() {
super();
this.state = {
shown: true,
hide: false
};
}
toggle() {
this.setState({
shown: !this.state.shown
});
}
toggles() {
this.setState({
shown: !this.state.hide
});
}
render() {
var shown = {
display: this.state.shown ? "block" : "none"
};
var hidden = {
display: this.state.shown ? "none" : "block"
}
return (
<div>
<button onClick={this.toggle.bind(this)} style={ shown }>
<div>
<p>What is the type of your property?</p>
<button >Residence</button>
<button>Commercial</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
</button>
<button onClick={this.toggles.bind(this)} style={ hidden }>
<div>
<p>What is the type of your commercial property?</p>
<button>Office</button>
<button>Restaurant</button>
<button >Outlet</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
</button>
</div>
)
}
}
export default Jh
What should be my approach?
There are many patterns to achieve a "switch case", I'll try to show my favorites:
For sipmlicity, I'll use a generic use case.
Straight Forward
Managing visible state for every component:
return {visible && <CoolComponent id={1} />};
Switch case in disguise
Manage a state of object keys. (currentCounter)
const countersPicker = {
counter1: <Counter id={1} />,
counter2: <Counter id={2} />,
coolComponent: <CoolComponent id={3} />
};
return {countersPicker[currentCounter]};
Here you also can take action on the object, for example, adding a header:
return {Object.entries(countersPicker).map(([key,component]) =>
<div key={key}>
<h1>Component key = {key}</h1>
{component}
</div>
)};
Filter Children
Manage a predicate and use it for filtering/mapping the children.
Check React.Children API.
return (
<FilterComponents predicate={predicate}>
<Counter key={1} id={1} />
<Counter key={2} id={2} />
<CoolComponent key={3} id={3} />
<BestComponent key={4} id={4} />
</FilterComponents>
);
function FilterComponents({ children, predicate }) {
const filteredChildren = React.Children.toArray(children).filter(child =>
// Use the predicate.
// Filter a child by key, key & type or even use ref etc.
);
return <div>{filteredChildren}</div>;
}
I believe you are looking for something like this.
Main things to-do:
Enhance your state-value. Keep track of the different pages in sequence by using an array. Track the current page. Track the start and end of the collection.
Here is the sandbox as well: https://codesandbox.io/s/unruffled-sun-gpzx6
import React from "react";
class Pages extends React.Component {
state = {
currentPage: "property",
pages: ["property", "type", "firstBusiness"],
start: true,
end: false
};
changePage = event => {
const { currentPage, pages } = this.state;
const { name } = event.target;
//check if we are going to end
if (
name == "next" &&
pages[pages.indexOf(currentPage) + 1] === pages[pages.length - 1]
) {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
end: true,
start: false
});
//go to next page
} else if (name == "next") {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
start: false
});
//check if we are going to beginning
} else if (
name == "back" &&
currentPage !== pages[0] &&
pages[pages.indexOf(currentPage) - 1] == pages[0]
) {
this.setState({
currentPage: pages[pages.indexOf(currentPage) - 1],
start: true
});
//go back one page
} else {
this.setState({
currentPage: pages[pages.indexOf(currentPage) - 1],
end: false
});
}
};
goToNextPage = () => {
const { currentPage, pages, end } = this.state;
//check if we are going to end
if (pages[pages.indexOf(currentPage) + 1] === pages[pages.length - 1]) {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
end: true,
start: false
});
//go to next page
} else if (end) {
return;
} else {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
start: false
});
}
};
render() {
const { currentPage, start, end } = this.state;
return (
<div style={{ background: "gray" }}>
{currentPage === "property" ? (
<div>
<p>What is the type of your property?</p>
<button onClick={this.goToNextPage}>Residence</button>
<button onClick={this.goToNextPage}>Commercial</button>
</div>
) : null}
{currentPage === "type" ? (
<div>
<p>What is the type of your commercial property?</p>
<button onClick={this.goToNextPage}>Office</button>
<button onClick={this.goToNextPage}>Restaurant</button>
<button onClick={this.goToNextPage}>Outlet</button>
</div>
) : null}
{currentPage === "firstBusiness" ? (
<div>
<p>Is this your first business?</p>
<button onClick={this.goToNextPage}>Yes</button>
<button onClick={this.goToNextPage}>No</button>
</div>
) : null}
<div>
<button onClick={this.changePage} name="back" disabled={start}>
Back
</button>
<button onClick={this.changePage} name="next" disabled={end}>
Next
</button>
</div>
</div>
);
}
}
export default Pages;
So essentially you want router like functionality. Here is one approach:
class FirstPage extends React.Component {
render() {
//...first page content
}
}
class SecondPage extends React.Component {
render() {
//...second page content
}
}
const pages = {
first: FirstPage,
second: SecondPage
};
class App extends React.Component {
constructor() {
this.state = {
page: 'first'
};
}
render() {
const PageComponent = pages[this.state.page];
return <div>
<button onClick={() => this.setState({page: 'first'})}>First page</button>
<button onClick={() => this.setState({page: 'second'})}>Second page</button>
<PageComponent/>
</div>
}
}
There are many ways to solve this problem. But in my opinion the best solution is the one which solves the problem in a succinct manner.
Please find below the working solution which I have tried and works like a charm:
import React from "react";
class Pages extends React.Component {
state = {
activeTab: 1
};
toggle = tab => {
this.setState({
activeTab: tab
});
};
togglePage = page => {
if (page === "next") {
this.setState({
activeTab: this.state.activeTab + 1
});
} else if (page === "back") {
this.setState({
activeTab: this.state.activeTab - 1
});
}
};
render() {
return (
<div style={{ background: "#dedede" }}>
<div hidden={this.state.activeTab === 1 ? false : true}>
<p>1) What is the type of your property?</p>
<button class="btn btn-primary" onClick={() => this.toggle(2)}>
Residence
</button>
<button onClick={() => this.toggle(2)}>Commercial</button>
</div>
<div hidden={this.state.activeTab === 2 ? false : true}>
<p>2) What is the type of your commercial property?</p>
<button onClick={() => this.toggle(3)}>Office</button>
<button onClick={() => this.toggle(3)}>Restaurant</button>
<button onClick={() => this.toggle(3)}>Outlet</button>
</div>
<div hidden={this.state.activeTab === 3 ? false : true}>
<p>3) Is this your first business?</p>
<button onClick={this.NextAction}>Yes</button>
<button onClick={this.NextAction}>No</button>
</div>
<div>
<button
onClick={() => this.togglePage("back")}
name="back"
disabled={this.state.activeTab === 1 ? true : false}
>
Back
</button>
<button
onClick={() => this.togglePage("next")}
name="next"
disabled={this.state.activeTab === 3 ? true : false}
>
Next
</button>
</div>
</div>
);
}
}
export default Pages;
In react we have a hidden attribute which you can use to show/hide the elements without having to write any css for the same.
And I have tried to solve the problem with the least number of variables.
The sandbox for the same can be found here : https://codesandbox.io/s/mysolution-g8fu6
Hope this helps!

React Current Image in Image Gallery

Hello i have a litle problem with react-image-gallery.
In ImageGallery component i pass startIndex value like this.state.currentImage and this.state.currentImage depends on number photo with we are going to click.
When we click on photo for example number 4 this.state.currentImage is chaining on number 4 and its correct for me but in <imageGallery/> component startIndex doesn't work like i should. My modal always start on first image index[0].
import React, { Component } from "react";
import { Modal, ModalClose, ModalBody } from "react-modal-bootstrap";
import ImageGallery from "react-image-gallery";
import "./index.css";
export default class Images extends Component {
constructor(props) {
super(props);
var data = { title: "photos", images: [], ...props.data };
this.state = {
open: false,
showPlayButton: true,
showGalleryPlayButton: false,
showFullscreenButton: true,
showGalleryFullscreenButton: false,
currentImage: 0,
test: 0,
player: [],
data: data
};
console.log("Images: ", this.state.data);
this.openLightbox = this.openLightbox.bind(this);
this._renderImages = this._renderImages.bind(this);
this._onSlide = this._onSlide.bind(this);
this._onReady = this._onReady.bind(this);
}
state = {
isOpen: false
};
openModal = event => {
console.log(event.target);
this.setState({ isOpen: true });
};
openLightbox(index, event) {
// console.log('index',index);
event.preventDefault();
// this.setState({
// isOpen: true,
// currentImage: index
// });
this.setState(
prevState => {
return {
currentImage: index,
isOpen: true
};
},
() => {
console.log("currentImage", this.state.currentImage);
console.log("event", index);
}
);
}
hideModal = () => {
this.setState({ isOpen: false });
};
_renderImages(item) {
return (
<div className="images image-gallery-image">
<div className="images image-wrapper">
<h1>{this.state.currentImage}</h1>
<img src={item.img} alt="" className="images multimedia_image" />
<span className="images image-gallery-description">{item.desc}</span>
</div>
</div>
);
}
_onReady(event) {
const player = this.state.player;
player.push(event.target);
this.setState({
player: player
});
}
_onSlide() {
this.state.data.images.forEach(player => {});
}
handleImageLoad(event) {
console.log("Image loaded ", event.target);
}
render() {
var openLightbox = this.openLightbox;
var currentImage = this.state.currentImage;
const number = this.state.currentImage;
return (
<div className="images row">
<div className="images col-xs-12 col-sm-12 col-md-12 col-lg-12">
<div className="images title">{this.state.data.title}</div>
</div>
<div className="images col-xs-12 col-sm-12 col-md-12 col-lg-12">
<div className="images row">
{this.state.data.images.map((object, i) => {
let backgroundImage = {
backgroundImage: "url(" + object.thumbnail + ")",
top: 0,
right: 0,
left: 0,
bottom: 0,
position: "absolute",
flex: 1,
backgroundPosition: "center",
backgroundSize: "cover",
zIndex: 1
};
return (
<div
className="images item col-xs-4 col-sm-4 col-md-3 col-lg-3 images__single-item"
key={i}
>
<div
className="images multimedia_button"
onClick={e => this.openLightbox(i, e)}
>
<div style={backgroundImage} />
</div>
</div>
);
})}
</div>
</div>
<Modal isOpen={this.state.isOpen} onRequestHide={this.hideModal}>
<button
type="button"
className="images player_button_close"
onClick={this.hideModal}
>
X
</button>
<ModalBody>
<ImageGallery
items={this.state.data.images}
startIndex={this.state.currentImage}
slideInterval={2000}
showPlayButton={false}
showFullscreenButton={false}
onImageLoad={this.handleImageLoad}
onSlide={this._onSlide}
showIndex={true}
renderItem={this._renderImages}
/>
</ModalBody>
</Modal>
</div>
);
}
}
I found a solution when I needed to reinitialize current element. It can be useful for some other dev.
The answer is A “key” a special string attribute when we create lists of elements.
Whenever "key" is going change element is rerender. So
this.state = {
open: false,
showPlayButton: true,
showGalleryPlayButton: false,
showFullscreenButton: true,
showGalleryFullscreenButton: false,
currentImage: 0,
test: 0,
player: [],
data: data,
number:0
};
openLightbox(index, event) {
event.preventDefault();
this.setState(
prevState => {
return {
currentImage: index,
isOpen: true,
number:prevState.number+1
};
},
() => {
console.log("currentImage", this.state.currentImage);
console.log("event", index);
}
);
And here we neet to add our key={this.state.number}
<Modal isOpen={this.state.isOpen} onRequestHide={this.hideModal}>
<button
type="button"
className="images player_button_close"
onClick={this.hideModal}
>
X
</button>
<ModalBody>
<ImageGallery
key={this.state.number}
items={this.state.data.images}
startIndex={this.state.currentImage}
slideInterval={2000}
showPlayButton={false}
showFullscreenButton={false}
onImageLoad={this.handleImageLoad}
onSlide={this._onSlide}
showIndex={true}
renderItem={this._renderImages}
/>
</ModalBody>
</Modal>

Resources