so basically I'm using ref to get component dimensions in componentDidMount() and I can read and console.log that and it gives me the width I want(look into code), but when I want to read and console.log that in the render() method and to use that informations, it gives me undefined. And I don't know what is wrong
var Tooltip = React.createClass({
componentDidMount() {
this.tooltipSize = this.refs.tooltip.getBoundingClientRect();
this.tooltipWidth = this.tooltipSize.width;
// console.log(this.tooltipWidth); here it gives me the width
},
render(){
var tooltipSize,
tooltipWidth,
tooltipStyle = {
top: 0,
left: 0,
};
// console.log(tooltipWidth); here it gives me undefined
return(
<div ref="tooltip" className="tooltip" style={tooltipStyle}>{this.props.tooltip}</div>
);
}
});
var Button = React.createClass({
getInitialState() {
return {
iconStyle: this.props.iconStyle,
style: this.props.style,
cursorPos: {},
};
},
componentDidMount() {
this.size = this.refs.button.getBoundingClientRect();
this.width = this.size.width;
this.height = this.size.height;
this.top = this.size.top;
this.left = this.size.left;
},
...
render() {
var _props = this.props,
top,
left,
width,
height,
size,
//other variables
...
return(
<Style>
{`
.IconButton{
position: relative;
}
.IconButton:disabled{
color: ${_props.disabledColor};
}
.btnhref{
text-decoration: none;
background-color: blue;
}
`}
<a {...opts} className="btnhref" id="tak">
<button ref="button" className={"IconButton" + _props.className} disabled={disabled} style={buttonStyle}
onMouseEnter={this.showTooltip} onMouseLeave={this.removeTooltip} >
<Ink background={true} style={rippleStyle} opacity={rippleOpacity} />
<FontIcon className={_props.iconClassName}/>
</button>
</a>
</Style>
);
}
});
class IconButton extends React.Component {
render(){
return(
<div>
<Tooltip tooltip={this.props.tooltip} />
<Button href={this.props.href} className={this.props.className} iconStyle={this.props.iconStyle} style={this.props.style} iconClassName={this.props.iconClassName} disabled={this.props.disabled} disableTouchRipple={this.props.disableTouchRipple} />
</div>
);
}
}
And one thing else. How can I send variables with informations about dimensions of another component(Button component) to the Tooltip component? Because I need to use them inside of this component to place it. Thanks
Updated code:
var Tooltip = React.createClass({
getInitialState() {
return {
tooltipWidth: null,
tooltipHeight: null
};
},
componentDidMount() {
this.tooltipSize = this.refs.tooltip.getBoundingClientRect();
this.setState({
tooltipWidth: this.tooltipSize.width,
tooltipHeight: this.tooltipSize.height
});
},
...
render(){
var _props = this.props,
fontSize,
fontStyle,
tooltipSize,
tooltipWidth = this.state.tooltipWidth,
tooltipHeight = this.state.tooltipHeight,
w = this.props.buttonWidth,
h = this.props.buttonHeight,
y = this.props.buttonTop,
x = this.props.buttonLeft,
tooltipStyle = {
top: y - tooltipHeight - 20 + "px",
left: x - tooltipWidth/2 + w/2 + "px",
};;
...
return(
<div ref="tooltip" className="tooltip" style={fontStyle}>{this.props.tooltip}</div>
);
}
});
var Button = React.createClass({
getInitialState() {
return {
iconStyle: this.props.iconStyle,
style: this.props.style,
cursorPos: {},
width: null,
height: null,
top: null,
left: null,
};
},
componentDidMount() {
this.size = this.refs.button.getBoundingClientRect();
this.width = this.size.width;
this.height = this.size.height;
this.top = this.size.top;
this.left = this.size.left;
},
transferring1(){
var width = this.width;
return width;
},
transferring2(){
var height = this.height;
return height;
},
transferring3(){
var top = this.top;
return top;
},
transferring4(){
var left = this.left;
return left;
},
...
render() {
var _props = this.props,
opts,
top,
left,
width,
height,
size;
...
return(
<Style>
{`
.IconButton{
position: relative;
}
.IconButton:disabled{
color: ${_props.disabledColor};
}
.btnhref{
text-decoration: none;
background-color: blue;
}
`}
<a {...opts} className="btnhref" id="tak">
<button ref="button" className={"IconButton" + _props.className} disabled={disabled} style={buttonStyle}
onMouseEnter={this.showTooltip} onMouseLeave={this.removeTooltip} >
<Ink background={true} style={rippleStyle} opacity={rippleOpacity} />
<FontIcon className={_props.iconClassName}/>
</button>
</a>
</Style>
);
}
});
class IconButton extends React.Component {
constructor(props) {
super(props);
this.state = {
buttonWidth: null,
buttonHeight: null,
buttonTop: null,
buttonLeft: null,
};
}
componentDidMount() {
this.setState({
buttonWidth: this.refs.btn.transferring1(),
buttonHeight: this.refs.btn.transferring2(),
buttonTop: this.refs.btn.transferring3(),
buttonLeft: this.refs.btn.transferring4(),
});
}
render(){
return(
<div>
<Tooltip tooltipPosition={this.props.tooltipPosition} tooltip={this.props.tooltip} touch={this.props.touch} buttonWidth={this.state.buttonWidth} buttonHeight={this.state.buttonHeight} buttonTop={this.state.buttonTop} buttonLeft={this.state.buttonLeft}/>
<Button ref="btn" href={this.props.href} className={this.props.className} iconStyle={this.props.iconStyle} style={this.props.style} iconClassName={this.props.iconClassName}
disabled={this.props.disabled} disableTouchRipple={this.props.disableTouchRipple} />
</div>
);
}
}
ReactDOM.render(
<IconButton href="" className="" iconStyle="" style="" iconClassName="face" disabled="" disableTouchRipple="" tooltip="! ! ! Guzik ! to ! kozak ! ! !" tooltipPosition="" touch="true" />,
document.getElementById('app')
);
I think you should use state for setting variables in react
example
var Tooltip = React.createClass({
constructor(){
super();
this.state = {tooltipWidth: 0}
}
componentDidMount() {
this.tooltipSize = this.refs.tooltip.getBoundingClientRect();
this.setState({tooltipWidth: this.tooltipSize.width}); //Update the state of this component
},
render(){
console.log(this.state.tooltipWidth) //your tooltip width
return(
<div ref="tooltip" className="tooltip" style={tooltipStyle}>{this.props.tooltip}</div>
);
}
});
and for passing another's component dimension, you should calculate the size of Button component on the parent component (IconButton).
Then pass it to Tooltip like this (just example)
<Tooltip buttonHeight={this.state.buttonHeight} tooltip={this.props.tooltip} />
Related
I'd like to draw rect with React,
is there anything wrong in the following?
I don't figure out why this code doesn't draw a rect.
class GameView{
constructor(props) {
this.canvas = React.createRef()
this.width = 0;
this.height = 0;
this.canvas_style = {
width: 600,
height: 400
}
}
componentDidMount() {
this.width = document.body.offsetWidth;
this.height = document.body.offsetHeight;
this.canvas_style = {
width: this.width,
height: this.height
}
this.context = this.canvas.current.getContext("2d");
}
render() {
return (
<canvas id="game-view" ref={this.canvas} width={this.width} height={this.height} />
)
}
}
class View extends GameView {
constructor(props) {
super(props);
}
componentDidMount() {
super.componentDidMount();
this.context.fillStyle = "black";
this.context.fillRect(0, 0, 200, 200)
}
render() {
return (
<canvas id="game-view" ref={this.canvas} width={this.width} height={this.height} style={this.canvas_style} />
)
}
}
I tried docment.getElementById("game-view") but I got the same result
The GameView needs to extend React.Component, so React can render it.
The height of the container (body in this case) needs to be greater than 0, so the canvas would have some height.
View doesn't need to render a different JSX:
class GameView extends React.Component {
canvas = React.createRef()
width = document.body.offsetWidth;
height = document.body.offsetHeight;
componentDidMount() {
this.context = this.canvas.current.getContext("2d");
}
render() {
return (
<canvas id="game-view" ref={this.canvas} width={this.width} height={this.height} />
)
}
}
class View extends GameView {
componentDidMount() {
super.componentDidMount();
this.context.fillStyle = "black";
this.context.fillRect(0, 0, 200, 200)
}
}
ReactDOM.render(
<View />,
root
);
html, body {
margin: 0;
height: 100%;
}
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
I have a network that I want to draw with Konva (and the react-konva bindings). When positions update I want to animate the nodes in the network to their new positions while also animating the start and end position of the link that connects them.
I started with the following simple example, but can't seem to get a Line to animate in the same way that the nodes do.
Is there a way to fix this, or am I approaching it in the wrong way?
import React from "react";
import { Stage, Layer, Rect, Line } from "react-konva";
class Node extends React.Component {
componentDidUpdate() {
this.rect.to({
x: this.props.x,
y: this.props.y,
});
}
render() {
const { id } = this.props;
const color = id === "a" ? "blue" : "red";
return (
<Rect
ref={node => {
this.rect = node;
}}
width={5}
height={5}
fill={color}
/>
);
}
}
class Link extends React.Component {
componentDidUpdate() {
const x0 = 0;
const y0 = 0;
const x1 = 100;
const y1 = 100;
this.line.to({
x: x0,
y: y0,
points: [x1, y1, x0, y0],
});
}
render() {
const color = "#ccc";
return (
<Line
ref={node => {
this.line = node;
}}
stroke={color}
/>
);
}
}
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: [{ id: "a", x: 0, y: 0 }, { id: "b", x: 200, y: 200 }],
links: [
{
source: "a",
target: "b",
},
],
};
}
handleClick = () => {
const nodes = this.state.nodes.map(node => {
const position = node.x === 0 ? { x: 200, y: 200 } : { x: 0, y: 0 };
return Object.assign({}, node, position);
});
this.setState({
nodes,
});
};
render() {
const { links, nodes } = this.state;
return (
<React.Fragment>
<Stage width={800} height={800}>
<Layer>
{nodes.map((node, index) => {
return (
<Node
key={`node-${index}`}
x={node.x}
y={node.y}
id={node.id}
/>
);
})}
</Layer>
<Layer>
{links.map(link => {
return (
<Link
source={nodes.find(node => node.id === link.source)}
target={nodes.find(node => node.id === link.target)}
/>
);
})}
</Layer>
</Stage>
<button onClick={this.handleClick}>Click me</button>
</React.Fragment>
);
}
}
export default Graph;
You may need to set initial values for points attribute for a better tween.
Also, you are not using the source and target in the Link component. You should use that props for calculating animations.
import React from "react";
import { render } from "react-dom";
import { Stage, Layer, Rect, Line } from "react-konva";
class Node extends React.Component {
componentDidMount() {
this.rect.setAttrs({
x: this.props.x,
y: this.props.y
});
}
componentDidUpdate() {
this.rect.to({
x: this.props.x,
y: this.props.y
});
}
render() {
const { id } = this.props;
const color = id === "a" ? "blue" : "red";
return (
<Rect
ref={node => {
this.rect = node;
}}
width={5}
height={5}
fill={color}
/>
);
}
}
class Link extends React.Component {
componentDidMount() {
// set initial value:
const { source, target } = this.props;
console.log(source, target);
this.line.setAttrs({
points: [source.x, source.y, target.x, target.y]
});
}
componentDidUpdate() {
this.animate();
}
animate() {
const { source, target } = this.props;
this.line.to({
points: [source.x, source.y, target.x, target.y]
});
}
render() {
const color = "#ccc";
return (
<Line
ref={node => {
this.line = node;
}}
stroke={color}
/>
);
}
}
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: [{ id: "a", x: 0, y: 0 }, { id: "b", x: 200, y: 200 }],
links: [
{
source: "a",
target: "b"
}
]
};
}
handleClick = () => {
const nodes = this.state.nodes.map(node => {
const position = node.x === 0 ? { x: 200, y: 200 } : { x: 0, y: 0 };
return Object.assign({}, node, position);
});
this.setState({
nodes
});
};
render() {
const { links, nodes } = this.state;
return (
<React.Fragment>
<Stage width={800} height={300}>
<Layer>
{nodes.map((node, index) => {
return (
<Node
key={`node-${index}`}
x={node.x}
y={node.y}
id={node.id}
/>
);
})}
</Layer>
<Layer>
{links.map(link => {
return (
<Link
source={nodes.find(node => node.id === link.source)}
target={nodes.find(node => node.id === link.target)}
/>
);
})}
</Layer>
</Stage>
<button onClick={this.handleClick}>Click me</button>
</React.Fragment>
);
}
}
render(<Graph />, document.getElementById("root"));
Demo: https://codesandbox.io/s/react-konva-animating-line-demo-erufn
I try to build a to-do-list in react.
I have 2 components so far:
The first one handles the input:
import React from 'react';
import ListItems from './ListItems.js';
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
<ListItems
entries={this.state.entries}
/>
</div>
</div>
)
}
}
export default InputComponent;
The second one is about the actual entries in the list:
import React from 'react';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
this.listTasks = this.listTasks.bind(this);
}
lineThrough(item) {
console.log(item);
//item.style = {
// textDecoration: 'line-through'
//}
}
listTasks(item) {
return(
<li key = { item.index }>
<div
ref = { (r) => this._itemText = r }
style = {{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{ item.text }
</div>
<button onClick={ () => this.lineThrough(this._itemText) }>Done!</button>
<button>Dismiss!</button>
</li>
)
}
render() {
let items = this.props.entries;
let listThem = items.map( this.listTasks );
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
{ listThem }
</div>
</ul>
)
}
}
export default ListItems;
As you can see, i want to have two buttons for each entry, one for the text to be line-through, and one to delete the entry.
I am currently stuck at the point where i try to address a specific entry with the "Done!" button to line-through this entry's text.
I set a ref on the div containing the text i want to style and pass that ref to the onClick event handler.
Anyways, the ref seems to be overwritten each time i post a new entry...
Now, always the last of all entries is addressed. How can i properly address each one of the entries?
What would be the best practice to solve such a problem?
you could pass an additional prop with index/key of the todo into every item of your todo list. With passing event object to your handler lineThrough() you can now get the related todo id from the attributes of your event target.
Kind regards
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
}
this.done = this.done.bind(this);
}
done(id) {
this.state.todos[id].done();
}
render() {
return (
this.state.todos.map(t => <Todo item={t} onDone={this.done} />)
);
}
}
const Todo = ({item, onDone}) => {
return (
<div>
<h1>{item.title}</h1>
<button onClick={() => onDone(item.id)}>done</button>
</div>
)
}
You need map listItems then every list item get its own ref
import React from 'react';
import ReactDOM from 'react-dom';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
}
lineThrough(item) {
item.style.textDecoration = "line-through";
}
render() {
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
<li key={this.props.item.index}>
<div
ref={(r) => this._itemText = r}
style={{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{this.props.item.text}
</div>
<button onClick={() => this.lineThrough(this._itemText)}>Done!</button>
<button>Dismiss!</button>
</li>
</div>
</ul>
)
}
}
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
{this.state.entries.map((item, index) => {
return <ListItems key={index} item={item} />
})}
</div>
</div>
)
}
}
I have two pens, and I'm trying to use a React component I defined in one pen inside another, but I'm not clear on how Codepen actually handles React imports between pens. I went to the destination pen and added the source pen's address to the Javascript references, but I don't know how to proceed from there. I can get this to work in a local node project using traditional export, but the Codepen element is giving me trouble. Here's the code:
SOURCE (https://codepen.io/ejpg/pen/LmOVoR):
export default class Wheel extends React.Component // Export default causes error
{
constructor(props){
super(props);
this.state = {
spin : false,
value: 0
};
this.spin = this.spin.bind(this);
}
spin(e){
var val = this.state.value + 720 + (Math.floor(Math.random() * 24) * 15);
console.log((this.state.value % 360) / 15);
e.target.style.webkitTransform = 'rotate(' + -val + 'deg)';
e.target.style.webkitTransition = '-webkit-transform 4s ease-out';
this.setState({value: val});
}
render(){
const wheelVals = [800, "BANKRUPT", 200, 300, 350, 250, 400, 300, 200, 250, 500, 350, 250,
"BANKRUPT", 200, 300, 400, 250, 600, "LOSE A TURN", 200, 300, 250, 200];
return (<div><img width="400" height="400" src="https://orig00.deviantart.net/0a38/f/2010/242/f/6/singapore_wheel_of_fortune_by_wheelgenius-d2xmb9v.jpg" onClick={(e) => this.spin(e)}/><br/><br/>{wheelVals[(this.state.value % 360) / 15]}
</div>);
}
}
DESTINATION (https://codepen.io/ejpg/pen/bMgWpN):
let { Grid, Row, Col, ButtonToolbar, Button } = ReactBootstrap;
// How do I import the class?
class CustomButton extends React.Component {
onHandleClick = () => {
this.props.onClick();
};
render(){
return <Button bsStyle={this.props.bsStyle} onClick={this.onHandleClick}><strong>{this.props.text}</strong></Button>;
}
}
class Letter extends React.Component {
onHandleClick = () => {
this.props.onClick(this.props.letter);
};
render () {
const style = { border: '1px solid black',
display: 'inline-block',
fontSize: '3.5vw',
width: '4vw',
height: '10vh',
textAlign: 'center',
whiteSpace: 'no-wrap',
overflow: 'hidden'};
if (this.props.letter === ' ') style.border = '';
return (
<div
style={style}
key={this.props.key}
onClick={this.onHandleClick} // Have to pass onClick to div
>
{this.props.letter}
</div>
);
}
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
var blanks = '';
for (var i = 0; i < this.props.answer.length; ++i)
{
this.props.answer[i] === ' ' ?
blanks += ' ': blanks += '-';
}
this.state = {
phrase: blanks,
alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
bonus: false,
revealed: false
};
this.callLetter = this.callLetter.bind(this);
this.bonusRound = this.bonusRound.bind(this);
this.complete = this.complete.bind(this);
}
replaceAt(str, index, replacement) {
return str.substr(0, index) + replacement + str.substr(index + replacement.length);
}
complete(){
if (this.state.revealed === false)
{
this.setState({phrase: this.props.answer, revealed: true});
}
}
checkForLetter(letter, phr)
{
this.setState((prevState, props) => {
var prephrase = prevState.phrase;
var index = phr.indexOf(letter);
while( index !== -1)
{
prephrase = this.replaceAt(prephrase, index, letter);
index = phr.indexOf(letter, index + 1);
}
return ({phrase: prephrase});
});
}
callLetter(letter) {
this.setState((prevState, props) => {
var alphaclone = prevState.alpha;
var letterindex = alphaclone.indexOf(letter);
alphaclone = alphaclone.slice(0, letterindex) + alphaclone.slice(letterindex + 1);
return ({alpha: alphaclone});
});
this.checkForLetter(letter, this.props.answer);
}
bonusRound(){
if (this.state.bonus === false)
{
this.callLetter('R');
this.callLetter('S');
this.callLetter('T');
this.callLetter('L');
this.callLetter('N');
this.callLetter('E');
this.setState({bonus: true});
}
}
render() {
return (
<Grid>
<Row className="show-grid" >
{
this.state.phrase.split(' ').map((item, j) =>
(
<div style = {{display:'inline-block'}}>
<Letter letter = {' '}/>
{item.split('').map((item, i) =>
(
<Letter letter= {item}/>
)) }
</div>
))
}
</Row>
<Row className="show-grid" style={{margin: '3vh'}}>
{
this.state.alpha.split('').map((item, i) =>
(
<Letter letter={item} key={i} onClick={this.callLetter}/>
))
}
</Row>
<Row className="show-grid" style={{margin: '3vh'}}>
<ButtonToolbar>
<CustomButton bsStyle = {"primary"} text= {"BONUS ROUND"} onClick = {this.bonusRound}/>
<CustomButton bsStyle = {"danger"} text= {"REVEAL ALL"} onClick = {this.complete}/>
</ButtonToolbar>
</Row>
</Grid>
);
}
}
ReactDOM.render(
<MyComponent answer='A VERY VERY EXCESSIVELY LONG TEST'/>,
document.getElementsByClassName('container-fluid')[0]
);
Any help is greatly appreciated.
EDIT: I can't believe I actually have to explicitly state that I don't want to copy and paste it.
For that you have to make your pen containing the component as a module. You can do this by going to Settings > Javascript and checking the Add type="module" checkbox.
Then you can import the component in another pen using the URL of your pen:
import MyComponent from 'https://codepen.io/user/pen/xyz.js';
The entire doc regarding this may be found here: https://blog.codepen.io/2017/12/26/adding-typemodule-scripts-pens/.
Hope this helps :)
I currently creating a dynamic design UI. I'm using an axios call too get the dynamic design properties like background color, font color, images to be displayed.
import React, {Component} from "react";
import MainButton from '../utilities/MainButton';
const tinycolor = require("tinycolor2");
const styles = {
underlineStyle: {
borderColor: blue800
}
}
class MobileHome extends React.Component {
constructor(props) {
super(props);
this.color = '';
this.state = {
mainColor: '',
fontColor: ''
}
}
componentWillMount() {
axios.get('/getEventConfig').then(res => {
var config = res.data;
var color = tinycolor(config.COLOR);
var font = '#fff';
if (color.isLight()){
font = '#000';
}
this.color = color;
this.setState({mainColor: config.COLOR}); // error on timing issue
console.log('set state', this.state.mainColor);
});
}
render() {
return (
<div className="center-panel">
<TextField
hintText="Enter Code Here"
fullWidth={true}
underlineFocusStyle={styles.underlineStyle}
id="event_code" />
<MainButton
label="Upload Photo"
fullWidth={true}
size="xl"
color={color}
fontcolor={this.state.fontColor}
onClick={this.uploadPhoto}/>
</div>
)
}
}
export default MobileHome
On which, my MainButton is another component, just calling the Material UI RaisedButton:
class MainButton extends React.Component {
constructor(props) {
super(props);
console.log('p', this.props);
let mainColor = _.isNil(this.props.color) ? '#2E65C2': this.props.color;
let fontColor = _.isNil(this.props.fontColor) ? '#FFF': this.props.fontColor;
this.styles = {
root: {
width: 225
},
label: {
color: fontColor,
paddingTop: 5
},
color: mainColor,
}
}
render() {
return (
<RaisedButton
label={this.props.label}
fullWidth={this.props.fullWidth}
buttonStyle={this.styles.button}
style={this.styles.root}
backgroundColor={this.styles.color}
labelStyle={this.styles.label}
onClick={this.props.onClick}
/>
)
}
}
export default MainButton;
The problem is, the MainButton is rendered already before the axios call is completed. I'm looking for some way, for the render to wait before the axios call to be completed, before it shows the UI. Is that possible?
You can use ternary operator to show MainButton based on state change.
Check below code, here I have taken new state variable resReceived and setting it to false by default. In API res setting to true.
import React, {Component} from "react";
import MainButton from '../utilities/MainButton';
const tinycolor = require("tinycolor2");
const styles = {
underlineStyle: {
borderColor: blue800
}
}
class MobileHome extends React.Component {
constructor(props) {
super(props);
this.color = '';
this.state = {
mainColor: '',
fontColor: '',
resReceived: false
}
}
componentWillMount() {
axios.get('/getEventConfig').then(res => {
var config = res.data;
var color = tinycolor(config.COLOR);
var font = '#fff';
if (color.isLight()){
font = '#000';
}
this.color = color;
this.setState({mainColor: config.COLOR, resReceived: true}); // error on timing issue
console.log('set state', this.state.mainColor);
});
}
render() {
return (
<div className="center-panel">
<TextField
hintText="Enter Code Here"
fullWidth={true}
underlineFocusStyle={styles.underlineStyle}
id="event_code" />
{this.state.resReceived ?
<MainButton
label="Upload Photo"
fullWidth={true}
size="xl"
color={color}
fontcolor={this.state.fontColor}
onClick={this.uploadPhoto}/>
: ''}
}
</div>
)
}
}
export default MobileHome
OR
If you want to keep the button disabled until you receive a response then try below one.
import React, {Component} from "react";
import MainButton from '../utilities/MainButton';
const tinycolor = require("tinycolor2");
const styles = {
underlineStyle: {
borderColor: blue800
}
}
class MobileHome extends React.Component {
constructor(props) {
super(props);
this.color = '';
this.state = {
mainColor: '',
fontColor: '',
disableMainButton: true
}
}
componentWillMount() {
axios.get('/getEventConfig').then(res => {
var config = res.data;
var color = tinycolor(config.COLOR);
var font = '#fff';
if (color.isLight()){
font = '#000';
}
this.color = color;
this.setState({mainColor: config.COLOR, disableMainButton: false}); // error on timing issue
console.log('set state', this.state.mainColor);
});
}
render() {
return (
<div className="center-panel">
<TextField
hintText="Enter Code Here"
fullWidth={true}
underlineFocusStyle={styles.underlineStyle}
id="event_code" />
<MainButton
label="Upload Photo"
fullWidth={true}
size="xl"
color={color}
fontcolor={this.state.fontColor}
onClick={this.uploadPhoto}
disabled = {this.state.disableMainButton}
/>
}
</div>
)
}
}
export default MobileHome
Here is the Problem:
mainColor and fontColor are init in constructor of MainButton.
constructor can be init only once, so those two dynamic fields can never be dynamic.
Here is the solution:
Move the styles to render function so that the colors can be dynamic when data change.
Tips: check the react doc https://reactjs.org/docs/state-and-lifecycle.html.
render() {
const { fontColor, mainColor } = this.props;
const styles = {
root: {
width: 225
},
label: {
color: _.isNil(this.props.fontColor) ? '#FFF': fontColor,
paddingTop: 5
},
color: _.isNil(this.props.color) ? '#2E65C2': mainColor,
}
return (
<RaisedButton
label={this.props.label}
fullWidth={this.props.fullWidth}
buttonStyle={styles.button}
style={styles.root}
backgroundColor={styles.color}
labelStyle={styles.label}
onClick={this.props.onClick}
/>
)
}