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>
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.
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 am trying to get my form to upload several files, but once I upload the first one, I have no chance to load a second one. Any Idea what I am doing wrong?
This is my upload component:
import React, { Component } from 'react'
import * as RB from 'react-bootstrap'
import Button from 'components/Button/Button'
class uploadMob extends Component {
constructor(props) {
super(props)
this.state = {
files: [],
}
}
onFilesAdded = (e) => {
const filesArray = this.state.files
filesArray.push(e.target.files[0])
this.setState({ files: filesArray })
this.uploadFiles()
}
async uploadFiles() {
this.state.files.forEach((file) => {
this.sendRequest(file)
})
}
sendRequest(file) {
const { pdfUploadToState } = this.props
pdfUploadToState(file)
}
render() {
const files = this.state.files
return (
<RB.Form.Group>
<div className="upload-btn-wrapper">
<div className="Files">
{files.map((file, key) => {
return (
<div key={key} className="Row">
<span className="Filename">
{file.name}
</span>
</div>
)
})}
</div>
<Button size="sm" variant="light">
Dateien hochladen
</Button>
<input
type="file"
name="files"
id="files"
onChange={(e) => {
this.onFilesAdded(e)
}}
multiple
/>
</div>
</RB.Form.Group>
)
}
}
export default uploadMob
The first file is uploaded perfectly, but as mentioned, the button does not respond when trying to upload a second one.
Thanks for the help!
Your code seems correct but when you use input type file with multiple attribute you need to select multiple files and then hit upload button insted of selecting files one by one.
also replace
filesArray.push(e.target.files[0])
with
for (var i = 0; i < files.length; i++)
{
filesArray.push(e.target.files[i]);
}
to upload file one by one
replace
onFilesAdded = (e) =>
{
this.state.files.push(e.target.files[0])
this.uploadFiles()
}
hope this will help you
I have a react component in which user can upload Image and he's also shown the preview of uploaded image. He can delete the image by clicking delete button corresponding to Image. I am using react-dropzone for it. Here's the code:
class UploadImage extends React.Component {
constructor(props) {
super(props);
this.onDrop = this.onDrop.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.state = {
filesToBeSent: [],
filesPreview: [],
printCount: 10,
};
}
onDrop(acceptedFiles, rejectedFiles) {
const filesToBeSent = this.state.filesToBeSent;
if (filesToBeSent.length < this.state.printCount) {
this.setState(prevState => ({
filesToBeSent: prevState.filesToBeSent.concat([{acceptedFiles}])
}));
console.log(filesToBeSent.length);
for (var i in filesToBeSent) {
console.log(filesToBeSent[i]);
this.setState(prevState => ({
filesPreview: prevState.filesPreview.concat([
<div>
<img src={filesToBeSent[i][0]}/>
<Button variant="fab" aria-label="Delete" onClick={(e) => this.deleteImage(e,i)}>
<DeleteIcon/>
</Button>
</div>
])
}));
}
} else {
alert("you have reached the limit of printing at a time")
}
}
deleteImage(e, id) {
console.log(id);
e.preventDefault();
this.setState({filesToBeSent: this.state.filesToBeSent.filter(function(fid) {
return fid !== id
})});
}
render() {
return(
<div>
<Dropzone onDrop={(files) => this.onDrop(files)}>
<div>
Upload your Property Images Here
</div>
</Dropzone>
{this.state.filesToBeSent.length > 0 ? (
<div>
<h2>
Uploading{this.state.filesToBeSent.length} files...
</h2>
</div>
) : null}
<div>
Files to be printed are: {this.state.filesPreview}
</div>
</div>
)
}
}
export default UploadImage;
My Question is my component is not re-rendering even after adding or removing an Image. Also, I've taken care of not mutating state arrays directly. Somebody, please help.
Try like this, I have used ES6
.