Update React state from outside render when using HOC? - reactjs

I'm creating a React widget and using the react-jss HOC for styling. The widget is just a small part of a larger page, and I want to be able to signal it to open or close with a button on another part of the page outside of the React render. Originally I was doing it like this:
var modal = ReactDOM.render(<Modal />, document.getElementById('widget'))
// Inside an onClick function
modal.toggleModal()
That was before JSS, but now widget doesn't return the component, it returns the JSS HOC. I've tried passing <Widget /> a prop and updating that and then using widget.forceUpdate() but that did nothing. Not really sure what else to try at this point. I'm currently just toggling everything outside of React, but I want the component to be able to close itself as well.
import React, { Component } from 'react'
import injectSheet from 'react-jss'
const styles = {
show: {
display: 'block',
},
modal: {
display: 'none',
background: 'rgba(0, 0, 0, 0.3)',
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
width: '100%',
height: '100%',
},
form: {
maxWidth: '440px',
margin: '15px',
padding: '30px',
background: '#fff',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
},
input: {
width: '100%',
marginBottom: '15px'
},
button: {
width: '100%'
}
}
class Modal extends Component {
constructor(props) {
super(props)
this.state = {
show: false
}
this.toggleModal = this.toggleModal.bind(this)
}
toggleModal() {
this.setState({show: !this.state.show})
}
render() {
const { classes } = this.props
return (
<div className={`${classes.modal} ${this.state.show ? classes.show : ''}`}>
<form className={classes.form}>
<label htmlFor="aatitle-field">Title</label>
<input className={classes.input} type="text" id="aatitle-field" name="title" value="" />
<button className={`btn ${classes.button}`}>Save</button>
</form>
</div>
)
}
}
export default injectSheet(styles)(Modal)

