How to include styles in React create portal - javascript

I have ViewAllGraphs class:
import '../styles/Graph.css'
export class ViewAllGraphs extends React.Component {
constructor(props) {
super(props);
this.state = {
showWindowPortal: false,
}
And render method:
return (
<div>
{
this.state.showWindowPortal && (
<Graph closeWindowPortal={this.closeWindowPortal} >
<h1>Id графика : {this.state.currentId}</h1>
<h1>Название графика : {this.state.currentTitle}</h1>
<img o src={`data:image/png;base64,${this.state.currentImage}`} />
<h1>Данные графика : {this.state.currentData}</h1>
<button className="graph-button-close" onClick={() => this.closeWindowPortal()} >
Закрыть график
</button>
</Graph>
)
}
</div>
My CSS file is located in ../styles/Graph.css
I want to style my graph component, for example, the button. This is code of this component:
import React from "react";
import ReactDOM from 'react-dom'
import '../styles/Graph.css'
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
id: 0,
}
this.containerEl = null;
this.externalWindow = null;
}
componentDidMount() {
this.externalWindow = window.open('', '');
this.containerEl = this.externalWindow.document.createElement('div');
this.externalWindow.document.body.appendChild(this.containerEl);
this.externalWindow.document.title = 'A React portal window';
this.externalWindow.addEventListener('beforeunload', () => {
this.props.closeWindowPortal();
});
this.shouldComponentUpdate();
this.setState({
id: 1,
})
}
shouldComponentUpdate() {
return true;
}
componentWillUnmount() {
this.externalWindow.close();
}
render() {
if (!this.containerEl) {
return null;
}
else
return ReactDOM.createPortal(this.props.children, this.containerEl);
}
};
export default Graph
I am trying to include the CSS file and apply className="graph-button-close" in render method to my button, but it's not working. Why can't I import the CSS file to graph class?

You can try these code:
this.containerEl = this.externalWindow.document.createElement('div');
this.containerEl.className = 'image';
this.containerEl.style.backgroundImage = 'url(http://via.placeholder.com/350x150)';
// add the image to its container; add both to the body
// this.containerEl.appendChild(img);
this.externalWindow.document.body.appendChild(this.containerEl);
Or for current elem you can use inline styles in parent component
let styleConfig = { backgroundColor: 'blue' }
In render method:
<p style={styleConfig}>Данные графика : {this.state.currentData}</p>

To style a component functionally, and I hope this works for Class Components as well, is that for the styling part of the top of the file, I import the style as a component, something like this,
import componentStyling from '../styles/Graphs.css`;
A bit of advice is that 99% of the time, I want a style to only apply to that component. It's tremendously hard to think of unique class names every single time I make to add styling to a component, so I rename my CSS files with the following format, classComponentName.module.css, or classComponentName.module.scss, if you're using SCSS.
So, whatever the name of the component you're making is, whether it's functional or a class component, name your CSS files with respect to that and then suffix it with .module.css.
Now, the import looks something like this,
import componentStyling from `../styles/Graphs.module.css`;
Now, in the rendering part of the component, wherever I want to apply a class from Graphs.module.css to an HTML component in the component I have, I simply write,
<htmlElement className={componentStyling.classNameFromTheStylesFile}>
{/* some more JSX here */}
</htmlElement>
Where classNameFromTheStylesFile is a class name that exists within Graphs.module.css, which can be for example,
.classNameFromTheStylesFile {
background-color: blue;
};
I hope I got the question right.
Cheers!

Related

How do you render div objects from non-React libraries in React?

