15

I wanted to set my element's style as such:

this.refs.element.style = {
    ...this.props.style,
    background: 'blue',
};

But apparently you can't use an object to set the ref's style. I have to use a CSS style string with ; separating the prop:values

I'm aware that most people would set style in the render function, but for performance reasons, I can't repeatedly re-render.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
neaumusic
  • 10,027
  • 9
  • 55
  • 83

6 Answers6

39

A performant answer is to map and join the Object.entries with semicolons:

const style = {
  ...this.props.style,
  background: 'blue',
};

const styleString = (
  Object.entries(style).map(([k, v]) => `${k}:${v}`).join(';')
);

It unwraps background:'blue', to background:blue; which works well for CSS


To replace any capital letter with dash lowercase letter

k = k.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
neaumusic
  • 10,027
  • 9
  • 55
  • 83
  • 1
    Be aware that properties like backgroundColor should be renamed to background-color, but apart from that, a good use of reduce()! – Eric Sellin Nov 02 '18 at 16:07
  • @EricSellin good catch, fixed that with a `.replace`, should work but maybe there are edge cases – neaumusic Nov 02 '18 at 20:03
  • but it wont work in IE because IE doesn't support Object.entries() – Srinivasan Natarajan Feb 26 '20 at 19:57
  • 1
    This will be rather slow for large objects - you're doing n string concatenations, and each concatenation is on the order of the length of the strings. You'd probably want a map + join instead. – Yi Jiang May 18 '20 at 21:58
  • 2
    Who needs IE support in 2021? – Tofandel Aug 04 '21 at 15:44
  • I admit I don't really worry much about IE anymore, but it won't be officially retired until 2022; a reasonably good polyfill for Object.entries is here: https://stackoverflow.com/questions/45849831/object-entries-alternative-for-internet-explorer-and-reactjs – Daniel Beck Aug 04 '21 at 16:03
10

this solution works in IE and handles camelCase keys like backgroundColor

const style = {
    width: '1px',
    height: '1px',
    backgroundColor: 'red',
    transform: 'rotateZ(45deg)',
}
const styleToString = (style) => {
    return Object.keys(style).reduce((acc, key) => (
        acc + key.split(/(?=[A-Z])/).join('-').toLowerCase() + ':' + style[key] + ';'
    ), '');
};

console.log(styleToString(style));
// output - "width:1px;height:1px;background-color:red;transform:rotateZ(45deg);"
Artem Bochkarev
  • 1,242
  • 13
  • 23
4

Use https://www.npmjs.com/package/json-to-css. Note it will not add a semicolon to the last property to fix it you can beautify it with https://www.npmjs.com/package/cssbeautify Example


    const cssbeautify = require('cssbeautify')

    const Css = require('json-to-css')

    const json = {
        "h1": {
            "font-size": "18vw",
            "color": "#f00"
        },
        ".btn": {
            "font-size": "18vw",
            "color": "#f00"
        }
    }


    const r = Css.of(json)
    console.log(r)

    const beautified = cssbeautify(r, {
        autosemicolon: true
    })

    console.log(beautified)

Result


  console.log src/utils/playground/index.spec.ts:22 // json-to-css
    h1{font-size:18vw;color:#f00}
    .btn{font-size:18vw;color:#f00}

  console.log src/utils/playground/index.spec.ts:29 // cssbeautify
    h1 {
        font-size: 18vw;
        color: #f00;
    }
    
    .btn {
        font-size: 18vw;
        color: #f00;
    }
   
Chetan Jain
  • 236
  • 6
  • 16
1

Adding to the great answer of @Artem Bochkarev

I'm adding a snippet to do the opposite conversion as well (string to object) which may come in handy to anyone stumbling here

const style = {
  width: '1px',
  height: '1px',
  backgroundColor: 'red',
  transform: 'rotateZ(45deg)',
};
const styleToString = (style) => {
  return Object.keys(style).reduce((acc, key) => (
    acc + key.split(/(?=[A-Z])/).join('-').toLowerCase() + ':' + style[key] + ';'
  ), '');
};
const stringToStyle = (style) => {
  const styles = {};
  style.split(';').forEach((s) => {
    const parts = s.split(':', 2);
    if (parts.length > 1) {
      styles[parts[0].trim().replace(/-([a-z])/ig, (_, l) => l.toUpperCase())] = parts[1].trim();
    }
  });
  return styles;
};

console.log(styleToString(style));
// output - "width:1px;height:1px;background-color:red;transform:rotateZ(45deg);"

console.log(stringToStyle(styleToString(style)));
Tofandel
  • 3,006
  • 1
  • 29
  • 48
0

TL;DR: The problem is that you are overwriting the entire "style" property of the element and losing its prototype and methods. You must add your style object without change the entire property. If you want to apply an object-like style to a DOM element, just do:

Object.assign(this.refs.element.style, {
    background: 'blue',
    color: 'white',
    /** style properties:values goes here */
});

Explanation: The property "style" is an instance of the "CSSStyleDeclaration" interface. If you overwrite the interface it wont be a "CSSStyleDeclaration" anymore. It works when you set a string as value because javascript will pass the string directly to the element, without process anything.

CSSStyleDeclaration Reference Doc: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration

If you want to do a test, go to your navigator > inspector > console and paste the code below:

const p1 = document.createElement('p');
p1.style = { color: 'blue' };

const p2 = document.createElement('p');
Object.assign(p2.style, { color: 'blue' });

console.log(p1);
console.log(p2);

The output will be:

<p style=""></p>
<p style="color: blue;"></p>
Felippe Regazio
  • 189
  • 2
  • 4
-1

the css function in @material-ui/system can help you out check more info here

import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import NoSsr from '@material-ui/core/NoSsr';
import { createMuiTheme } from '@material-ui/core/styles';
import { compose, spacing, palette, css } from '@material-ui/system';

const Box = styled.div`
  ${css(
    compose(
      spacing,
      palette,
    ),
  )}
`;

const theme = createMuiTheme();

export default function CssProp() {
  return (
    <NoSsr>
      <ThemeProvider theme={theme}>
        <Box color="white" css={{ bgcolor: 'palevioletred', p: 1, textTransform: 'uppercase' }}>
          CssProp
        </Box>
      </ThemeProvider>
    </NoSsr>
  );
}
Kent Wood
  • 1,392
  • 2
  • 14
  • 30