1

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;
andriusain
  • 1,211
  • 10
  • 18

1 Answers1

0

I'm not sure if this is the best option in your case, but it would probably be better to store your styles into object like this:

const styleOptions={
    eventStatus:{
       color:"white",
       /* other stuff in camelCase */
    },
    /* other options */

}

Then apply your style like this:

render(){
    const style = styleOptions.eventStatus;
    return(
        <div style={style}>
          ...
        </div>
    )
}

It seems to be a more React way of styling your components, you don't create nodes, and you don't have to parse your css files.

Who Knows
  • 101
  • 7
  • Thanks for your answer. A problem that comes to mind in this way is the ability to have CSS pseudo elements, hover, before, after etc... – andriusain May 25 '18 at 19:44
  • You just have to manage it in different ways. Using event function like onMouseOver for :hover, for :after here is an example :https://stackoverflow.com/questions/28269669/css-pseudo-elements-in-react. – Who Knows May 26 '18 at 21:02
  • And that's exactly why I think this will not be the appropriate way. It is better not to rely directly on JavaScript for things that can be achieved just with plain CSS... also the implementation would become too cumbersome and overcomplicated compared to just applying the CSS in a style tag manually – andriusain May 26 '18 at 22:00
  • Actually it can be pretty simple. If you define your style object with "hover" and "before","after" options, just have to create a Style Component with methods and default behaviour. Then you just have to give it style object as props and it should work perfectly with any given style. It is, I think, more reliable and less ressource consuming than parsing css files and creating element through dom. Which is not recommended in React. – Who Knows May 27 '18 at 12:44
  • Thanks for your answer, it is not clear to me however how would I go about that parting from the plain text CSS I retrieve from the API. If you are so kind to provide a example I would be grateful. Thanks – andriusain May 27 '18 at 21:45
  • I would need to see how you put your css data into Event props. You might be able to use a SCSS loader, to get you css as a js object directly. – Who Knows May 28 '18 at 11:08