I found a library I want to use in my project, but it's a plain JavaScript library and doesn't know anything about React.
https://www.npmjs.com/package/json-formatter-js
Is it possible to use this in my React project? I tried this, but the render crashes.
class JSONView extends Component {
constructor(props) {
super(props);
}
render() {
const formatter = new JSONFormatter(this.props.data);
const Rendered = formatter.render();
return (
<div>
<Rendered />
</div>
);
}
}
The error I get is this.
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
The typeof Rendered is object.
Try this
class JSONView extends React.Component<any, any> {
constructor(props) {
super(props);
}
public refs:{
JsonDiv: HTMLDivElement;
};
componentDidMount(){
const formatter = new JSONFormatter(this.props.data);
this.refs.JsonDiv.appendChild(formatter.render());
}
render() {
return (
<div ref='JsonDiv'>
</div>
);
}
}
or this one
class JSONView extends React.Component<any, any> {
constructor(props) {
super(props);
}
render() {
const formatter = new JSONFormatter(this.props.data);
return (
<div dangerouslySetInnerHTML={formatter.render()}>
</div>
);
}
}
Looks like the library returns HTMLDOMElement from function formatter.render().
So you can't use JSX syntax to render it.
While I agree that the other answers can work, I would prefer using a dom ref and appending the returned HTMLDOMElement from formatter.render() in componentDidMount lifecycle method.
import React, { Component } from 'react';
import { render } from 'react-dom';
import JSONFormatter from "json-formatter-js";
import Hello from './Hello';
import './style.css';
class JSONView extends Component {
constructor(props) {
super(props);
}
ref = null;
componentDidMount(){
const formatter = new JSONFormatter(this.props.data);
const Rendered = formatter.render();
this.ref.appendChild(Rendered);
}
render() {
return (
<div ref={e => this.ref = e}>
</div>
);
}
}
render(<JSONView data={{Hello: {Hello: "World"}}} />, document.getElementById('root'));
Working Demo You can drill down to the object.
In the other approach which makes use of dangerouslySetInnerHTML or just rendering the string content, you have the risk of losing DOM events.
import React, { Component } from 'react';
import { render } from 'react-dom';
import JSONFormatter from "json-formatter-js";
import Hello from './Hello';
class JSONView extends Component {
constructor(props) {
super(props);
}
ref = null;
render() {
const formatter = new JSONFormatter(this.props.data);
console.log(formatter);
const Rendered = formatter.render();
console.log(Rendered.innerHTML);
return (
<div>
<div dangerouslySetInnerHTML={{__html:Rendered.innerHTML}} />
</div>
);
}
}
render(<JSONView data={{Hello: {Hello: "World"}}} />, document.getElementById('root'));
Working Link You cannot drill down to the object.
edit
try
{ Rendered }
there might be warning if it ships class with html tags.
between curly braces {} and it'll work (99 %) and if it doesn't then continue reading.
you can render if it returns string.
currently it might b returning html object which is incompatible with react dom render.
if it contains class property then it'll cause problem even if it's in string format.
you can use react-html-parser to render html strings in react. reference
const Rendered = formatter.render().outerHTML // this will give you html string instead of html object
now use react-html-parser if you don't wanna manage class attribute name conflict as react accepts className
import ReactHtmlParser from 'react-html-parser';
class JSONView extends Component {
constructor(props) {
super(props);
}
render() {
const formatter = new JSONFormatter(this.props.data);
const Rendered = formatter.render().outerHTML;
return (
<div>
{ ReactHtmlParser(Rendered) }
</div>
);
}
}

React presentational / container component separation

Okay, I am trying to make a project using container / presentational components design but I am missing some kind of information or knowledege.
What I am trying to do is to render a component based upon it's parent component's type:
I have this route: http://localhost:3000/:componentId
componentId has a backend model with a type (HTML or Text).
So, I guess I could create the Component with the following structure:
page.js:
const HtmlPage = props => {
return <Fragment>My HTML Component</Fragment>
};
const TextPage = props => {
return <Fragment>My Text Component</Fragment>
};
// material-ui styles
const HtmlComponent = withStyles(styles)(HtmlPage);
const TextComponent = withStyles(styles)(TextPage);
export {
HtmlComponent,
TextComponent
};
index.js:
import {
HtmlComponent as HtmlPage,
TextComponent as TextPage
} from './page';
class Component extends React.Component {
constructor(props) {
super(props);
this.state = { componentType: 'HTML' };
}
componentDidMount() {
// Retrieve model's type from backend
}
render() {
switch(this.state.componentType) {
case 'HTML':
return <HtmlPage />;
case 'Text':
return <TextPage />;
}
}
}
export default Component;
So my question is, is this component "split" well formed or I should create multiple components for HtmlPage and TextPage and then import them on a parent Component?
Any comment is appreciated.

Call class method of default import in react

