I'd need your help because I have a modal in react SPA which contains an iFrame (fullscreen is allowed) and, when I click on fullscreen and then close (with x button) the modal, it causes a redirect on the parent page. I tried to add a listener on che "unload" event but nothing change.
Do you have any idea what causes this behaviour?
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
this.mediaRef = React.createRef();
}
componentDidMount() {
this.mediaRef.current.addEventListener('unload', this.onCloseEvent);
}
onCloseEvent = (event) => {
// it should cause the closing of the modal
this.props.setSelectedMedia(null);
}
renderMedia = () => {
if (this.props.selectedMedia.type.includes("image")) {
return (
<Img
as={motion.img}
src={this.props.selectedMedia.url}
alt="Enlarged Pic"
ref={this.mediaRef}
initial={{ y: "-100vh" }}
animate={{ y: "0" }}
/>
);
} else if (this.props.selectedMedia.type.includes("video")) {
//<ModalVideo channel = "custom" url = { this.props.selectedMedia.url } />
return (
<Iframe2
//width = "560"
//height = "560"
allow="fullscreen *; autoplay; encrypted-media"
allowfullscreen="true"
title="video"
src={this.props.selectedMedia.url}
ref={this.mediaRef}
/>
);
}
};
handleClick = e => {
if (this.mediaRef && !this.mediaRef.current.contains(e.target))
this.props.setSelectedMedia(null);
};
render() {
return (
<ModalContainer
as={motion.div}
onClick={this.handleClick}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
{this.renderMedia()}
</ModalContainer>
);
}
}
export default Modal;
Thank you very much for your help.
Related
In the TableAndInfo Component, each row's parent element is rendered by the renderEls function and the content is returned by the renderSection function. This passes in the content in the sections property as a parameter, as well as the class of the parent container. Passing the props value of date, time, and current in the sections array results in a successful initial render, but they do not continue updating when the state changes. I have inspected the code with the React Developer Tools and I see that the state is being updated by the functions defined in the App function and other components. How do I ensure that the grandchildren elements are re-rendered when the state is updated? Sorry if this doesn't make sense, I was having trouble trying to explain the problem. Thanks in advance!
function App() {
var [component, updateView] = useState('ServerFunctions');
var updateDateAndTime = function(formatDate) {
setInterval(function() {
if (document.getElementsByClassName('date')[0] && document.getElementsByClassName('time')[0]) {
updateDate(formatDate('date'));
updateTime(formatDate('time'));
}
}, 1000);
};
useEffect(() => {
updateDateAndTime(formatDate);
});
var [date, updateDate] = useState(formatDate('date'));
var [time, updateTime] = useState(formatDate('time'));
switch(component) {
case 'Welcome':
return (<Welcome updateView={updateView} date={date} time={time} backspacePinpad={backspacePinpad} />);
case 'ServerFunctions':
return (<ServerFunctions updateView={updateView} date={date} time={time} />);
default:
return null;
}
}
class TablesAndInfo extends React.Component {
sections = [[['info-row pct-space-b', 'flex between full-h'], {
1: ['Menu', 'button', 'gray white-f clickable roundish quarter-w label clickable'],
2: [this.props.current, 'div', 'f-override white-b small left three-quarter-w no-txt-overflow'],
}], [['info-table full-h', 'flex full-h'], {
1: ['Another Round', 'button', 'info-button blue white-f clickable'],
2: ['Select All', 'button', 'info-button gray white-f clickable'],
3: ['Name Check', 'button', 'info-button yellow clickable'],
}], [['tables-section full-h', 'tables-section full-h white-b'], {
}], [['new-table-b full-h', 'new-table-b med single round label full-h full-w'], {
1: ['New Table', 'button', 'blue white-f med single clickable round label clickable full-h full-w']
}]];
renderEls(num, classes) {
return (
<div className={classes}>
{this.sections[num].map((child, key) => {
if (typeof child === 'object' && !(child instanceof Array)) {
return (
<div className={this.sections[num][0][0]}>{this.renderSection(child, this.sections[num][0][1], key)}</div>
)
} else {
return null;
}
})}
</div>
)
}
renderSection(obj, parentClass) {
return (
<div className={parentClass}>
{Object.keys(obj).map((key, i) => {
if (obj[key][1] === 'button') {
return (
<button key={i} className={"flex center " + obj[key][2]}>{obj[key][0]}</button>
)
} else {
return (
<div key={i} className={"flex center " + obj[key][2]}>{obj[key][0]}</div>
)
}
})}
</div>
)
}
render() {
return (
<div className="tables space-r">
<div className="info tables-section">
{this.renderEls(0, 'info-table info-r')}
{this.renderEls(1, 'info-table info-b')}
</div>
{this.renderEls(2, 'table-view tables-section full-h pct-space-b')}
{this.renderEls(3, 'new-table')}
</div>
)
}
class ServerFunctions extends React.Component {
return (
<div className="App ServerFunctions">
<Header signOff={this.signOff} renderBttnRow={this.renderBttnRow} />
<div className="container order-control flex space-b">
<SelectedItems />
<TablesAndInfo current={this.state.current} date={this.props.date} time={this.props.time} />
<Functions current={this.state.current} />
</div>
<Footer current={this.state.current} renderBttnRow={this.renderBttnRow} />
</div>
)
}
}
Ended up solving this! Here's what I added in the TablesAndInfo Component:
constructor(props) {
super(props);
this.state = {current: this.props.current, date: this.props.date, time: this.props.time}
}
shouldComponentUpdate(nextProps) {
if ((nextProps.current !== this.state.current) || (nextProps.date !== this.state.date) || (nextProps.time !== this.state.time)) {
this.setState({current: nextProps.current});
this.setState({date: nextProps.date});
this.setState({time: nextProps.time});
}
return true;
}
componentDidUpdate() {
this.sections[0][1][2][0] = this.state.current;
this.sections[0][2][1][0] = this.state.time;
this.sections[0][2][2][0] = this.state.date;
}
im currently trying to figure out how to solve this problem:
As you can see, the search bar value is initialized to "TSLA", however, instead of replacing the TSLA data widget with an AAPL data widget, below the TSLA widget it shows a new widget for "A", "AA", "AAP" and finally "AAPL", rendering a new widget as I type each letter of the ticker into the search bar.
Here is the code for the search bar, with the state variable "value" being what I pass to my widgets:
class OneStockData extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'TSLA',
clicked: false
}
this.handleChange = this.handleChange.bind(this)
}
toggleBtnHandler = () => {
return this.setState({
clicked:!this.state.clicked
})
}
handleChange (event) {
this.setState({value: event.target.value })
}
render() {
const styles = ['button'];
let text = 'Search';
return (
<div>
<Container fluid>
<Row>
<Card>
<CardTitle className="text-uppercase text-muted h6 mb-0">Enter Stock Ticker: </CardTitle>
<input type="text" value={this.state.value} onChange={this.handleChange} />
<div>
<button className="button" onClick={this.toggleBtnHandler}>{text}</button>
</div>
</Card>
<Card>
<SSIWidget value = {this.state.value}/>
</Card>
</Row >
And here is the code in the SSIWidget:
class SSIWidget extends React.Component {
constructor(props){
super(props);
}
AddWidget = () => {
const script = document.createElement('script');
script.src ="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js";
script.async = true;
script.innerHTML = JSON.stringify(
{
"symbol": this.props.value,
"width": 1000,
"locale": "en",
"colorTheme": "light",
"isTransparent": false
}
)
document.getElementById("myContainer6").appendChild(script);
}
componentDidMount() {
this.AddWidget();
}
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.AddWidget();
}
}
render() {
return(
<div id="myContainer6">
<div className="tradingview-widget-container">
<div className="tradingview-widget-container__widget">
</div>
</div>
</div>
);
}
}
There are 2 problems going on:
It renders an new "SSIWidget" as each letter of the ticker is typed, and stacks them below each other. This is a big no-no, I want the widget where TSLA data is being shown to be the ONLY SSIWidget, and the data replaced by each new ticker after I click "Search"
As I clear the search bar, I would want the widget to show the value of "TSLA" as it was initialized to be.
I have tried different search bars and a few resourced on "onClick", but haven't yet found a solution to my issue. Can someone here point me in the right direction?
Maintain another state say searchedValue and update it upon search button click.
Refactored code
class OneStockData extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "TSLA",
searchedValue: "TSLA", //<------- here
clicked: false,
};
this.handleChange = this.handleChange.bind(this);
}
toggleBtnHandler = () => {
return this.setState({
clicked: !this.state.clicked,
searchedValue: this.state.value === "" ? "TSLA" : this.state.value, //<------- here
});
};
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
const styles = ["button"];
let text = "Search";
return (
<div>
<Container fluid>
<Row>
<Card>
<CardTitle className="text-uppercase text-muted h6 mb-0">
Enter Stock Ticker:{" "}
</CardTitle>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<div>
<button className="button" onClick={this.toggleBtnHandler}>
{text}
</button>
</div>
</Card>
<Card>
<SSIWidget value={this.state.searchedValue} /> //<------- here
</Card>
</Row>
</Container>
</div>
);
}
}
Edit: Follow up qn based on comment.
You are appending the child. Before appending you need to clear children.
AddWidget = () => {
const script = document.createElement('script');
script.src ="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js";
script.async = true;
script.innerHTML = JSON.stringify(
{
"symbol": this.props.value,
"width": 1000,
"locale": "en",
"colorTheme": "light",
"isTransparent": false
}
)
document.getElementById("myContainer6").innerHTML = ''; //<----here
document.getElementById("myContainer6").appendChild(script);
}
I have several pictures and onPress I want to make tapped picture bigger(on focus) make background dark with opacity like this.
If I will tap on this picture I wanna do roughly same thing but on mobile (react-native).
I believe you need to zoom the image from center and not from the top left. You can use this approach, and here is the pen for it: Codepen Link
class Profile extends React.Component {
constructor(props){
super(props);
this.state = {
height: '100%',
width: '100%',
flag: 0
};
}
render() {
var { pic } = this.props;
return (
<div>
<img src={pic} id="myImage" height={this.state.height} width={this.state.width} onClick={this.zoomHandler.bind(this)}/>
</div>
);
}
zoomHandler()
{
if(this.state.flag === 0)
{
document.getElementById("myImage").style.transform = "scale(2,2)";
this.setState({flag: 1});
}
else
{
document.getElementById("myImage").style.transform = "scale(1,1)";
this.setState({flag: 0});
}
}
}
React.render(
<Profile
pic="https://www.diariogol.com/uploads/s1/55/38/91/7/messi-leo-getafe-camp-nou_15_970x597.jpeg" />,
document.getElementById('app')
);
I have used states for height and weight, which can also be used for the zoom effect.
Use:
- Scale animation with Animated Component,
Example:
state = {
zoomImage: 1
}
zoomIn=()=>{
Animated.timing(this.state.zoomImage, {
toValue: 2,
duration: 2000,
userNativeDriver: true
})
}
render():
const animatedStyle = {
transform: [
{
scale: this.state.zoomImage
}
]
}
<TouchableOpacity onPress={() => this.zoomIn}>
<Image style={animatedStyle}/>
</TouchableOpacity>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Refer: https://medium.com/react-native-training/react-native-animations-using-the-animated-api-ebe8e0669fae
Im trying to implement infinite scroll for React.js. Everything works fine, except I want to be able to use the window scrollbar, to activate the infinite scroll. The code at the moment, has 2 scrollbars( I only want one).
The code is from stackoverflow answered here, I read his complete answer, I tried setting the height to 100%, but it makes the infinite scroll not work. : Stackoverflow- answered by Aniket Jha, ( the longest answer with 4 upvotes)
Double scroll happens when I render this in this way:
<div>
<First />
<div ref="iScroll" style={{ height: "100vh", overflow: "auto"}}>
<ul>
{this.displayItems()}
</ul>
{this.state.loadingState ? <p className="loading"> loading More
Items..</p> : ""}
</div>
</div>
I have a Link to Codepen if this helps
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
items: 30,
loadingState: false
};
}
componentDidMount() {
this.refs.iScroll.addEventListener("scroll", () => {
if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >= this.refs.iScroll.scrollHeight - 20){
this.loadMoreItems();
}
});
}
displayItems() {
var items = [];
for (var i = 0; i < this.state.items; i++) {
items.push(<li key={i}>Item {i}</li>);
}
return items;
}
loadMoreItems() {
if(this.state.loadingState){
return;
}
this.setState({ loadingState: true });
setTimeout(() => {
this.setState({ items: this.state.items + 10, loadingState: false });
}, 1000);
}
render() {
return (<div>
<First />
<div ref="iScroll" style={{ height: "100vh", overflow: "auto"}}>
<ul>
{this.displayItems()}
</ul>
{this.state.loadingState ? <p className="loading"> loading More Items..</p> : ""}
</div>
</div>
);
}
}
class First extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<h1>Testing</h1>
)
}
}
ReactDOM.render(<Layout />, document.getElementById('example'));
If you don't want the second scrollbar to appear, you need to style the title and sibling div so that they fit in the available viewport.
In your codepen, you have height: '100%' for your scrolling div. This styles the div so that it doesn't need to scroll and infinite scroll therefore doesn't work.
If you style that div so that it takes up less than the height of the available viewport, and render enough items to fill it up, infinite scroll will work fine.
If you then style the title div combination so that they fit exactly into the available viewport space, you won't get a second scrollbar.
Below is an option you have to do this. What I've done is set the height of the scrolling div to be the viewport height (100vh) minus 100px. That's not precisely calculated, but what you want is to subtract the space required for the title from the size of the viewport.
This implementation works fine for me, and should for you as well.
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
items: 30,
loadingState: false
};
}
componentDidMount() {
this.refs.iScroll.addEventListener("scroll", () => {
if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >= this.refs.iScroll.scrollHeight - 20){
this.loadMoreItems();
}
});
}
displayItems() {
var items = [];
for (var i = 0; i < this.state.items; i++) {
items.push(<li key={i}>Item {i}</li>);
}
return items;
}
loadMoreItems() {
if(this.state.loadingState){
return;
}
this.setState({ loadingState: true });
setTimeout(() => {
this.setState({ items: this.state.items + 10, loadingState: false });
}, 1000);
}
render() {
return (<div>
<First />
<div ref="iScroll" style={{ height: "calc(100vh - 100px)", overflow: "auto"}}>
<ul>
{this.displayItems()}
</ul>
{this.state.loadingState ? <p className="loading"> loading More Items..</p> : ""}
</div>
</div>
);
}
}
class First extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<h1>Testing</h1>
)
}
}
ReactDOM.render(<Layout />, document.getElementById('example'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="example"></div>
I'm trying to load items from JSON and toggle a dropdown div with description on click. While I can display elements sequentially (ex: loc1 & desc1, loc2 & desc2) on static divs I'm having trouble finding out how to render it properly when the second part (desc) is hidden and only shows when the loc div is clicked.
What would be the best way to map the result so it doesn't show as loc1 & loc2, desc1 & desc2 but as loc1 & desc1, loc2 & desc2?
Code:
var places = {
library: {
location: [
{
loc_name: "library1",
"desc": "desc1 : Modern and spacious building"
},
{
loc_name: "library2",
"desc": "desc2 : A cosy small building"
}
]
}
};
function contentClass(isShow) {
if (isShow) {
return "content";
}
return "content invisible";
}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isShow: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(function (prevState) {
return { isShow: !prevState.isShow };
});
}
render() {
const libraries_desc = places.library.location.map((libr) =>
<div>
<p>{libr.desc}</p>
</div>
);
const lib_names = places.library.location.map((libr) =>
<div>
<p>{libr.loc_name}</p>
</div>
);
return (
<div>
<div className='control' onClick={this.handleClick}>
<h4>{lib_names}</h4>
<div className={contentClass(this.state.isShow)}>{libraries_desc}</div>
</div>
</div>
);
}
}
render((
<Toggle />
), document.getElementById('root'));
Current result:
library1
library2
desc1 : Modern and spacious building
desc 2 : A cosy small building
Desired Result:
library1
desc1 : Modern and spacious building (hidden but shown when clicked)
library2
desc 2 : A cosy small building (hidden but shown when clicked)
Codesandbox
I might try extracting a location into a separate component. By extracting it, each location is responsible for knowing its state. In your case, that means its visibility (controlled by this.state.isShow).
Here's how you could do it:
import React from 'react';
import { render } from 'react-dom';
var places = {
library: {
location: [
{
loc_name: "library1",
"desc": "Modern and spacious building"
},
{
loc_name: "library2",
"desc": "A cosy small building"
}
]
}
};
class Location extends React.Component {
constructor(props) {
super(props);
this.state = { isShow: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(function (prevState) {
return { isShow: !prevState.isShow };
});
}
contentClass(isShow) {
if (isShow) {
return "content";
}
return "content invisible";
}
render() {
return (
<div className='control' onClick={this.handleClick}>
<h4>{this.props.desc}</h4>
<div className={this.contentClass(this.state.isShow)}>{this.props.loc_name}</div>
</div>
)
}
}
class Toggle extends React.Component {
constructor(props) {
super(props);
}
render() {
const locations = places.library.location.map(location => {
return <Location {...location} />
})
return (
<div>
{locations}
</div>
);
}
}
render((
<Toggle />
), document.getElementById('root'));
Your Toggle Component should be like this.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
isShow: false,
id: -1, // initial value
};
}
handleClick = (id) => {
this.setState({
isShow: !this.state.isShow,
id: id
});
}
render() {
const { location } = places.library;
const { isShow, id } = this.state;
return (
<div className="control">
{location.map((libr, index) => (
<div key={index} onClick={() => { this.handleClick(index) }}>
<p>{libr.loc_name}</p>
{(isShow && (id === index)) && <p>{libr.desc}</p>}
</div>
))}
</div>
);
}
}
So when you click on the div element. A click event will be triggered called handleClick which will pass the index as a param to the function. which will set isShow to false or truth and vice versa along with the current element you want to show which will be selected through this.state.id. So everytime isShow is true and this.state.id matched index element of the array. Your description will show otherwise it will be hidden as you want.
So your desired result will be something like this.
library1
desc1 : Modern and spacious building (hidden but shown when clicked)
library2
desc 2 : A cosy small building (hidden but shown when clicked)