I am building this custom wrapper block in Gutenberg (Based on create-guten-block).
It works fine on its own, but wont save the Innerblocks! So when I add a new block in the editor, it shows up fine, but when I save and refresh, the innerblock is gone! (the wrapper is still there with the saved attributes).
Can anyone of you clever people tell me why?
MY SECTION BLOCK (Wrapper):
import './style.scss';
import './editor.scss';
const {__} = wp.i18n; // Import __() from wp.i18n
const {registerBlockType} = wp.blocks; // Import registerBlockType() from wp.blocks
const { ColorPalette, InnerBlocks, MediaUpload, InspectorControls } = wp.editor;
const {Button, RangeControl, PanelBody, PanelRow, RadioControl } = wp.components; // used in Inspector controls
registerBlockType('my-blocks/my-section', {
title: __('my Section'), // Block title.
icon: 'welcome-add-page',
category: 'common',
keywords: [
__('my Section'),
__('my Block'),
],
attributes: {
overlayOpacity: {
type: 'number',
default: 40
},
sectionBackgroundImage: {
type: 'string',
default: null
},
sectionBgColor: {
type: 'string',
default: '#e2e2e2',
},
sectionLineColor: {
type: 'string',
default: 'gray', //rgba(194,194,194,0.8)
}
},
edit: props => {
const {
setAttributes,
attributes,
className
} = props;
const {overlayOpacity, sectionBackgroundImage, sectionBgColor, sectionLineColor} = props.attributes;
return ([
<InspectorControls>
/*This code is edited out to save space*/
</InspectorControls>,
<section className={[className + ' page-section-top page-section-bottom ' + sectionLineColor]}
style={{
backgroundColor: sectionBgColor,
backgroundImage: `url(${sectionBackgroundImage})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
}}
>
<div className="page-section-image-dimmer"
style={{opacity: overlayOpacity / 100}}
/>
<InnerBlocks />
</section>
]);
},
save: props => {
const {
className
} = props;
const {overlayOpacity, sectionBackgroundImage, sectionBgColor, sectionLineColor} = props.attributes;
return ([
<section className={[className + ' page-section-top page-section-bottom ' + sectionLineColor]}
style={{
backgroundColor: sectionBgColor,
backgroundImage: `url(${sectionBackgroundImage})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
}}
>
<div className="page-section-image-dimmer"
style={{opacity: overlayOpacity / 100}}
/>
<InnerBlocks.Content />
</section>
]);
}
});
Actually... It seems my code is correct. Somehow it was the create-guten-block setup that was buggy. I did a fresh install and copied the above code in to a new custom block, and now it works :)
Related
I am trying to build a React.js SharePoint modern web part, which have the following capabilities:-
Inside the Web Part settings page >> there are 2 fields named as "Who We Are" & "Our Value" which allow the user to enter HTML.
The web part will render 2 buttons "Who We Are" & "Our Value" >> and when the user clicks on any button >> a Popup will be shown with the entered HTML code in step-1
Something as follow:-
But to be able to render HTML code as Rich-Text inside my Web Part, i have to use the dangerouslySetInnerHTML attribute inside the .tsx file. as follow:-
import * as React from 'react';
import { useId, useBoolean } from '#fluentui/react-hooks';
import {
getTheme,
mergeStyleSets,
FontWeights,
Modal,
IIconProps,
IStackProps,
} from '#fluentui/react';
import { IconButton, IButtonStyles } from '#fluentui/react/lib/Button';
export const MYModal2 = (myprops) => {
const [isModalOpen, { setTrue: showModal, setFalse: hideModal }] = useBoolean(false);
const [isPopup, setisPopup] = React.useState(true);
const titleId = useId('title');
React.useEffect(() => {
showModal();
}, [isPopup]);
function ExitHandler() {
hideModal();
setisPopup(current => !current)
myprops.handler();
}
return (
<div>
<Modal
titleAriaId={titleId}
isOpen={isModalOpen}
onDismiss={ExitHandler}
isBlocking={true}
containerClassName={contentStyles.container}
>
<div className={contentStyles.header}>
<span id={titleId}>Modal Popup</span>
<IconButton
styles={iconButtonStyles}
iconProps={cancelIcon}
ariaLabel="Close popup modal"
onClick={ExitHandler}
/>
</div>
<div className={contentStyles.body}>
<p dangerouslySetInnerHTML={{__html:myprops.OurValue}}>
</p>
</div>
</Modal>
</div>
);
};
const cancelIcon: IIconProps = { iconName: 'Cancel' };
const theme = getTheme();
const contentStyles = mergeStyleSets({
container: {
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'stretch',
},
header: [
// eslint-disable-next-line deprecation/deprecation
theme.fonts.xLarge,
{
flex: '1 1 auto',
borderTop: '4px solid ${theme.palette.themePrimary}',
color: theme.palette.neutralPrimary,
display: 'flex',
alignItems: 'center',
fontWeight: FontWeights.semibold,
padding: '12px 12px 14px 24px',
},
],
body: {
flex: '4 4 auto',
padding: '0 24px 24px 24px',
overflowY: 'hidden',
selectors: {
p: { margin: '14px 0' },
'p:first-child': { marginTop: 0 },
'p:last-child': { marginBottom: 0 },
},
},
});
const stackProps: Partial<IStackProps> = {
horizontal: true,
tokens: { childrenGap: 40 },
styles: { root: { marginBottom: 20 } },
};
const iconButtonStyles: Partial<IButtonStyles> = {
root: {
color: theme.palette.neutralPrimary,
marginLeft: 'auto',
marginTop: '4px',
marginRight: '2px',
},
rootHovered: {
color: theme.palette.neutralDark,
},
};
And to secure the dangerouslySetInnerHTML, i did the following steps:-
1- Inside my Node.Js CMD >> i run this command inside my project directory:-
npm install dompurify eslint-plugin-risxss
2- Then inside my above .tsx i made the following modifications:-
I added this import import { sanitize } from 'dompurify';
An I replaced this unsafe code <p dangerouslySetInnerHTML={{__html:myprops.OurValue}}></p> with this <div dangerouslySetInnerHTML={{ __html: sanitize(myprops.OurValue) }} />
So I have the following question:-
Now my approach (of using sanitize(myprops.OurValue) will/should securely render HTML as Rich-Text inside the popup since i am using the sanitize function which is part of the dompurify eslint-plugin-risxss. but i read another approach which mentioned that to securely render HTML as Rich-Text inside the popup, we can use the html-react-parser package as follow {parse(myprops.OurValue)}. So what are the differences between using 'html-react-parser' & using 'dompurify eslint-plugin-risxss' to securely render an HTML code as Rich-Text inside the React web part's popup?
Here is my Full web part code:-
inside the MyModalPopupWebPart.ts:-
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 'MyModalPopupWebPartStrings';
import MyModalPopup from './components/MyModalPopup';
import { IMyModalPopupProps } from './components/IMyModalPopupProps';
export interface IMyModalPopupWebPartProps {
description: string;
WhoWeAre: string;
OurValue:string;
}
export default class MyModalPopupWebPart extends BaseClientSideWebPart<IMyModalPopupWebPartProps> {
public render(): void {
const element: React.ReactElement<IMyModalPopupProps> = React.createElement(
MyModalPopup,
{
description: this.properties.description,
WhoWeAre: this.properties.WhoWeAre,
OurValue: this.properties.OurValue
}
);
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('WhoWeAre', {
label: "who We Are",
multiline: true
}),
PropertyPaneTextField('OurValue', {
label: "Our value"
}), PropertyPaneTextField('description', {
label: "Description",
multiline: true
}),
]
}
]
}
]
};
}
}
inside the MyModalPopup.tsx:-
import * as React from 'react';
import { IMyModalPopupProps } from './IMyModalPopupProps';
import { DefaultButton } from '#fluentui/react/lib/Button';
import { MYModal } from './MYModal';
import { MYModal2 } from './MYModal2';
interface IPopupState {
showModal: string;
}
export default class MyModalPopup extends React.Component<IMyModalPopupProps, IPopupState> {
constructor(props: IMyModalPopupProps, state: IPopupState) {
super(props);
this.state = {
showModal: ''
};
this.handler = this.handler.bind(this);
this.Buttonclick = this.Buttonclick.bind(this);
}
handler() {
this.setState({
showModal: ''
})
}
private Buttonclick(e, whichModal) {
e.preventDefault();
this.setState({ showModal: whichModal });
}
public render(): React.ReactElement<IMyModalPopupProps> {
const { showModal } = this.state;
return (
<div>
<DefaultButton onClick={(e) => this.Buttonclick(e, 'our-value')} text="Our Value" />
{ showModal === 'our-value' && <MYModal2 OurValue={this.props.OurValue} myprops={this.state} handler={this.handler} />}
<DefaultButton onClick={(e) => this.Buttonclick(e, 'who-we-are')} text="Who We Are" />
{ showModal === 'who-we-are' && <MYModal WhoWeAre={this.props.WhoWeAre} myprops={this.state} handler={this.handler} />}
</div>
);
}
}
Actually, html-react-parser returns ReactJs object, and its return type is like React.createElement or like type of called JSX.
Using DOMPurify.sanitize will return safe pure HTML elements which those are different to the object that html-react-parser returns. the risxss ESLint plugin will force you to use sanitizing with any kind of sanitize function or library, that I left an answer to your other question to how to Sanitize your string HTML.
Eventually, using sanitizing is better because is the html-react-parser will convert your string HTML to ReactJs object with some tiny changes that would be dangerous because it is possible to have some script of string HTML in the project and it maybe will be harmful it just remove the onclick or onload, etc, from HTML tags but sanitizing will remove all possible harmful tags. also sanitizing will receive configuration, which means you can have your own options for sanitizing.
I am kind of new to react & TypeScript.
I am trying to modify some functionality but having hard time doing that.
Currently we show all the subscriptions and all the transactions on the same page instead what I want to do is that based on the subscription selection i want show all the transactions (Currently both subscriptions and transactions are siblings).
I have created an on click event on subscription which is calling handleViewTransaction
The problem is that I am not able to re-render TransactionsTable from the handleViewTransaction.
Do I need to make it parent-child or there is a way to achieve it without much code refactoring?
import * as React from 'react'
import {RouteComponentProps} from 'react-router-dom'
import * as _ from 'lodash'
import * as cx from 'classnames'
import {Accordion, AccordionItem} from 'react-light-accordion'
import * as dcbApi from '../../../components/dcbApi'
import {Page} from '../../components/page'
import {Loader} from '../../components/loader'
import {Modal} from '../../components/modal'
import {SubscriptionStore, SubscriptionStoreState, useSubscriptionStore} from '../../../stores/subscription'
import {TransactionsTable} from '../../components/tables/transactions'
import {SubscriptionsTable} from '../../components/tables/subscriptions'
import {TransactionsPage} from './transactions'
import {BokuSubscription, BokuTransaction} from '../../../types'
export interface ResultPageProps extends RouteComponentProps<{}> {
userId?: string
subscriptions?: BokuSubscription[]
transactions?: BokuTransaction[]
isLoading?: boolean
onRefetch?: () => any
}
export const ResultPage = ({ isLoading: isLoadingParent, history, userId, subscriptions, transactions, onRefetch }: ResultPageProps) => {
const [isLoading, setIsLoading] = React.useState(false);
const [subscriptionCancellationSent, setSubscriptionCancellationSent] = React.useState(false);
const [transactionCancellationSent, setTransactionCancellationSent] = React.useState(false);
const [selectedTransaction, setSelectedTransaction] = React.useState(null);
const hasTransactions = false;
const navigateHome = () =>
history.push('/');
const handleViewTransaction = async (daznUserId: string, subscriptionId: string, aggregator: string) => {
// TODO: Show subscription specific transactions
};
const handleSubsctiptionCancel = async (userId: string, subscriptionId: string) => {
try {
setIsLoading(true);
const response = await dcbApi.cancelSubscription(userId, 'boku', subscriptionId);
setIsLoading(false);
setSubscriptionCancellationSent(true);
Modal.info({
title: `Your cancellation request has been submited for ${userId}`,
okText: 'OK',
content: (
<div>
<span>Transaction Id:</span> {response.transactionId}<br/>
<span>Subscription Id:</span> {response.subscriptionId}<br/>
<span>Aggregator:</span> {response.aggregator}<br/>
<span>Status:</span> {response.status}<br/>
</div>
),
})
//this.setState({ cancellationSent: true })
} catch (err) {
setIsLoading(false);
console.error(err);
Modal.info({
title: `Your cancellation request for ${userId} has failed`,
okText: 'OK',
content: (
<div>
{_.get(err, 'message') && <div>Error message: {err.message}</div>}
<Accordion atomic={true}>
<AccordionItem title='Toggle detailed error'>
<pre>{JSON.stringify(err, null, 2)}</pre>
</AccordionItem>
</Accordion>
</div>
),
})
} finally {
if (typeof onRefetch === 'function') {
onRefetch()
}
}
};
const handleChargeRefund = async (userId: string, chargeId: string, reasonCode: number) => {
try {
setIsLoading(true);
const response = await dcbApi.refund(userId, 'boku', chargeId, reasonCode)
setIsLoading(false);
setTransactionCancellationSent(true);
Modal.info({
title: `Your refund request has been submited`,
okText: 'OK',
content: (
<div>
<span>Charge Id:</span> {response.chargeId}<br/>
<span>Country:</span> {response.country}<br/>
<span>Refund status:</span> {response.refundStatus}<br/>
<span>Refund Id:</span> {response.refundId}<br/>
</div>
),
})
//this.setState({ cancellationSent: true })
} catch (err) {
setIsLoading(false);
console.error(err);
Modal.info({
title: `Your refund request has failed`,
okText: 'OK',
content: (
<div>
{_.get(err, 'message') && <div>Error message: {err.message}</div>}
<Accordion atomic={true}>
<AccordionItem title='Toggle detailed error'>
<pre>{JSON.stringify(err, null, 2)}</pre>
</AccordionItem>
</Accordion>
</div>
),
})
} finally {
if (typeof onRefetch === 'function') {
onRefetch()
}
}
};
return (
<Page
theme='dark' // light|dark
title='Back to home'
onLogoClick={navigateHome}
onTitleClick={navigateHome}
overlay={(!isLoading && !isLoadingParent) ? undefined :
<div style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
background: 'rgba(130,131,134,.34)'
}}>
<div style={{
textAlign: 'center',
position: 'relative',
top: '50%',
left: '50%',
transform: 'translate(-50%,-50%)'
}}>
<Loader/><br/><br/>
<span style={{
color: 'white',
fontSize: 22,
fontFamily: 'Trim',
textAlign: 'center'
}}>Loading</span>
</div>
</div>}
>
<div style={{width: '100%'}}>
<div
style={{
textAlign: 'center',
marginBottom: 24,
fontFamily: 'Trim',
fontWeight: 500,
fontSize: '26px'
}}
>Subscription statuses for user: {userId}</div>
<SubscriptionsTable
userId={userId}
data={subscriptions}
cancellationSent={subscriptionCancellationSent}
onCancelSubscription={handleSubsctiptionCancel}
onViewTransaction={handleViewTransaction}
/>
<div id="transactions">
<div
style={{
textAlign: 'center',
marginTop: 24,
marginBottom: 24,
fontFamily: 'Trim',
fontWeight: 500,
fontSize: '26px',
}}
>
{hasTransactions ?
`Transactions for user: ${userId}` :
<i>This user does not have transactions linked to their subscription</i>}
</div>
{hasTransactions &&
<TransactionsTable
userId={userId}
data={transactions}
cancellationSent={transactionCancellationSent}
onRefundCharge={handleChargeRefund}
/>}
</div>
</div>
</Page>
)
};
What if you add a filtered state inside ResultPage and pass this to TransactionsTable? handleViewTransaction will do the filtering depending on subscriptionId. Some "pseudo code":
First, set default with all transactions.
const [filteredTransactions, setFilteredTransactions] = useState(transactions);
In the handleViewTransactions, do something like:
const filteredData = transactions.filter((item) => item.subscriptionId === subscriptionId);
setFilteredTransactions(filteredData);
The TransactionsTable component will use the filtered transactions instead:
<TransactionsTable
...
data={filteredTransactions}
...
/>
The only way to make a component rerender is to have its state or its props change, or an ancestor rerenders. With that in mind, if you want the sibling of a component to rerender when something happens, the best solution is to make the common parent update its state, and pass some value into the sibling.
For your type of scenario, you probably want to hold the selection state in the parent, and pass the value and setter functions down into the children as necessary
I have an Card Component. It contains an image and text. By default, the image will be an redImage and the text are black. Onmouseover on that card, the default redimage should change to whiteimage and the text need to change into white color. I am displaying the card contents using map method.
Now, i can change the color, while mouseover using css, but, i can't change the image properly. But, i can change the image on hover, if i am not using the map function by hardcoding the content directly in the component. After using map method only, i am facing the issue. Please let me know, how can I achieve that. Please find my code below.
/***App***/
import React,{ Component } from 'react';
import SchDic from './SchDic'
class App extends Component {
constructor(props){
super(props);
this.state ={
Lists : [
{id:1, imgRed:require('../assets/images/red/das-red.svg'), imgWhite: require('../assets/images/white/das-whi.svg'),
Name: 'All-in-1 Dashboard',
Details:'Easily navigate the simple-to-use dashboard to track your volunteers, manage your opportunities and relationships, and gain access to advanced reporting.'},
{id:2, imgRed:require('../assets/images/red/scr-red.svg'), imgWhite: require('../assets/images/white/dig-whi.svg'),
Name: 'Screening Organizations',
Details:'Control the opportunities visible to your students by screening organizations. Invite your partnered nonprofits.' },
{id:3, imgRed:require('../assets/images/red/dig-red.svg'), imgWhite: require('../assets/images/white/pos-whi.svg'),
Name: 'Digitize Submission',
Details:'View all your student submissions to see what’s pending, approved or rejected.'},
{id:4, imgRed:require('../assets/images/red/tra-red.svg'), imgWhite: require('../assets/images/white/scr-whi.svg'),
Name: 'Tracking & Reporting',
Details:'Get up-to-date reports about how students are progressing with their commitments or requirements. Gain access to customizable analytics about individuals or groups of students.'},
{id:5, imgRed:require('../assets/images/red/pos-red.svg'), imgWhite: require('../assets/images/white/sup-whi.svg'),
Name: 'Post School-Run Events',
Details:'Get administration involved by postings school-run volunteering events directly on your private Opportunity Board..'},
{id:6, imgRed:require('../assets/images/red/sup-red.svg'), imgWhite: require('../assets/images/white/tra-whi.svg'),
Name: 'Support',
Details:'Get access to tons of resources on our FAQ or contact our team directly. We take pride in our commitment in helping you build your community.'},
],
};
}
render() {
return (
<div className="App" >
<SchDic Lists = {this.state.Lists}/>
</div>
);
}
}
export default App;
/***SchDiv***/
import React,{ Component } from 'react';
import { Card,CardMedia,CardHeader,CardContent,Typography,withStyles } from '#material-ui/core';
const Styles = {
card: {
width:'385px',
height:'241px',
padding:'0px',
margin:'15px',
cursor: 'pointer',
'&:hover': {
background: '#E8583E',
color:'white',
}
},
media: {
height: 41,
maxWidth:41,
margin:'15px',
},
name:{
padding:'1px',
margin:'15px',
},
details:{
fontSize: '14px',
padding: '0 15px',
minHeight: '25px',
align: 'left',
},
};
class SchDic extends Component {
constructor(props){
super(props);
this.state = {
value: 0,
img: require('../assets/images/red/das-red.svg'),
imgOne: require('../assets/images/red/dig-red.svg'),
imgTwo: require('../assets/images/red/pos-red.svg'),
imgThree: require('../assets/images/red/scr-red.svg'),
imgFour: require('../assets/images/red/sup-red.svg'),
imgFive: require('../assets/images/red/tra-red.svg'),
};
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseOut = this.handleMouseOut.bind(this);
}
handleChange = (event, value) => {
this.setState({ value });
};
handleMouseOver(val) {
if(val === 0){
this.setState({
img: require('../assets/images/white/das-whi.svg')
});
} else if(val === 1){
this.setState({
imgOne: require('../assets/images/white/dig-whi.svg')
});
} else if(val === 2){
this.setState({
imgTwo: require('../assets/images/white/pos-whi.svg')
});
} else if(val===3){
this.setState({
imgThree: require('../assets/images/white/scr-whi.svg')
});
} else if(val===4){
this.setState({
imgFour: require('../assets/images/white/sup-whi.svg')
});
} else {
this.setState({
imgFive: require('../assets/images/white/tra-whi.svg')
});
}
}
handleMouseOut(val) {
this.setState({
img: require('../assets/images/red/das-red.svg'),
imgOne: require('../assets/images/red/dig-red.svg'),
imgTwo: require('../assets/images/red/pos-red.svg'),
imgThree: require('../assets/images/red/scr-red.svg'),
imgFour: require('../assets/images/red/sup-red.svg'),
imgFive: require('../assets/images/red/tra-red.svg'),
});
}
render(){
const { classes }= this.props
const { Lists } = this.props;
const Post = Lists.map(List => {
return(
<div >
<Card className={classes.card} onMouseOver={() => this.handleMouseOver(List.id)} onMouseOut={this.handleMouseOut} elevation={1}>
<CardMedia
className={classes.media}
image={List.imgRed}
/>
<CardHeader className={classes.name}
titleTypographyProps={{variant:'h5' }}
title={List.Name}
/>
<CardContent className={classes.details} >
<Typography variant='Body2' color=" " component="p">
{List.Details}
</Typography>
</CardContent>
</Card>
</div>
)}
);
const divStyle = {
paddingLeft:'350px',
paddingRight:'150px',
display: 'flex',
flexWrap: 'wrap',
};
return(
<div className="coreFeatures" style={divStyle} >
{ Post }
</div>
)
}
}
export default withStyles(Styles)(SchDic);
"well i also stuck in the similar problem
but i got the solution which really works
just create an image folder in public folder of ur react project
now i created a tag in one of the react component as:
<img src= {process.env.PUBLIC_URL + '/image/xyz.png'} />
now i want this image to change by using mouseover listener
<img src= {process.env.PUBLIC_URL + '/image/xyz.png'} onMouseOver={() => this.changeImg()}/>
i defined the changeImg function as:
changeLogo= () => { var a= document.querySelector('.logoA');
a.setAttribute("src",'./images/logoB.svg')
}
but the problem is ...(just read this post)
https://facebook.github.io/create-react-app/docs/using-the-public-folder "
Answer For My Question,
import React,{ Component } from 'react';
import SchDic from './SchDic'
class App extends Component {
constructor(props){
super(props);
this.state ={
Lists : [
{id:1, imgRed:require('../assets/images/red/das-red.svg'), imgWhite: require('../assets/images/white/das-whi.svg'),
Name: 'All-in-1 Dashboard',
Details:'Easily navigate the simple-to-use dashboard to track your volunteers, manage your opportunities and relationships, and gain access to advanced reporting.'},
{id:2, imgRed:require('../assets/images/red/scr-red.svg'), imgWhite: require('../assets/images/white/dig-whi.svg'),
Name: 'Screening Organizations',
Details:'Control the opportunities visible to your students by screening organizations. Invite your partnered nonprofits.' },
{id:3, imgRed:require('../assets/images/red/dig-red.svg'), imgWhite: require('../assets/images/white/pos-whi.svg'),
Name: 'Digitize Submission',
Details:'View all your student submissions to see what’s pending, approved or rejected.'},
{id:4, imgRed:require('../assets/images/red/tra-red.svg'), imgWhite: require('../assets/images/white/scr-whi.svg'),
Name: 'Tracking & Reporting',
Details:'Get up-to-date reports about how students are progressing with their commitments or requirements. Gain access to customizable analytics about individuals or groups of students.'},
{id:5, imgRed:require('../assets/images/red/pos-red.svg'), imgWhite: require('../assets/images/white/sup-whi.svg'),
Name: 'Post School-Run Events',
Details:'Get administration involved by postings school-run volunteering events directly on your private Opportunity Board..'},
{id:6, imgRed:require('../assets/images/red/sup-red.svg'), imgWhite: require('../assets/images/white/tra-whi.svg'),
Name: 'Support',
Details:'Get access to tons of resources on our FAQ or contact our team directly. We take pride in our commitment in helping you build your community.'},
],
};
}
render() {
return (
<div className="App" >
<SchDic Lists = {this.state.Lists}/>
</div>
);
}
}
export default App;
/***SchDiv***/
import React,{ Component } from 'react';
import { Card,CardMedia,CardHeader,CardContent,Typography,withStyles } from '#material-ui/core';
const Styles = {
card: {
width:'385px',
height:'241px',
padding:'0px',
margin:'15px',
cursor: 'pointer',
'&:hover': {
background: '#E8583E',
color:'white',
"& $imgOne": {
display: 'none'
},
"& $imgTwo": {
display: 'block'
},
},
},
media: {
height: 41,
maxWidth:41,
margin:'15px',
"& + $imgOne": {
display: 'block'
},
"& + $imgTwo": {
display: 'none'
}
},
imgOne: {},
imgTwo: {},
name:{
padding:'1px',
margin:'15px',
},
details:{
fontSize: '14px',
padding: '0 15px',
minHeight: '25px',
align: 'left',
},
};
class SchDic extends Component {
constructor(props){
super(props);
this.state = {
value: 0,
};
}
handleChange = (event, value) => {
this.setState({ value });
};
render(){
const { classes }= this.props
const { Lists } = this.props;
const Post = Lists.map(List => {
return(
<div >
<Card className={classes.card} elevation={1}>
<CardMedia
className={`${classes.media} ${classes.imgOne}`}
image={List.imgRed}
/>
<CardMedia
className={`${classes.media} ${classes.imgTwo}`}
image={List.imgWhite}
/>
<CardHeader className={classes.name}
titleTypographyProps={{variant:'h5' }}
title={List.Name}
/>
<CardContent className={classes.details} >
<Typography variant='Body2' color=" " component="p">
{List.Details}
</Typography>
</CardContent>
</Card>
</div>
)}
);
const divStyle = {
paddingLeft:'350px',
paddingRight:'150px',
display: 'flex',
flexWrap: 'wrap',
};
return(
<div className="coreFeatures" style={divStyle} >
{ Post }
</div>
)
}
}
export default withStyles(Styles)(SchDic);
I have my array where you can add a "player" to it and it updates just fine. I also have a total number of players that looks at the array and gives the total sum. It works but it does not factor in the new items.
I know I need to pass it the new updated state of the array (players) but I'm just stuck on how to do that.
I've provided snips of my code as follows
Array
const players = [
{
name: 'Jabba',
score: 10,
id: 11
},
{
name: 'Han',
id: 1
},
{
name: 'Luke',
id: 2
},
Function to add to the array
handleChange = e => {
this.setState({
newPlayerName: e.target.value
});
};
handleAddPlayer = e => {
this.setState(prevState => ({
players: [
...prevState.players,
{
name: this.state.newPlayerName,
id: getRandomInt(20, 155)
}
]
}));
};
my state
this.state = {
id: players.id,
totalScore: 0,
totalPlayers: players,
countInfo: [],
evilName: '',
color: '#6E68C5',
scoreColor: '#74D8FF',
fontAwe: 'score-icon',
incrementcolor: '',
scoreNameColor: 'white',
glow: '',
buttonStyle: 'count-button-start',
newPlayerName: '',
max_chars: 15,
chars_left: '',
players
};
And finally the function that should update the total but doesn't and where I'm stuck
function Stats(props) {
const totalPlayers = props.players.length;
const totalScore = props.players.reduce(
function(total, player) {
return props.totalScore + props.totalScore;
},
0
);
return (
<div style={{ width: '100%' }}>
<PlayerNumb>Number of players: <b>{totalPlayers}</b></PlayerNumb>
</div>
);
}
the const totalPlayers = props.players.length; is where I seemed to be tripped up. Any help would be greatly appreciated and apologies if I gave any confusing information as React is still new to me.
Also an example of how I called stats:
<Stats
style={{ width: '100%' }}
totalScore={this.state.totalScore}
players={players}
/>
And the complete render code of how Players is created
render() {
const listPlayers = this.state.players.map(player => (
<Counter
key={player.id}
player={player}
playersArray={this.state.players}
name={player.name}
sortableGroupDecorator={this.sortableGroupDecorator}
decrementCountTotal={this.decrementCountTotal}
incrementCountTotal={this.incrementCountTotal}
removePlayer={this.removePlayer.bind(this)} //bind this to stay in the context of the parent component
handleClick={player}
/>
));
return (
<ContainLeft style={{ alignItems: 'center' }}>
<ProjectTitle>Score Keeper</ProjectTitle>
<Copy>
A sortable list of players that with adjustable scores. Warning, don't go negative!
</Copy>
<GroupHolder>
<Stats
style={{ width: '100%' }}
totalScore={this.state.totalScore}
players={players}
/>
<div
style={{ width: '100%' }}
className="container"
ref={this.sortableContainersDecorator}
>
<div className="group">
<div className="group-list" ref={this.sortableGroupDecorator}>
{listPlayers}
</div>
</div>
</div>
<HandleForm>
<form
style={{ width: '100%' }}
onSubmit={this.handleClick.bind(this)}
>
<p className="sort-player">Add A Player</p>
<InputText
type="text"
maxLength="15"
max="4"
name="usr"
placeholder="Enter a new name"
value={this.state.newPlayerName}
onChange={this.handleChange}
/>
<PlayerButton onClick={this.handleAddPlayer}>
Press to Add Player
</PlayerButton>
<CharacterCount>
Characters left
{' '}
{this.state.max_chars - this.state.newPlayerName.length}
</CharacterCount>
</form>
</HandleForm>
</GroupHolder>
</ContainLeft>
);
}
}
export default Container;
You're calling your render function with an undefined variable called players
players={players}
You either need to pass this.state.players or define it with const players = this.state.players;
You should always be developing locally with an eslint plugin for your editor, which will highlight problems like these as syntax errors and save you hours of time.
I need to make a calendar with events and I decided to use react-big-calendar. But I need to make events of different colors. So each event will have some category and each category has corresponding color. How can I change the color of the event with react?
Result should look something like this
Sorry, I haven't read the documentation really well. It can be done with the help of eventPropGetter attribute. I've made it like this:
eventStyleGetter: function(event, start, end, isSelected) {
console.log(event);
var backgroundColor = '#' + event.hexColor;
var style = {
backgroundColor: backgroundColor,
borderRadius: '0px',
opacity: 0.8,
color: 'black',
border: '0px',
display: 'block'
};
return {
style: style
};
},
render: function () {
return (
<Layout active="plan" title="Planning">
<div className="content-app fixed-header">
<div className="app-body">
<div className="box">
<BigCalendar
events={this.events}
defaultDate={new Date()}
defaultView='week'
views={[]}
onSelectSlot={(this.slotSelected)}
onSelectEvent={(this.eventSelected)}
eventPropGetter={(this.eventStyleGetter)}
/>
</div>
</div>
</div>
</Layout>
);
}
Additional tip on how to style different kinds of events: In the myEvents array of event objects, I gave each object a boolean property isMine, then I defined:
<BigCalendar
// other props here
eventPropGetter={
(event, start, end, isSelected) => {
let newStyle = {
backgroundColor: "lightgrey",
color: 'black',
borderRadius: "0px",
border: "none"
};
if (event.isMine){
newStyle.backgroundColor = "lightgreen"
}
return {
className: "",
style: newStyle
};
}
}
/>
This solution is simple !
eventPropGetter={(event) => {
const backgroundColor = event.allday ? 'yellow' : 'blue';
return { style: { backgroundColor } }
}}
change the condition according to your need and it is done.
Siva Surya's solution is the fastest, and I have added the color property as well. Thanks...
import React, {useEffect, useLayoutEffect} from 'react';
import { Calendar, momentLocalizer,globalizeLocalizer } from 'react-big-calendar'
import moment from 'moment';
import { connect } from 'frontity';
import BackgroundWrapper from 'react-big-calendar/lib/BackgroundWrapper';
const MyCalendar = ({ actions, state, objetoBloque, formato }) => {
const localizer = momentLocalizer(moment);
const myEventsList = [
{
title: 'My Event',
start: '2022-06-21T13:45:00-05:00',
end: '2022-06-25T14:00:00-05:00',
// elcolor:'red'
colorEvento:'red'
},
{
title: 'Otro',
start: '2022-06-15T13:45:00-05:00',
end: '2022-06-23T14:00:00-05:00',
colorEvento:'green',
color:'white'
}
];
return(
<div>
<Calendar
// defaultDate = {defaultDate}
localizer={localizer}
events={myEventsList}
startAccessor="start"
endAccessor="end"
style={{ height: 500 }}
BackgroundWrapper = "red"
eventPropGetter={(myEventsList) => {
const backgroundColor = myEventsList.colorEvento ? myEventsList.colorEvento : 'blue';
const color = myEventsList.color ? myEventsList.color : 'blue';
return { style: { backgroundColor ,color} }
}}
/>
</div>
)
}
export default connect(MyCalendar);
Searching for how to change the border colour of an event also lead me here, and I couldn't find the answer anywhere else, but found that adding the following done the trick:
border: "black",
borderStyle: "solid"