I'm wondering whether its possible to call a method on a component that I import from another file. Basically, my situation is that I have two react classes. One of them is a Sudoku puzzle, which I call Game, and which includes the updateArray() method:
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {arr: [[5,0,4,9,0,0,0,0,2],
[9,0,0,0,0,2,8,0,0],
[0,0,6,7,0,0,0,0,9],
[0,0,5,0,0,6,0,0,3],
[3,0,0,0,7,0,0,0,1],
[4,0,0,1,0,0,9,0,0],
[2,0,0,0,0,9,7,0,0],
[0,0,8,4,0,0,0,0,6],
[6,0,0,0,0,3,4,0,8]]};
this.handleSubmit = this.handleSubmit.bind(this);
this.updateArray = this.updateArray.bind(this);
}
componentWillReceiveProps(nextProps) {
if(nextProps.arr != this.props.arr){
this.setState({arr: nextProps.value });
}
}
updateArray(str_arr) {
this.setState({arr: str_arr});
}
handleSubmit(event) {
...
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className = "game">
<div className = "game-board">
<Board value = {this.state.arr} />
</div>
<div className = "game-info">
<div></div>
</div>
</div>
<input type="submit" value="Submit" />
</form>
);
}
}
export default Game;
And then I have a second class that gets a image of a sudoku puzzle and makes a corresponding 9x9 array using computer vision methods. I then try to send the array back to Game using its updateArray function:
import Game from './Sudoku';
export default class ImageInput extends React.Component {
constructor(props) {
super(props);
this.state = {
uploadedFile: ''
};
}
onImageDrop(files) {
this.setState({uploadedFile: files[0]});
this.handleImageUpload(files[0]);
}
handleImageUpload(file) {
var upload = request.post('/')
.field('file', file)
upload.end((err, response) => {
if (err) {
console.error(err);
}
else {
console.log(response);
console.log(Game);
//ERROR HAPPENING HERE
Game.updateArray(response.text);
}
});
}
render() {
return (
<div>
<Dropzone
multiple = {false}
accept = "image/jpg, image/png"
onDrop={this.onImageDrop.bind(this)}>
<p>Drop an image or click to select file to upload</p>
</Dropzone>
);
}
}
However, when I try to send the array to Game's method, I get a Uncaught TypeError:
Uncaught TypeError: _Sudoku2.default.updateArray is not a function
at eval (image_input.js?8ad4:43)
at Request.callback (client.js?8e7e:609)
at Request.eval (client.js?8e7e:436)
at Request.Emitter.emit (index.js?5abe:133)
at XMLHttpRequest.xhr.onreadystatechange (client.js?8e7e:703)
I want the updateArray() method to update the Game from a separate file, which will then cause the Game to re-render. Is this possible? I've spent a lot of time reading documentation, and it seems as though what I'm suggesting is not the typical workflow of react. Is it dangerous, and if so, can someone explain why?
Also, both classes are rendered in a separate file that looks like this:
import Game from './Sudoku';
import ImageUpload from './image_input';
document.addEventListener('DOMContentLoaded', function() {
ReactDOM.render(
React.createElement(ImageUpload),
document.getElementById('image-upload'),
);
ReactDOM.render(
React.createElement(Game),
document.getElementById('sudoku_game'),
);
});
First of all, in your separate file (the one rendering both Game and ImageInput components):
Make it render only one component. This could have a original name like App for instance. Like this:
import App from './App';
document.addEventListener('DOMContentLoaded', function() {
ReactDOM.render(
React.createElement(App),
document.getElementById('root'),
);
});
You would only have to change the imports and name of the root element as needed of course.
Then, for the App component:
import React from 'react';
import Game from './Sudoku';
import ImageUpload from './image_input';
class App extends React.Component {
constructor() {
super();
this.state = {
sudokuArray = [];
}
}
updateArray(newArray) {
this.setState({sudokuArray: newArray})
}
render() {
<div>
<Game sudokuArray={this.state.sudokuArray} />
<ImageUpload updateArray={this.updateArray.bind(this)} />
</div>
}
}
export default App;
And inside your ImageInput component you would call the update method like:
this.props.updateArray(response.text).
Also, inside your Game component, change the render function, specifically the part with the Board component to: <Board value = {this.props.sudokuArray} />.
This is a rather common situation when you are learning React. You find yourself trying to pass some prop or run some method inside a component that is not "below" the component you are currently working with. In these cases, maybe the prop you want to pass or the method you want to run should belong to a parent component. Which is what I suggested with my answer. You could also make Game as a child of ImageInput or vice-versa.

Is there a React lifecycle method to do something only when component receive props the first time?

