I'm glad I'm finally making my first post after many years of referring to this website. A big thank you to everyone here, I'm looking forward to being an active member and positively contribute to the community.
I'm trying to look for a more react-js(ish) solution to the following problem: We have a small application built with React, which consists of a simple list of events. One of the requirements is that the client is able to provide custom styles for a particular event. So when we fetch the data for an event, that particular event might have some particular styles associated. These are retrieved via an API call that returns them as text.
In order to make this as simple and non-cumbersome as possible for the client, those styles consist of simple selectors e.g. '.event-container', '.event-date' etc...
In the vanilla JS working code that I have those styles get parsed and I prepend the event id to those so that they end up being '#event-1234 .event-container', '#event-1234 .event-date' etc... then I create a style node in the head of the document and fill it with the resulting CSS...
This works well however it is not the 'react' way of doing things and I was asked if I could find a different solution that adhered more to React mechanics... which basically means, I think, that those styles would have to be applied to a JSX object directly. In order to do this I may need a way to run something like a querySelector in the react event component which returns that JSX node and then apply the styles to the JSX node...
So far I'm thinking this is a little cumbersome... and would increase the code complexity and reduce the flexibility of the CSS that may be applied, but maybe someone has better ideas than I do as I am not highly experienced with react yet...
Many thanks in advance for everyone's input on this.
That's some of the CSS coming from the API call for a particular event:
.event-card {
width: 100% !important;
background-color:black;
border-radius: 40px;
padding: 20px;
box-shadow: 2px 2px 2px 4px rgba(0, 0, 0, .4);
}
/* Title */
.event-name {
color:#79acd1;
}
/* Type */
.event-type {
color: white;
}
/* Progress: .event-status + .event-remaining */
.event-progress {
color:white;
}
/* Comments */
.event-comments {
color:white;
}
/* Description */
.event-description {
color:white;
}
/* Status 'open'|'closed' */
.event-status {
color:#57a5d0;
}
/* Remaining days */
.event-remaining {
color:#57a5d0;
}
/* Progress Bar Progress */
.mdc-linear-progress__bar-inner {
background-color:#57a5d0;
}
/* Progress Bar Buffer */
.mdc-linear-progress__buffer {
background-color:#44516C;
}
/* Date */
.event-date {
color:#89BBFE;
}
/* Load Button */
.mdc-button {
background-color:#79acd1;
}
Here's the vanilla JS object which takes care of parsing the selectors and prepending the relevant event Id and attaching them to the document:
export default {
applyStyle : function(css, id) {
let style = this.parseCSS(css, id);
if (css) {
this.createNode(style, id);
}
},
parseCSS: function (css, id) {
let _rules = css.trim().split('}');
_rules.pop();
let rules = [];
for (var rule of _rules) {
rule = rule.trim() + '}';
rules.push(`#${id} ${rule}`);
}
if (!rules.length) {
return;
}
return rules.join('\r\n');
},
createNode: function (css, id) {
let styleId = id + '-style';
if (document.getElementById(styleId)) {
return;
}
let node = document.createElement('style');
node.id = styleId;
node.innerHTML = css;
document.head.appendChild(node);
}
};
This is our event container component and where I implement the vanilla JS solution (see 'applyStyle'):
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Loader from '../common/Loader.jsx';
import Error from '../common/Error.jsx';
import EventPanel from './EventPanel.jsx';
import {ERROR_FETCH_EVENT} from '../../constants/ErrorMessages';
import styles from '../../util/styles';
class Event extends Component {
constructor() {
super();
}
render() {
const {event} = this.props;
if (!event) {
return (<Loader/>);
}
if (event.errorMessage) {
return (
<Error messageHeader={ERROR_FETCH_EVENT} error={this.props.event.errorMessage}/>
);
}
this.htmlId = `event-${this.props.event.id}`;
let css = this.props.event.css.data || '';
styles.applyStyle(css, this.htmlId);
return (
<div id={this.htmlId} className="oc-kse-content">
<div className="oc-kse-events">
<EventPanel event={this.props.event} onEventClick={this.props.onEventClick}/>
</div>
</div>
);
}
}
Event.propTypes = {
event: PropTypes.object,
onEventClick: PropTypes.func
};
export default Event;
And finally the event panel component which contains most of the JSX for the event:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Button} from 'rmwc/Button';
import {Card} from 'rmwc/Card';
import {LinearProgress} from 'rmwc/LinearProgress';
import format from 'date-fns/format';
import differenceInDays from 'date-fns/difference_in_days';
import config from '../../config/AppConfig';
class EventPanel extends Component {
render() {
const {event} = this.props;
const daysRemaining = this.daysRemaining(event);
const percentagePassed = this.percentagePassed(event);
const isOpen = event.status && event.status === 'open';
const startDate = this.formattedDate(event.startEvent);
const endDate = this.formattedDate(event.endEvent);
const startTime = this.formattedTime(event.startEvent);
const endTime = this.formattedTime(event.endEvent);
if (event) {
return (
<React.Fragment>
<Card className="event-card">
{
event.imageFormats && <div className="media">
<img
src={`${config.IMAGE_HOST}${event.imageFormats.impexp.$ref}`}
alt={event.name}
className="media-image"/>
</div>
}
<div className="content">
<div className="detail">
<div className="event-type">{event.eventType}</div>
<div className="event-title">
<span className="event-name">{event.name}</span>
<span className="event-comments">
<i className="fa fa-comments fa-fw"></i>{
event.representationCount
? event.representationCount
: 0
}
</span>
</div>
<div className="event-description">
{event.description}
</div>
<div className="event-progress">
<span className="event-status">{event.status}</span>
<span className="event-remaining">{daysRemaining >= 0 && daysRemaining + ' days left'}</span>
</div>
<div>
{!isNaN(percentagePassed) && isOpen && <LinearProgress progress={percentagePassed}></LinearProgress>}
</div>
<div className="event-dates">
<div className="event-date">
<span className="span-block space-right-small">{startDate}</span>
<span>{startTime}</span>
</div>
<div className="event-date">
<span className="span-block space-right-small">{endDate}</span>
<span>{endTime}</span>
</div>
</div>
</div>
<div className="actions">
<Button
raised={true}
className="primary"
onClick={() => this.props.onEventClick(event.id)}>LOAD</Button>
</div>
</div>
</Card>
</React.Fragment>
);
}
}
}
EventPanel.propTypes = {
event: PropTypes.object,
onEventClick: PropTypes.func
};
export default EventPanel;