First of all, please use the classnames library to generate classNames, it's so more elegant.
Secondly, classes are applied in order they are parsed by the brower. So what you're doing is tricky. How can we know if the modal class is parsed before show? (This is a requirement in your current code). You can simply move the modal declaration before the show declaration in styles and surely this will work, but this is a fragile solution. Better is to use a show and hide class and apply them according the state. This removes the dependency of which style class is loaded first. So remove display:none from modal and introduce hide:
Something like:
import React, { Component } from 'react'
import injectSheet from 'react-jss'
const styles = {
show: {
display: 'block',
},
hide: {
display: 'none',
},
modal: {
background: 'rgba(0, 0, 0, 0.3)',
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
width: '100%',
height: '100%',
},
form: {
maxWidth: '440px',
margin: '15px',
padding: '30px',
background: '#fff',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
},
input: {
width: '100%',
marginBottom: '15px'
},
button: {
width: '100%'
}
}
class Modal extends Component {
constructor(props) {
super(props)
this.state = {
show: false
}
this.toggleModal = this.toggleModal.bind(this)
}
toggleModal() {
this.setState({show: !this.state.show})
}
render() {
const { classes } = this.props
return (
<div className={`${classes.modal} ${this.state.show ? classes.show : classes.hide}`}>
<form className={classes.form}>
<label htmlFor="aatitle-field">Title</label>
<input className={classes.input} type="text" id="aatitle-field" name="title" value="" />
<button className={`btn ${classes.button}`}>Save</button>
</form>
</div>
)
}
}
export default injectSheet(styles)(Modal)
```

Related

Trying to change react Carousel into class component

i have react carousel in the functional component , i am trying to change that component in to class component.. can any one help on this..
this is the link of code sand box....https://codesandbox.io/s/suspicious-cookies-fvm80?file=/src/App.js
functional component
import React, { useState } from 'react';
import ItemsCarousel from 'react-items-carousel';
export default () => {
const [activeItemIndex, setActiveItemIndex] = useState(0);
const chevronWidth = 40;
return (
<div style={{ padding: `0 ${chevronWidth}px` }}>
<ItemsCarousel
requestToChangeActive={setActiveItemIndex}
activeItemIndex={activeItemIndex}
numberOfCards={2}
gutter={20}
leftChevron={<button>{'<'}</button>}
rightChevron={<button>{'>'}</button>}
outsideChevron
chevronWidth={chevronWidth}
>
<div style={{ height: 200, background: '#EEE' }}>First card</div>
<div style={{ height: 200, background: '#EEE' }}>Second card</div>
<div style={{ height: 200, background: '#EEE' }}>Third card</div>
<div style={{ height: 200, background: '#EEE' }}>Fourth card</div>
</ItemsCarousel>
</div>
);
};
Wrap jsx in render() method, change useState hook to destruct from this.state, and create function for setting state.
Example
export default class carousel extends Component {
constructor(props) {
super();
this.state = {
activeItemIndex:0
};
}
setActiveItemIndex = (value) => {
this.setState({ activeItemIndex: value });
}
render() {
const { activeItemIndex } = this.state;
const chevronWidth = 40;
return (
<div style={{ padding: `0 ${chevronWidth}px` }}>
<ItemsCarousel
requestToChangeActive={this.setActiveItemIndex}
activeItemIndex={activeItemIndex}
numberOfCards={2}
gutter={20}
leftChevron={<button>{"<"}</button>}
rightChevron={<button>{">"}</button>}
outsideChevron
chevronWidth={chevronWidth}
>
<div style={{ height: 200, background: "#EEE" }}>First card</div>
<div style={{ height: 200, background: "#EEE" }}>Second card</div>
<div style={{ height: 200, background: "#EEE" }}>Third card</div>
<div style={{ height: 200, background: "#EEE" }}>Fourth card</div>
</ItemsCarousel>
</div>
);
}
}

Change the tick color in MuiCheckbox material UI

I can't seem to find a way to change the tick color in Material UI MuiCheckbox
All the Demos show how to change the color of the whole checkbox, but in all of them, the tick is white.
How can I change the color of the tick only?
Below is an approach that seems to work. The gist of the approach is to create a box (via the :after pseudo-element) that is slightly smaller than the icon for the check and has the desired color as the background color. Then place that box behind the "checked" icon.
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import FormGroup from "#material-ui/core/FormGroup";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
const CheckboxWithGreenCheck = withStyles({
root: {
"&$checked": {
"& .MuiIconButton-label": {
position: "relative",
zIndex: 0
},
"& .MuiIconButton-label:after": {
content: '""',
left: 4,
top: 4,
height: 15,
width: 15,
position: "absolute",
backgroundColor: "lightgreen",
zIndex: -1
}
}
},
checked: {}
})(Checkbox);
export default function CheckboxLabels() {
const [state, setState] = React.useState({
checkedA: true,
checkedB: false
});
const handleChange = name => event => {
setState({ ...state, [name]: event.target.checked });
};
return (
<FormGroup>
<FormControlLabel
control={
<CheckboxWithGreenCheck
checked={state.checkedA}
onChange={handleChange("checkedA")}
value="checkedA"
color="primary"
/>
}
label="Custom check color"
/>
</FormGroup>
);
}
An alternative approach would be to create a custom icon that includes the desired color for the check and then use it via the checkedIcon property as in the Custom icon example in the demos.
If you only want to change the tick color you have to use the following technique. It's an enhanced version of #Ryan above.
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import FormGroup from "#material-ui/core/FormGroup";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
const CheckboxWithGreenCheck = withStyles({
root: {
"& .MuiSvgIcon-root": {
fill: "white",
"&:hover": {
backgroundColor: "white"
}
},
"&$checked": {
"& .MuiIconButton-label": {
position: "relative",
zIndex: 0,
border: "1px solid #bbbbbb",
borderRadius: 3
},
"& .MuiIconButton-label:after": {
content: '""',
left: 4,
top: 4,
height: 15,
width: 15,
position: "absolute",
backgroundColor: "green",
zIndex: -1,
borderColor: "transparent"
}
},
"&:not($checked) .MuiIconButton-label": {
position: "relative",
zIndex: 0,
border: "1px solid #bbbbbb",
borderRadius: 3
},
"&:not($checked) .MuiIconButton-label:after": {
content: '""',
left: 4,
top: 4,
height: 15,
width: 15,
position: "absolute",
backgroundColor: "white",
zIndex: -1,
borderColor: "transparent"
}
},
checked: {}
})(Checkbox);
export default function CheckboxLabels() {
const [state, setState] = React.useState({
checkedA: true,
checkedB: false
});
const handleChange = (name) => (event) => {
setState({ ...state, [name]: event.target.checked });
};
return (
<FormGroup>
<FormControlLabel
control={
<CheckboxWithGreenCheck
checked={state.checkedA}
onChange={handleChange("checkedA")}
value="checkedA"
/>
}
label="Custom check color"
/>
</FormGroup>
);
}
https://codesandbox.io/s/checkbox-custom-check-color-forked-k3kw2?file=/demo.js
There is actually no color settings for the tick. It takes the color of the background by default.
You can wrap your checkbox in a div, put a background color on this div and the tick should be colored.

How to delay a component render in React?

I have an instagram widget thas uses iframe, but when I switch between routes, the widget loads too slow and does'nt have time to render properly.
Can You tell me, how to set delay rendering of the component, jr another solution to this problem?
Here is the component:
import React, { Component } from 'react';
const divStyle = [
{
border: 'none',
overflow: 'hidden',
width: '100%'
},
{
font: "10px/14px 'Roboto','Helvetica Neue',Arial,Helvetica,sans-serif",
fontWeight: '400',
width: '100%',
textAlign: 'right'
},
{
color: '#777',
textDecoration: 'none'
}
];
class Instagram extends Component {
render() {
return (
<div id="instagram">
<iframe src="https://snapwidget.com/embed/711808" className="snapwidget-widget" allowtransparency="true" frameborder="0" scrolling="no" style={divStyle[0]}></iframe>
</div>
);
}
}
export default Instagram;
Also the code is located in the CodeSandbox.
Thanks for any help!
This can be possible solution from your code sandbox.
NOTE: Please Replace your loader with loading div.
CodeSandbox: https://codesandbox.io/s/damp-platform-950yw
import React, { Component } from "react";
const divStyle = [
{
border: "none",
overflow: "hidden",
width: "100%"
},
{
font: "10px/14px 'Roboto','Helvetica Neue',Arial,Helvetica,sans-serif",
fontWeight: "400",
width: "100%",
textAlign: "right"
},
{
color: "#777",
textDecoration: "none"
}
];
class Instagram extends Component {
state = {
loading: true
}
handelOnLoad = () => {
this.setState({
loading: false
})
}
render() {
return (
<>
{this.state.loading && <div style={{
position: "fixed",
background: "rgba(0,0,0,0.7)",
top: 0,
bottom: 0,
right: 0,
left: 0,
color: "#fff"
}}>Loading</div>}
<div id="instagram">
<iframe
src="https://snapwidget.com/embed/711808"
className="snapwidget-widget"
allowtransparency="true"
frameborder="0"
scrolling="no"
style={divStyle[0]}
onLoad={this.handelOnLoad}
/>
</div>
</>
);
}
}
export default Instagram;
You can make use of state to render,
class Instagram extends Component {
state={
show: false
}
componentDidMount(){
setTimeout(()=>{
this.setState({show: true})
},5000) //runs after 5sec
}
render() {
return (
<div id="instagram">
{ this.state.show && <iframe src="https://snapwidget.com/embed/711808" className="snapwidget-widget" allowtransparency="true" frameborder="0" scrolling="no" style={divStyle[0]}></iframe> }
</div>
);
}
}

React Component wont render in Codepen

I have a simple radio button group component on codepen here that is not rendering in codepen. I want to post this to the code review stackexchange, since it is one of the first components i've built and will be necessary in many places on a web app I am building. However for that post, I want my codepen example to be working.
I think I am probably breaking some rule about how to use es6 in react to get the app to render, but I am struggling to debug. My console.logs() are not helping, and the error messages in codepen arent helping a ton either.
Since I linked to my codepen, I have to accompany it with code, so here's what I have in my codepen at the moment:
import React, { Component } from 'react';
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap';
class ToolButtonGroup extends Component {
constructor(props) {
super(props);
};
render() {
// Get Variables from the params prop
const { header, buttons, initialVal } = this.props.params;
const { borderRadius, margin, padding, fontsize, border } = this.props.params;
const { gridID, gridColumns, minRowHeight } = this.props.params;
// Create the individual buttons
const pageButtons = buttons.map((buttoninfo, idx) => {
return (
<ToggleButton
key={idx}
style={{
"borderRadius": borderRadius,
"margin": margin,
"padding": padding,
"fontSize": fontsize,
"border": border
}}
bsSize="large"
value={buttoninfo.value}>
{buttoninfo.label}
</ToggleButton>
)
})
// Return the button group
return(
<div
style={{"border": "1px solid red" }}
id={gridID}>
<h2 style={{
"width": "100%",
"margin": "0 auto",
"fontSize": "1.75em",
"marginTop": "5px",
"border": "none"
}}
>{header}</h2>
<ToggleButtonGroup
type="radio"
name="charttype-options"
defaultValue={initialVal}
onChange={this.props.handler}
style={{
"display": "grid",
"gridTemplateColumns": "repeat(" + gridColumns + ", 1fr)",
"gridAutoRows": "auto",
"gridGap": "8px"
}}
>
{pageButtons}
</ToggleButtonGroup>
</div>
)
}
}
class StarterApp extends Component {
constructor(props){
super(props);
this.state = {
pitchersOrHitters: "",
position: ""
}
}
// Button and Select Handlers!
handlePitchHitChange = (pitchersOrHitters) => {
this.setState({pitchersOrHitters})
}
handlePositionChange = (position) => {
this.setState({ position: position });
}
render() {
console.log("A")
// 0. Load State and Props
const { pitchersOrHitters, position } = this.state;
// Pitcher or Hitter Radio Button Group params
const pitchOrHitButtonGroup = {
borderRadius: "25px",
margin: "1% 10%",
padding: "5%",
fontsize: "2em",
border: "2px solid #BBB",
gridColumns: 1, minRowHeight: "10px", "gridID": "buttons1",
header: "Choose One:",
buttons: [
{ value: "Pitchers", label: "Pitchers" },
{ value: "Hitters", label: "Hitters" },
],
initialVal: "Pitchers"}
// Pitcher or Hitter Radio Button Group params
const positionButtonGroup = {
borderRadius: "10px",
margin: "1% 10%",
padding: "5%",
fontsize: "1.25em",
border: "2px solid #BBB",
gridColumns: 4, minRowHeight: "20px", "gridID": "buttons2",
header: "Choose One:",
buttons: [
{ value: "SP", label: "SP" },
{ value: "RP", label: "RP" },
{ value: "1B", label: "1B" },
{ value: "2B", label: "2B" },
{ value: "SS", label: "SS" },
{ value: "3B", label: "3B" },
{ value: "LF", label: "LF" },
{ value: "RF", label: "RF" },
{ value: "CF", label: "CF" }
],
initialVal: "SP"}
return(
<div className="chart-grid-container">
<ToolButtonGroup
params={pitchOrHitButtonGroup}
value={pitchersOrHitters}
handler={this.handlePitchHitChange} />
<ToolButtonGroup
params={positionButtonGroup}
value={position}
handler={this.handlePositionChange} />
</div>
)
}
}
ReactDOM.render(
<StarterApp />,
document.getElementById('root')
);
.chart-grid-container {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: minmax(200px, auto);
grid-gap: 5px;
grid-template-areas:
"btns1 btns1 btns2 btns2 btns2 btns2 . . . . . .";
}
#buttons1 { grid-area: btns1; }
#buttons2 { grid-area: btns2; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-dom.min.js"></script>
<div id='root'>
COME ON WORK!
</div>
Im unsurprisingly struggling to get this code snippet working as well. Although in this case, it is because I don't know how to include react-bootstrap, which is something I've already done in my codepen.
Thanks!
I noticed I got the errors when using import statements on that specific project.
This is probably a limitation of transpiling engine on the codepen. Better if you use some platform (ie: enter link description here) that already has all of these solved out for you.
Here is your code on codesandbox.

Tabs navigation bar hides elements beneath it

I'm using material-ui to write a navigation bar by using Tab. My tab navigation bar is almost the same as Master.jsx:
import React from 'react';
import {
AppCanvas,
IconButton,
EnhancedButton,
Mixins,
Styles,
Tab,
Tabs,
Paper} from 'material-ui';
const {StylePropable} = Mixins;
const {Colors, Spacing, Typography} = Styles;
const ThemeManager = Styles.ThemeManager;
const DefaultRawTheme = Styles.LightRawTheme;
const Master = React.createClass({
mixins: [StylePropable],
getInitialState() {
let muiTheme = ThemeManager.getMuiTheme(DefaultRawTheme);
// To switch to RTL...
// muiTheme.isRtl = true;
return {
muiTheme,
};
},
propTypes: {
children: React.PropTypes.node,
history: React.PropTypes.object,
location: React.PropTypes.object,
},
childContextTypes: {
muiTheme: React.PropTypes.object,
},
getChildContext() {
return {
muiTheme: this.state.muiTheme,
};
},
getStyles() {
let darkWhite = Colors.darkWhite;
return {
footer: {
backgroundColor: Colors.grey900,
textAlign: 'center',
},
a: {
color: darkWhite,
},
p: {
margin: '0 auto',
padding: 0,
color: Colors.lightWhite,
maxWidth: 335,
},
github: {
position: 'fixed',
right: Spacing.desktopGutter / 2,
top: 8,
zIndex: 5,
color: 'white',
},
iconButton: {
color: darkWhite,
},
};
},
componentWillMount() {
let newMuiTheme = this.state.muiTheme;
newMuiTheme.inkBar.backgroundColor = Colors.yellow200;
this.setState({
muiTheme: newMuiTheme,
tabIndex: this._getSelectedIndex()});
let setTabsState = function() {
this.setState({renderTabs: !(document.body.clientWidth <= 647)});
}.bind(this);
setTabsState();
window.onresize = setTabsState;
},
componentWillReceiveProps(nextProps, nextContext) {
let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
this.setState({
tabIndex: this._getSelectedIndex(),
muiTheme: newMuiTheme,
});
},
render() {
let styles = this.getStyles();
let githubButton = (
<IconButton
iconStyle={styles.iconButton}
iconClassName="muidocs-icon-custom-github"
href="https://github.com/callemall/material-ui"
linkButton={true}
style={styles.github} />
);
let githubButton2 = (
<IconButton
iconStyle={styles.iconButton}
iconClassName="muidocs-icon-custom-github"
href="https://github.com/callemall/material-ui"
linkButton={true}/>
);
return (
<AppCanvas>
{githubButton}
{ this._getTabs() }
{this.props.children}
</AppCanvas>
);
},
_getTabs() {
let styles = {
root: {
backgroundColor: Colors.cyan500,
position: 'fixed',
height: 64,
top: 0,
right: 0,
zIndex: 1101,
width: '100%',
},
container: {
position: 'absolute',
right: (Spacing.desktopGutter / 2) + 48,
bottom: 0,
},
span: {
color: Colors.white,
fontWeight: Typography.fontWeightLight,
left: 45,
top: 22,
position: 'absolute',
fontSize: 26,
},
svgLogoContainer: {
position: 'fixed',
width: 300,
left: Spacing.desktopGutter,
},
svgLogo: {
width: 65,
backgroundColor: Colors.cyan500,
position: 'absolute',
top: 20,
},
tabs: {
width: 425,
bottom: 0,
},
tab: {
height: 64,
},
};
const materialIcon =
<EnhancedButton
style={styles.svgLogoContainer}
linkButton={true}
href="/#/home">
<img style={this.prepareStyles(styles.svgLogo)} src="images/material-ui-logo.svg"/>
<span style={this.prepareStyles(styles.span)}>material ui</span>
</EnhancedButton>
return (
<div>
<Paper
zDepth={0}
rounded={false}
style={styles.root}>
{materialIcon}
<div style={this.prepareStyles(styles.container)}>
<Tabs
style={styles.tabs}
value={this.state.tabIndex}
onChange={this._handleTabChange}>
<Tab
value="1"
label="GETTING STARTED"
style={styles.tab}
route="/get-started" />
<Tab
value="2"
label="CUSTOMIZATION"
style={styles.tab}
route="/customization"/>
<Tab
value="3"
label="COMPONENTS"
style={styles.tab}
route="/components"/>
</Tabs>
</div>
</Paper>
</div>
);
},
_getSelectedIndex() {
return this.props.history.isActive('/get-started') ? '1' :
this.props.history.isActive('/customization') ? '2' :
this.props.history.isActive('/components') ? '3' : '0';
},
_handleTabChange(value, e, tab) {
this.props.history.pushState(null, tab.props.route);
this.setState({tabIndex: this._getSelectedIndex()});
},
});
export default Master;
Basically I removed AppBar, AppLeftNav and FullWidthSection.
The only problem is that the Tabs hides some elements beneath it, see the picture below:
I must did something wrong, any ideas? Thanks!
Your root style is fixed. It will cause the element to stick on the top. Remove it.
OK, I found paddingTop: Spacing.desktopKeylineIncrement + 'px', in page-with-nav.jsx, which is the right solution.
The reason the elements beneath the navigation tab is covered is because fixed position elements are removed from the document flow and do not take up any space. So the elements are beginning at the top as if the header isn't there. What you have to do is use padding or margin to take up the space that would have been occupied by your header if it was in the normal flow.

Resources