I'm new to React so thank you for your patience in advance. Also using Redux.
I have a list of content pulled from the API, I display the text and a hidden text box and on a state change associated that alternates the visibility of the two. Essentially user can click on the text and edit the text, achieved by inverting the boolean and swapping the display. They can then save it and PUT to server etc.
Since my list length varies, I must initialize a number of state.isVisible[n]. equivalent to the number of content being displayed each time. This number must be counted, after the props come in. I am using Redux so the content is retrieved, stored, then given to props. It's done as the following:
constructor(props){
super(props);
this.state = {
isVisibleObj: {}
}
}
componentWillReceiveProps(){
const { isVisibleObj } = this.state
// set visibility of text box
let obj = {}
Object.keys(this.props.questions).forEach(key => obj[key] = false)
this.setState({isVisibleObj: obj})
}
My initial implementation was that in componentWillReceiveProps I do all the setState() to initialize the isVisible properties to a boolean.
The challenge I am having with this implementation is that, if a user open up multiple items for edit, and if she saves one of them, the PUT request on success would send back the edited content, now updating the store and props. This will trigger componentWillReceiveProps and reset all the visibilities, effectively closing all the other edits that are open.
Any suggestion on how to proceed?
I think you should make two components
List (NamesList.react)
import React, {PropTypes} from 'react';
import NameForm from './NameForm.react';
import Faker from 'Faker'
export default class NamesList extends React.Component {
constructor(){
super();
this.addItem = this.addItem.bind(this);
}
addItem(){
var randomName = Faker.name.findName();
this.props.addName(randomName);
}
render() {
let forms = this.props.names.map((name,i) => {
return <NameForm updateName={this.props.updateName} index={i} key={i} name={name} />
});
return (<div>
<div>{forms}</div>
<button onClick={this.addItem}>Add</button>
</div>);
}
}
NamesList.propTypes = {
names: PropTypes.arrayOf(PropTypes.string).isRequired
};
Form (NameForm.react)
import React, {PropTypes} from 'react';
export default class NameForm extends React.Component {
constructor(props) {
super(props);
this.updateName = this.updateName.bind(this);
this.state = {
showTextBox:false
}
}
updateName(){
this.setState({showTextBox:false});
this.props.updateName(this.props.index,this.refs.name.value);
}
render() {
if(this.state.showTextBox){
return (<div>
<input ref="name" defaultValue={this.props.name} />
<button onClick={this.updateName}>Save</button>
</div>);
}
return (<div onClick={() => {this.setState({showTextBox: !this.state.showTextBox})}}>
{this.props.name}
</div>);
}
}
NameForm.propTypes = {
name:PropTypes.string.isRequired
};
Invoke (App.js)
import React, { Component } from 'react';
import NamesList from './NamesList.react';
class App extends Component {
constructor(){
super();
this.addName = this.addName.bind(this);
this.updateName = this.updateName.bind(this);
this.state = {
names:['Praveen','Vartika']
}
}
addName(name){
let names = this.state.names.concat(name);
this.setState({
names: names
});
}
updateName(index,newName){
let names = this.state.names.map((name,i) => {
if(i==index){
return newName
}
return name;
});
this.setState({names:names});
}
render() {
return (
<NamesList names={this.state.names} updateName={this.updateName} addName={this.addName} />
);
}
}
export default App;
Now if your store changes after user saves something. React wont re-render Child component that didn't change

How can CKEditor be used with React.js in a way that allows React to recognize it?

