I am trying to create a simple pop up using React. In other words, when a user clicks on a button, a simple pop up should appear. However, with my current implementation I am getting a syntax error and I am unsure why. Any help would be much appreciated!
Here is my main file, where my mock_modal is called, e.g. where my popup is called
import PropTypes from 'prop-types';
import React from 'react';
...
class Calendar extends React.Component {
...
mockModalClick () {
}
hoverCustomSlot() {
this.setState({ hoverCustomSlot: !this.state.hoverCustomSlot });
}
render() {
const description = this.state.hoverCustomSlot ?
(<h4 className="custom-instructions">
Click, drag, and release to create your custom event
</h4>)
: null;
const addMockModal = this.props.registrarSupported ? (
<div className="cal-btn-wrapper">
<button
type="submit"
form="form1"
className="save-timetable add-button"
data-for="sis-btn-tooltip"
data-tip
onClick={this.state.seen ? <MockModal toggle={this.togglePop} /> : null}
>
<img src="/static/img/star.png" alt="SIS" style={{ marginTop: '2px' }} />
</button>
<ReactTooltip
id="sis-btn-tooltip"
class="tooltip"
type="dark"
place="bottom"
effect="solid"
>
<span>See my Mock Modal!</span>
</ReactTooltip>
</div>
) : null;
Here is the definition of my pop up
import React, { Component } from "react";
export default class PopUp extends Component {
handleClick = () => {
this.props.toggle();
};
render() {
return (
<div className="modal">
<div className="modal_content">
<span className="close" onClick={this.handleClick}>
×
</span>
<form>
<h3>Register!</h3>
<label>
Name:
<input type="text" name="name" />
</label>
<br />
<input type="submit" />
</form>
</div>
</div>
);
}
}
I have added ellipses and did not include other unnecessary code. The exact error that I am getting is:
ERROR in ./static/js/redux/ui/modals/mock_modal.jsx
web_1 | Module build failed: SyntaxError: Unexpected token (4:14)
export default class PopUp extends Component {
handleClick = () => {
this.props.toggle();
Try adding a const in front of handleClick.
Related
I am trying to achieve this using SPFX web part:-
The web part will render a Button and has a text field inside its settings.
The user add the Web Part >> edit it >> enter the text inside the text field (inside the setting page) >> save the web part>> then the web part will render a button >> if the user clicks on the button a popup will be shown with the entered text.
Now I found this link # https://www.c-sharpcorner.com/article/conecpt-of-react-portal-in-spfx/ which almost achieves what I am looking for, except that the Popup text inside the example is been hard-coded inside the .tsx file.. so what are the steps to make the Popup text configurable inside the web part settings instead of been hard-coded?
Thanks
Here is my ReactPortalWebPart.ts file:-
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '#microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '#microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '#microsoft/sp-webpart-base';
import * as strings from 'ReactPortalWebPartStrings';
import ReactPortal from './components/ReactPortal';
import { IReactPortalProps } from './components/IReactPortalProps';
export interface IReactPortalWebPartProps {
description: string;
}
export default class ReactPortalWebPart extends BaseClientSideWebPart<IReactPortalWebPartProps> {
public render(): void {
const element: React.ReactElement<IReactPortalProps> = React.createElement(
ReactPortal,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}
and here is the ReactPortal.tsx:-
import * as React from 'react';
import { IReactPortalProps } from './IReactPortalProps';
import Myportal from "./Myportal";
export default class ReactPortal extends React.Component<IReactPortalProps, {}> {
public render(): React.ReactElement<IReactPortalProps> {
return (
<div >
<Myportal/>
</div>
);
}
}
Here is the Myportal.tsx:-
import* as React from "react";
import usePortal from "react-cool-portal";
import "./mystyle.scss";
const Myportal = () => {
// const { Portal } = usePortal({ containerId: "my-portal-root" });
const { Portal, show, hide } = usePortal({ defaultShow: false,containerId:"my-portal-root" });
const handleClickBackdrop = (e: React.MouseEvent) => {
const { id } = e.target as HTMLDivElement;
if (id === "modal") hide();
};
return (
<div className="App">
<button className="btn" onClick={show} type="button">
Who we are
</button>
<button className="btn" onClick={show} type="button">
Our value
</button>
<Portal>
<div
id="modal"
className="modal"
onClick={handleClickBackdrop}
tabIndex={-1}
>
<div
className="modal-dialog"
role="dialog"
aria-labelledby="modal-label"
aria-modal="true"
>
<div className="modal-header">
<button
className="modal-close"
onClick={hide}
type="button"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<h1> Who we are</h1>
<h3>.................................................</h3>
Our overriding purpose is to dramatically improve the reliability, efficiency........
</div>
</div>
</div>
</Portal>
</div>
);
};
export default Myportal;
I think you need to re-write MyPortal as a React Component; bellow I'm tried written some example about this change that needed on MyPortal.tsx and ReactPortal.tsx, but I do not test then.
ReactPortal.tsx:
import * as React from 'react';
import { IReactPortalProps } from './IReactPortalProps';
import { IMyportalProps } from './IMyportalProps';
import Myportal from "./Myportal";
export default class ReactPortal extends React.Component<IReactPortalProps, {}> {
public render(): React.ReactElement<IReactPortalProps, IMyportalProps> {
return (
<div >
<Myportal title="Who we are" body="Our overriding purpose is to dramatically improve the reliability, efficiency........"/>
</div>
);
}
}
MyPortal.tsx
import* as React from "react";
import usePortal from "react-cool-portal";
import "./mystyle.scss";
export interface IMyportalProps {
title: string;
body: string
}
class Myportal extends React.Component
<IMyportalProps> {
constructor(props: IUnderstandStateComponentProps) {
super(props);
}
public render(): React.ReactElement
<IMyportalProps> {
const { Portal, show, hide } = usePortal({ defaultShow: false,containerId:"my-portal-root" });
const handleClickBackdrop = (e: React.MouseEvent) => {
const { id } = e.target as HTMLDivElement;
if (id === "modal") hide();
};
return (
<div className="App">
<button className="btn" onClick={show} type="button">
Who we are
</button>
<button className="btn" onClick={show} type="button">
Our value
</button>
<Portal>
<div
id="modal"
className="modal"
onClick={handleClickBackdrop}
tabIndex={-1}
>
<div
className="modal-dialog"
role="dialog"
aria-labelledby="modal-label"
aria-modal="true"
>
<div className="modal-header">
<button
className="modal-close"
onClick={hide}
type="button"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<h1>{this.props.title}</h1>
<h3>.................................................</h3>
{this.props.body}
</div>
</div>
</div>
</Portal>
</div>
);
};
My attempt :)
The idea is, you pass the "description" from the web part down to the dialog.
Please read the link above, how to pass properties in React from parent to the child components (or some basic React tutorial), it is highly recommended if you have chosen to work with SPFx using React.
Please note that an easier approach to create dialogs in SPFx coulod be, to use the Microsoft Fluent UI library, it works great with SPFx, has many tutorials, supports SharePoint themes, etc. In particular, it has the "Dialog" component built-in, among dozens of other useful components and controls.
Anyway, here we go with 'react-cool-portal'. Check out the comments in the code below.
ReactPortal.tsx
import * as React from 'react';
import { IReactPortalProps } from './IReactPortalProps';
import Myportal from "./Myportal";
export default class ReactPortal extends React.Component<IReactPortalProps, {}> {
// pass further the parameter that was received from the web part.
// The "description was passed to "ReactPortal" from WebPart with:
// React.createElement(
// ReactPortal,
// {
// description: this.properties.description
// }
public render(): React.ReactElement<IReactPortalProps> {
return (
<div>
<!-- pass the parameter down to the child component -->
<Myportal description={this.props.description} />
</div>
);
}
}
Myportal.tsx
import* as React from "react";
import usePortal from "react-cool-portal";
import "./mystyle.scss";
// accept properties (added "props" argument)
// and use it below in HTML {props.description}
const Myportal = (props) => {
// const { Portal } = usePortal({ containerId: "my-portal-root" });
const { Portal, show, hide } = usePortal({ defaultShow: false,containerId:"my-portal-root" });
const handleClickBackdrop = (e: React.MouseEvent) => {
const { id } = e.target as HTMLDivElement;
if (id === "modal") hide();
};
return (
<div className="App">
<button className="btn" onClick={show} type="button">
Who we are
</button>
<button className="btn" onClick={show} type="button">
Our value
</button>
<Portal>
<div
id="modal"
className="modal"
onClick={handleClickBackdrop}
tabIndex={-1}
>
<div
className="modal-dialog"
role="dialog"
aria-labelledby="modal-label"
aria-modal="true"
>
<div className="modal-header">
<button
className="modal-close"
onClick={hide}
type="button"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<h1> Who we are</h1>
<!-- use the property value somehow -->
<h3>{props.description}</h3>
</div>
</div>
</div>
</Portal>
</div>
);
};
export default Myportal;
You can achieve this in two ways.
Storing the popup content as part of the page property that is persisted once you edit the page
Storing the popup content in a list and fetching the data whenever the page is render. FYI, haven't fully thought about how to achieve it via this way.
I have updated relevant sections of the code you provided to show how to achieve the expected by storing the popup content as part of the page property.
Added another property to the props to store the popup content
export interface IReactPortalWebPartProps {
description: string;
content?: string;
}
Updated the render method of the component ReactPortalWebPart.ts
public render(): void {
const element: React.ReactElement<any> = React.createElement(
ReactPortal,
{
mode: this.displayMode, //used to determine if to display the text input or not based on the current state of the webpart.
content: this.properties.content, //the contents property of the webpart which is persisted in the page properties.
description: this.properties.description,
updateWebPartContent: this.updateWebPartContentProperty.bind(this) // the function that would be called from the portal component when the content is saved by the user
}
);
private updateWebPartContentProperty(htmlcontent) {
//console.log( htmlcontent )
//console.log( this.properties.content )
this.properties.content = htmlcontent;
this.onPropertyPaneFieldChanged( 'content', this.properties.content, htmlcontent );
//console.log( this.properties.content )
}
Passing the properties from ReactPortal to the Myportal functional component.
<Myportal
_mode={ this.props.mode }
_content={ this.props.content }
_updateWebPartContent={ this.props.updateWebPartContent }
/>
Updated the functional component with the props object, added the hook to save the input from the user and also using the current mode of the page to determine if the text area should be displayed or not.
const Myportal = (props) => {
const [ content, setContent ] = React.useState(props._content);
//abbreviated
return (
<div className="App">
{ props._mode == 2 && <div>
<textarea name="conent" id="conent" cols={30} rows={10} value={ props._content } onChange={ (evt) => { setContent(evt.target.value); } }
</textarea> <br />
<button className="btn" onClick={ () => { props._updateWebPartContent(content) } } type="button" >Save Content</button>
</div> }
<div className="modal-body">
{ props._content }
</div>
thank you for reading this. I am attempting to learn React by making a dummy website, however I've run into a roadblock.
I want the "display-page" div to only show the Send element initially (which is easy) but when someone clicks one of the 4 options from the content_bar div I want remove the current element and only show the newly clicked element (in this case it is 'Transactions')
I've read about useState and routing but I'm not sure how to implement
Thanks! Please let me know if I didnt give enough details
import React, { Component } from 'react';
import './Data.css';
import Transactions from './Transactions';
import Send from './Send';
class Data extends Component {
constructor(props) {
super(props);
this.state = {
content: <Send />
}
}
transactionpage = () => {
this.setState({content: <Transactions/>});
}
render() {
return(
<div className="content">
<div className="content_bar">
<h5>Send</h5>
<h5 onClick={this.transactionpage}>Transactions</h5>
<h5>Friends</h5>
<h5>Professional</h5>
</div>
<div className="display-page">
{this.state.content}
</div>
</div>
);
}
}
export default Data;
Looking at You can't press an <h5> tag and React code without state feels strange.
You need to learn more to achieve your goal, these are the topics:
JSX expresssion
Conditional rendering
State management
Let me show you my solution, it is one of many ways.
class Data extends Component {
constructor(props) {
super(props);
this.state = {
toDisplay: ''
};
this.changeToDisplay = this.changeToDisplay.bind(this);
}
changeToDisplay(e) {
this.setState({ toDisplay: e.target.textContent.toString() });
}
render() {
return (
<div className="content">
<div className="content_bar">
<button onClick={e => changeToDisplay(e)}>Send</button> <br />
<button onClick={e => changeToDisplay(e)}>Transactions</button> <br />
<button>Friends</button> <br />
<button>Professional</button> <br />
</div>
<div className="display-page">
{this.state.toDisplay === 'Send' ? <Send /> : null}
{this.state.toDisplay === 'Transactions' ? <Transactions /> : null}
</div>
</div>
);
}
}
I have an example of a toggle that hide/display content.
I used https://www.npmjs.com/package/react-animated-css and it worked perfectly when displaying the content, meaning that after showing the content the animation is playing.
Now that I press the toggle button, the content instantly vanishes without animation.
I checked in the console and the class for the animationOut is working, but it seems that the content closes before the animation have the time to play, so it's hidden.
How to fix this issue ?
The working code : https://stackblitz.com/edit/react-sorgz5?file=src/App.js
import React, { Component } from "react";
import ContentComponent from "./content.js";
import { Animated } from "react-animated-css";
export default class toggleComponent extends React.Component {
constructor() {
super();
this.state = {
isShowBody: false
};
}
handleClick = event => {
this.setState({ isShowBody: !this.state.isShowBody });
};
checkbox = () => {
return (
<div>
<span className="switch switch-sm">
<label>
<input
type="checkbox"
name="select"
onClick={this.handleClick.bind(this)}
/>
<span />
</label>
</span>
</div>
);
};
render() {
return (
<div>
{this.checkbox()}
<Animated
animationIn="bounceInLeft"
animationOut="fadeOut"
isVisible={this.state.isShowBody}
>
<div>{this.state.isShowBody && <ContentComponent />}</div>
</Animated>
</div>
);
}
}
Solved. I needed to remove this.state.isShowBody in <div>so the condition of the visibility is controller by isVisible.
<Animated
animationIn="bounceInLeft"
animationOut="fadeOut"
isVisible={this.state.isShowBody}
>
<div>{<ContentComponent />}</div>
</Animated>
I'm using react-bootstrap. I'm trying to display a list of cards. The list size can vary. But entire page becomes white after the component' Websites' is added at the bottom of 'Dashboard Page'. {this.state.page === 'websites' ? this.websites : null}
Websites Component
import * as React from 'react'
import { Card } from 'react-bootstrap'
import './Websites.css'
export default class Websites extends React.Component {
constructor(props) {
super(props)
this.websites = []
this.getWebsites()
}
getWebsites () {
// TODO: Need Implementation
var websites = this.makeTestWebsites()
for (var i = 0; i < websites.length; i++) {
this.websites.push(
<Card>
<Card.Body>
<Card.SubTitle>{websites[i].domain}</Card.SubTitle>
<Card.Title>{websites[i].name}</Card.Title>
<Card.SubTitle>{websites[i].websiteClass}</Card.SubTitle>
<Card.SubTitle>{websites[i].created}</Card.SubTitle>
</Card.Body>
</Card>
)
}
}
makeTestWebsites () {
return [
{
name: 'test domain',
domain: 'domain.com',
websiteClass: 'starter',
created: '2077-12-05'
},
{
name: 'hello domain',
domain: 'chicken.org',
websiteClass: 'premium',
created: '1996-02-12'
}
]
}
render () {
return (
<div>
{this.websites}
</div>
)
}
}
is called from DashboardPage
import * as React from 'react'
import { Button } from 'react-bootstrap'
import './dashboardStyle.css'
import Websites from './Websites'
export default class DashboardPage extends React.Component {
constructor(props) {
super(props)
this.state = {
page: 'websites'
}
this.websites = <Websites />
this.switchToWebsites()
}
switchToWebsites () {
this.setState({ page: 'websites' })
}
render () {
return (
<div>
<div className='sidemenu'>
<Button variant='primary' className='sidenav-button'>
<img
alt=''
src='/logo/xxxLogo.png'
width='30'
height='30'
className='sidenav-button-icon'
onClick={this.showLandingPage}
/>
Websites
</Button>
<Button variant='primary' className='sidenav-button'>
<img
alt=''
src='/logo/xxxLogo.png'
width='30'
height='30'
className='sidenav-button-icon'
onClick={this.showLandingPage}
/>
Email
</Button>
<Button variant='primary' className='sidenav-button'>
<img
alt=''
src='/logo/xxxLogo.png'
width='30'
height='30'
className='sidenav-button-icon'
onClick={this.showLandingPage}
/>
How To Use
</Button>
<Button variant='primary' className='sidenav-button'>
<img
alt=''
src='/logo/xxxLogo.png'
width='30'
height='30'
className='sidenav-button-icon'
onClick={this.showLandingPage}
/>
Support
</Button>
</div>
<div className='right-menu'>
{this.state.page === 'websites' ? this.websites : null}
</div>
</div>
)
}
}
Sorry for little details. I'm new and don't know what info you need. If you need any details please comment. Thank you.
Edit: Error log from console
Uncaught Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Try this:
Websites Component
import React from "react";
import { Card } from "react-bootstrap";
import "./Websites.css";
const testWebsites = [
{
name: "test domain",
domain: "domain.com",
websiteClass: "starter",
created: "2077-12-05"
},
{
name: "hello domain",
domain: "chicken.org",
websiteClass: "premium",
created: "1996-02-12"
}
];
export default class Websites extends React.Component {
constructor(props) {
super(props);
// TODO: Need Implementation
this.state = {
websites: testWebsites
};
}
render() {
return (
<div>
{this.state.websites.map(website => (
<Card key={website.name}>
<Card.Body>
<Card.SubTitle>{website.domain}</Card.SubTitle>
<Card.Title>{website.name}</Card.Title>
<Card.SubTitle>{website.websiteClass}</Card.SubTitle>
<Card.SubTitle>{website.created}</Card.SubTitle>
</Card.Body>
</Card>
))}
</div>
);
}
}
Dashboard Component
import React from "react";
import { Button } from "react-bootstrap";
import "./dashboardStyle.css";
import Websites from "./Websites";
export default class DashboardPage extends React.Component {
constructor(props) {
super(props);
this.state = {
page: "websites"
};
}
showLandingPage = () => {
this.setState({
page: "landing"
});
};
showWebsitesPage = () => {
this.setState({
page: "websites"
});
};
render() {
return (
<div>
<div className="sidemenu">
<Button
variant="primary"
className="sidenav-button"
onClick={this.showLandingPage}
>
<img
alt=""
src="/logo/BornBee Logo.png"
width="30"
height="30"
className="sidenav-button-icon"
/>
Websites
</Button>
<Button
variant="primary"
className="sidenav-button"
onClick={this.showLandingPage}
>
<img
alt=""
src="/logo/BornBee Logo.png"
width="30"
height="30"
className="sidenav-button-icon"
/>
Email
</Button>
<Button
variant="primary"
className="sidenav-button"
onClick={this.showLandingPage}
>
<img
alt=""
src="/logo/BornBee Logo.png"
width="30"
height="30"
className="sidenav-button-icon"
/>
How To Use
</Button>
<Button
variant="primary"
className="sidenav-button"
onClick={this.showLandingPage}
>
<img
alt=""
src="/logo/BornBee Logo.png"
width="30"
height="30"
className="sidenav-button-icon"
/>
Support
</Button>
</div>
<div className="right-menu">
{this.state.page === "websites" ? <Websites /> : null}
</div>
</div>
);
}
}
I made a few notable changes to your code.
Websites
Move your test data outside of the component
Use this.state instead of class properties
map over the data array. Note: the key prop must be unique (source)
Change your React import. I'm not sure if the wildcard import was a bug, but I've never seen it done like that.
Dashboard
Don't call setState in the constructor (source)
Don't store your component in a property variable
I moved your onClick to the <Button> component because that makes more sense to me. But, you can move it back to the img if you prefer or need it there.
Made your class methods arrow functions to bind this
One last thing I'd suggest is that, instead of manually handling the page routing like you're doing, perhaps use React Router.
I'm implementing this library : https://github.com/felixrieseberg/React-Dropzone-Component
To trigger another component or element programmatically I can use refbut I got an error of photoUploadDropAreaElement is not a function using below code.
triggerUploadDialog(){
this.photoUploadDropAreaElement.click(); // doesn't work?
console.log(this.photoUploadDropAreaElement);
}
render() {
return(
<div onClick={this.triggerUploadDialog.bind(this)}>
<DropzoneComponent ref={dropArea => this.photoUploadDropAreaElement = dropArea} />
</div>
);
The result of DropzoneComponent look like this
What's wrong here? I just want to trigger a click to open the file dialog for user to select file to upload.
I'm using import * as Dropzone from 'react-dropzone'; via npm install react-dropzone --save-dev. Go here for the details.
This dropzone package allows you to, by default, click on the UI's dropzone to open the file dialog for user to select a file to upload.
Here is the code I used, which includes a 'Choose File' button as well as a 'Delete' button. *Note: multiple={false} disables the user's ability to choose multiple files. You can simply change it to true and the multiple file selection will be enabled.
import * as React from 'react';
import * as Dropzone from 'react-dropzone';
export interface FileUploadState { file: Array<File> }
export class FileUpload extends React.Component<{}, FileUploadState> {
constructor(props: any) {
super(props);
this.state = {
file: []
}
}
onDrop(droppedFile: any) {
if (droppedFile && droppedFile.preview) {
window.URL.revokeObjectURL(droppedFile.preview);
}
this.setState({
file: droppedFile
});
console.log(droppedFile);
}
onDelete() {
this.setState({
file: []
});
}
render() {
let dropzoneRef: any;
return (
<div>
<div>
<Dropzone ref={(node) => { dropzoneRef = node; }} onDrop={this.onDrop.bind(this)} multiple={false}>
<div className="text-center">Drag and drop your file here, or click here to select file to upload.</div>
</Dropzone>
<button type="button" className="button primary" onClick={(e) => {
e.preventDefault(); // --> without this onClick fires 3 times
dropzoneRef.open();
}}>
Choose File(s)
</button>
<button type="button" className="button alert" onClick={this.onDelete.bind(this)}>
Delete
</button>
</div>
<hr />
<div>
<h2>File(s) to be uploaded: {this.state.file.length} </h2>
<ul>
{
this.state.file.map(f => <li><code>{f.name}</code></li>)
}
</ul>
</div>
</div>
)
}
}
For anyone still looking, it looks like the library was updated to expose an open function.
https://github.com/react-dropzone/react-dropzone/commit/773eb660c7848dd1d67e6e13c7f7261eaaa9fd4d
You can use it this way via refs:
dropzoneRef: any;
onClickPickImage = () => {
if (this.dropzoneRef) {
this.dropzoneRef.open();
}
};
// When rendering your component, save a ref
<Dropzone
ref={(ref: any) => {
this.dropzoneRef = ref;
}}
onDrop={this.onDrop}
>
<div onClick={this.onClickPickImage}>Trigger manually</div>
</Dropzone>
Try like this. It's work for me
triggerUploadDialog () {
this.photoUploadDropAreaElement.dropzone.element.click()
}
Component
<div onClick={this.triggerUploadDialog.bind(this)}>
<DropzoneComponent ref={dropArea => this.photoUploadDropAreaElement = dropArea} />
</div>
My problem was NOT including the input element. When I did, it worked.