I've tried using componentWillMount and componentDidMount to initialize CKEditor from within the context of React, but it doesn't seem to work no matter what combination I try. Has anyone found a solution to this besides switching editors?
I published a package on Npm for using CKEditor with React. It takes just 1 line of code to integrate in your project.
Github link - https://github.com/codeslayer1/react-ckeditor.
How to Use?
Install the package using npm install react-ckeditor-component --save.
Then include the component in your React app and pass it your content and any other props that you need(all props listed on Github page) -
<CKEditor activeClass="editor" content={this.state.content} onChange={this.updateContent} />
The package uses the default build of CKEditor but you can use a custom build as well along with any of the plugins you like. It also includes a sample application. Hope you will find it useful.
Sage describes an awesome solution in his answer. It was a lifesaver, as I've only just started using React, and I needed it to get this going. I did, however, change the implementation, also incorporating Jared's suggestions (using componentDidMount). Also, my need was to have a change callback, like so:
Usage of the component:
<CKEditor value={this.props.value} onChange={this.onChange}/>
Added this to index.html:
<script src="//cdn.ckeditor.com/4.6.1/basic/ckeditor.js"></script>
Using the following component code:
import React, {Component} from "react";
export default class CKEditor extends Component {
constructor(props) {
super(props);
this.componentDidMount = this.componentDidMount.bind(this);
}
render() {
return (
<textarea name="editor" cols="100" rows="6" defaultValue={this.props.value}></textarea>
)
}
componentDidMount() {
let configuration = {
toolbar: "Basic"
};
CKEDITOR.replace("editor", configuration);
CKEDITOR.instances.editor.on('change', function () {
let data = CKEDITOR.instances.editor.getData();
this.props.onChange(data);
}.bind(this));
}
}
Again, all credits to Sage!
The following is an improved version of the basic version above, which supports multiple CKEditor instances on the same page:
import React, {Component} from "react";
export default class CKEditor extends Component {
constructor(props) {
super(props);
this.elementName = "editor_" + this.props.id;
this.componentDidMount = this.componentDidMount.bind(this);
}
render() {
return (
<textarea name={this.elementName} defaultValue={this.props.value}></textarea>
)
}
componentDidMount() {
let configuration = {
toolbar: "Basic"
};
CKEDITOR.replace(this.elementName, configuration);
CKEDITOR.instances[this.elementName].on("change", function () {
let data = CKEDITOR.instances[this.elementName].getData();
this.props.onChange(data);
}.bind(this));
}
}
Please note that this requires some unique ID to be passed along as well:
<CKEditor id={...} value={this.props.value} onChange={this.onChange}/>
This is for a React component which displays a P paragraph of text. If the user wants to edit the text in the paragraph, they can click it which will then attach a CKEditor instance. When the user is done altering the text in the Editor instance, the "blur" event fires which transfers the CKEditor data to a state property and destroys the CKEditor Instance.
import React, {PropTypes, Component} from 'react';
export default class ConditionalWYSIWYG extends Component {
constructor(props) {
super(props);
this.state = {
field_name:this.props.field_name,
field_value:this.props.field_value,
showWYSIWYG:false
};
this.beginEdit = this.beginEdit.bind(this);
this.initEditor = this.initEditor.bind(this);
}
render() {
if ( this.state.showWYSIWYG ) {
var field = this.state.field_name;
this.initEditor(field);
return (
<textarea name='editor' cols="100" rows="6" defaultValue={unescape(this.state.field_value)}></textarea>
)
} else {
return (
<p className='description_field' onClick={this.beginEdit}>{unescape(this.state.field_value)}</p>
)
}
}
beginEdit() {
this.setState({showWYSIWYG:true})
}
initEditor(field) {
var self = this;
function toggle() {
CKEDITOR.replace("editor", { toolbar: "Basic", width: 870, height: 150 });
CKEDITOR.instances.editor.on('blur', function() {
let data = CKEDITOR.instances.editor.getData();
self.setState({
field_value:escape(data),
showWYSIWYG:false
});
self.value = data;
CKEDITOR.instances.editor.destroy();
});
}
window.setTimeout(toggle, 100);
}
}
The self.value = data allows me to retrieve the text from the parent component via a simple ref
The window.setTimeout(); gives React time to do what it does. Without this delay, I would get an Cannot read property 'getEditor' of undefined error in the console.
Hope this helps
Just refer the ckeditor.js in index.html, and use it with window.CKEDITOR. Don't use CKEDITOR straight like the document in React component.
Just read the first-line of ckeditor.js, you will find what about define of CKEDITOR.
Thanks to Sage, Sander & co. I just wanted to contribute a version for the "inline" mode of CKEditor.
First, disable CKEditor's "auto-inline" behavior with...
CKEDITOR.disableAutoInline = true
Then, for the actual component...
import React, {Component} from 'react';
export default class CKEditor extends Component {
constructor(props) {
super(props);
this.elementName = "editor_" + this.props.id;
this.componentDidMount = this.componentDidMount.bind(this);
this.onInput = this.onInput.bind(this);
}
onInput(data) {
console.log('onInput: ' + data);
}
render() {
return (
<div
contentEditable={true}
suppressContentEditableWarning
className="rte"
id={this.elementName}>
{this.props.value}</div>
)
}
componentDidMount() {
let configuration = {
toolbar: "Basic"
};
CKEDITOR.inline(this.elementName, configuration);
CKEDITOR.instances[this.elementName].on("change", function() {
let data = CKEDITOR.instances[this.elementName].getData();
this.onInput(data);
}.bind(this));
}
}
Usage would be something like this:
<CKEditor id="102" value="something" onInput={this.onInput} />

Categories

Resources