I have an object which I map through and convert the key/value pairs to Material-UI TreeItems. Is it a way to add unique styling (font-weight bold) to TreeItems if they are expandable?
TreeItem is expandable when it has any children. Otherwise, the children is undefined. You can conditionally render different styles after checking if any children exist:
import TreeView from "#material-ui/lab/TreeView";
import MuiTreeItem from "#material-ui/lab/TreeItem";
const useTreeItemStyles = makeStyles({
label: {
fontWeight: (props) => (props.children ? "bold" : "initial")
}
});
function TreeItem(props) {
const classes = useTreeItemStyles(props);
return <MuiTreeItem {...props} classes={{ label: classes.label }} />;
}
Live Demo
Related
I have a simple question that has to do with React rendering. Here's the link to the code sandbox before I explain: https://codesandbox.io/s/list-rerendering-y3iust?file=/src/App.js
Here's the deal, I have an array of objects stored in a parent component called App. Each object has a 'checked' field which I want to toggle on clicking on that object's respective checkbox. I loop through the array of objects and display them each within a Child component. When I click on a checkbox, the handleChange function executes and that list is updated within the parent component, causing App to rerender along with ALL of the Child components. The problem I want to solve is, how can I make it so I only rerender the Child component that was clicked instead of all of them?
I tried using useCallback along with a functional update to the list state, but that didn't do anything and it still rerenders all of the child components instead of the one being toggled. I have a hunch that I'm using useCallback incorrectly, and that a brand new function is being created. I'd like an explanation of how React does it's rerendering when it comes to arrays, comparing the previous array against the new array. I understand that in my code I'm providing a copy of the original list by destructuring it and then putting it inside a new array, which obviously is not a reference to the original list so React sets the copy as the new state:
App.js
import { useCallback, useState } from "react";
import Child from "./Child";
import "./styles.css";
const mockList = [
{ text: "1", id: 1, checked: false },
{ text: "2", id: 2, checked: false },
{ text: "3", id: 3, checked: false },
{ text: "4", id: 4, checked: false },
{ text: "5", id: 5, checked: false }
];
export default function App() {
const [list, setList] = useState(mockList);
const handleChange = useCallback((checked, id) => {
setList((oldList) => {
for (let i = 0; i < oldList.length; i++) {
if (oldList[i].id === id) {
oldList[i].checked = checked;
break;
}
}
return [...oldList];
});
}, []);
return (
<div className="App">
{list.map((item) => (
<Child
key={item.id}
text={item.text}
checked={item.checked}
handleChange={(checked) => handleChange(checked, item.id)}
/>
))}
</div>
);
}
Child.js
const Child = ({ text, checked, handleChange }) => {
console.log("Child rerender");
return (
<div
style={{
display: "flex",
border: "1px solid green",
justifyContent: "space-around"
}}
>
<p>{text}</p>
<input
style={{ width: "20rem" }}
type="checkbox"
checked={checked}
onChange={(e) => handleChange(e.checked)}
/>
</div>
);
};
export default Child;
Here's how you optimize it, first you use useCallback wrong, because every rerender the (e) => handleChange(e.checked) is a new instance, hence even if we memo the Child it will still rerender because props is always new.
So we need to useCallback to the function that invoke handleChange see my forked codesandbox
https://codesandbox.io/s/list-rerendering-forked-tlkwgh?file=/src/App.js
React has nothing to do with how you manipulate your arrays or objects. It simply goes on rendering your app tree and there are certain rules that decide when to stop rerendering a certain branch within the tree. Rendering simply means React calls the component functions which themselves return a sub-tree or nodes. Please see https://reactjs.org/docs/reconciliation.html
Try wrapping the Child with React.memo():
const Child = ({ text, checked, handleChange }) => {
console.log("Child rerender");
return (
<div
style={{
display: "flex",
border: "1px solid green",
justifyContent: "space-around"
}}
>
<p>{text}</p>
<input
style={{ width: "20rem" }}
type="checkbox"
checked={checked}
onChange={(e) => handleChange(e.checked)}
/>
</div>
);
};
export default React.memo(Child);
What this React.memo() does is essentially compare previous props with next props and only rerenders the Child component if any of the props have changed and doesn't re-render if the props have stayed the same.
As a side note: Please read this https://kentcdodds.com/blog/usememo-and-usecallback
TLDR: Over optimisation at the cost of code complexity is not recommended. And preventing “unnecessary” renders probably will have a drawback in the form of extra memory usage and computation: so only need to do it if very sure it will help in a specific problem.
I'm very new to react. And I'm trying to learn some new stuff. So what I want to do is to add CSS within my Header.js file, And I don't know how to do that. Because I don't want to import external or inline CSS. But rather use it like in Html with tag on the header. But not just that, I want to use that CSS specifically for the file, in this case, Header.js.
This might help
Header.js
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
// alignSelf: 'center',
},
textStyle: {
marginTop: Metrics.ratio(0),
marginHorizontal: Metrics.ratio(70),
fontFamily: Fonts.type.base,
fontSize: Fonts.size.normal,
color: Colors.black,
},
});
class Header extends React.Component {
render() {
return (
<div style={ styles.color } />
);
}
}
You have several possibilites.
The simplest is to use css code directly in the element like
<div style={{color:'#000',backgroundColor:'#fff'}}>
...
</div>
Or you can use the libraries for that, like styled-components (https://styled-components.com/) for this.
You need to import this:
import styled from 'styled-components';
Then you can define your element css on the top of the page, i.e. SomeCSSStyling
const SomeCSSStyling = styled.div`
color:#000;
background-color:#fff;
`
Then you can use this constant in the class code of the react component:
class MyReactComponent extends Component {
constructor(props) {
...
}
render() {
return (
<SomeCSSStyling>
...
</SomeCSSStyling>
);
}
}
export default MyReactComponent;
UPDATE:
You can also define :hover or ::before etc. with style-components:
const SomeCSSStyling = styled.div`
color:#000;
background-color:#fff;
&:hover{
font-weight:bold;
}
`
You can create style object inside react component like this:
const myDivStyles = {
color: "red",
fontSize: "32px"
}
All propertis are the same like in CSS but this one with "-" sign change in to camelCase nams, e.g. font-size change to fontSize, background-color change to backgroundColor.
Then you can add this style to elements in your components by style attribute.
render() {
return (
<div style={ myDivStles } />
)
}
You can also describe style without creating style object like this:
<div style={{ color: "red", backgroundColor: "#fff" }} />
Be sure you are using double closure {{ }}.
EDIT
With :hover selector
You have two prosibilites. First you can use component state to determinate if component is hovered and then prepare correct style, e.g:
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
hovered: false
}
this.toggleHover = this.toggleHover.bind(this)
}
toggleHover(state) {
this.setState({
hovered: state
})
}
render() {
const styles = {
color: this.state.hovered ? "red" : "blue"
}
return (
<div style={styles} onMouseEnter={ () => this.toggleHover( true ) } onMouseLeave={ () => this.toggleHover( false ) }>
Text
</div>
);
}
}
Second you can use js styled components syntax and refer to other component, you can read more about this here: https://styled-components.com/docs/advanced#referring-to-other-components
But to be honest when I dealing with :hover or other selectors I prefer using default css files or much more often scss files prepared for components. So when I have e.g Button component in same location I have Button.css ( or Button.scss ) file where I can work with standard css. After this I have css files connected with components which should handled them.
I am relatively new to React-JS and was wondering how I could pass my variables to my export function. I am using the jsPDF library.
At the time the Summary page is showing up, every thing is already in the database.
The Summary page creates in every round an IdeaTable component, writes it into an array and renders it bit by bit if the users click on the Next button (showNextTable()).
This component can use a JoinCode & playerID to assemble the table that was initiated by this player.
import React, { Component } from "react";
import { connect } from "react-redux";
import { Box, Button } from "grommet";
import IdeaTable from "../playerView/subPages/ideaComponents/IdeaTable";
import QuestionBox from "./QuestionBox";
import { FormUpload } from 'grommet-icons';
import jsPDF from 'jspdf';
export class Summary extends Component {
state = {
shownTable: 0
};
showSummary = () => {};
showNextTable = () => {
const { players } = this.props;
const { shownTable } = this.state;
this.setState({
shownTable: (shownTable + 1) % players.length
});
};
exportPDF = () => {
var doc = new jsPDF('p', 'pt');
doc.text(20,20, " Test string ");
doc.setFont('courier');
doc.setFontType('bold');
doc.save("generated.pdf");
};
render() {
const { topic, players } = this.props;
const { shownTable } = this.state;
const tables = [];
for (let i = 0; i < players.length; i++) {
const player = players[i];
const table = (
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={player.id} />
</Box>
);
tables.push(table);
}
return (
<Box
style={{ wordWrap: "break-word" }}
direction="column"
gap="medium"
pad="small"
overflow={{ horizontal: "auto" }}
>
<QuestionBox question={topic} />
{tables[shownTable]}
<Button
primary
hoverIndicator="true"
style={{ width: "100%" }}
onClick={this.showNextTable}
label="Next"
/>
< br />
<Button
icon={ <FormUpload color="white"/> }
primary={true}
hoverIndicator="true"
style={{
width: "30%",
background: "red",
alignSelf: "center"
}}
onClick={this.exportPDF}
label="Export PDF"
/>
</Box>
);
}
}
const mapStateToProps = state => ({
topic: state.topicReducer.topic,
players: state.topicReducer.players
});
const mapDispatchToProps = null;
export default connect(mapStateToProps, mapDispatchToProps)(Summary);
So basically how could I include the IdeaTable to work with my pdf export?
If you want to use html module of jsPDF you'll need a reference to generated DOM node.
See Refs and the DOM on how to get those.
Alternatively, if you want to construct PDF yourself, you would use data (e.g. from state or props), not the component references.
Related side note:
On each render of the parent component you are creating new instances for all possible IdeaTable in a for loop, and all are the same, and most not used. Idiomatically, this would be better:
state = {
shownPlayer: 0
};
Instead of {tables[shownTable]} you would have:
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={shownPlayer} ref={ideaTableRef}/>
</Box>
And you get rid of the for loop.
This way, in case you use html dom, you only have one reference to DOM to store.
In case you decide to use data to generate pdf on your own, you just use this.props.players[this.state.shownPlayer]
In case you want to generate pdf for all IdeaTables, even the ones not shown, than you can't use API that needs DOM. You can still use your players props to generate your own PDF, or you can consider something like React-Pdf
I'm creating a Material UI table what I believe is the default way, and I'm getting huge padding internally.
I have tried using withStyles and passing the resulting class into through the component property, like this:
const StyledPaper = withStyles(theme => ({
root: {
padding: "0",
},
}), Paper);
...
<Table component={StyledPaper}>
I have tried making classes and passing them in:
const useStyles = makeStyles(theme => ({
root: {
padding: "0",
},
}));
...
const classes = useStyles();
...
<Table classes={classes}>
I have futzed around endlessly and I'm not having any effect at all.
Any suggestions?
If you look at the DOM element class name, you would find out that it starts with MuiPaper-root under the MuiGrid-root element.
Perhaps use nesting selector is a good approach in this situation for customized styles
import { withStyles } from "#material-ui/core/styles";
const styles = {
root: {
"& .MuiPaper-root": {
padding: 0
}
}
};
...
const { classes } = this.props;
...
export default withStyles(styles)(App);
usage
Notice it's not inside the Table so you may want to add the padding for Grid
<Grid container>
<Grid item className={classes.root} // ...>
// ...
</Grid>
</Grid>
Similar online demo and related QA:
How to change material-ui Textfield label styles in react
So essentially I have a React component <Bore />. And I have an array of Bores and I need to style the first and last element of the array. I know how to access these elements with Bores[0] and Bores[Bores.length-1]. But my problem is figuring out how to style these specific components after creation. Would I have to do something like className += "newClass". I'm only 2 days into using React so any help would be greatly appreciated.
You can use style objects instead of mutating the class list. The important thing to remember is that CSS properties are camel case. Something like
class Parent extends Component {
constructor(props){
super(props);
this.state = {
style: {
backgroundColor: "green",
marginRight: "10px"
}
}
}
changeStyle = () => {
this.setState(() => {
return {
style: {
marginLeft: "10px",
backgroundColor: "red"
}
}
})
}
render = () => {
return (
<div>
<Child style={this.state.style} changeStyle={this.changeStyle}
</div>
)
}
}
const Child = ({ style, changeStyle }) => {
return (
<div style={style} onClick={changeStyle}>
<h1>Dummy</h1>
</div>
)
}
https://jsfiddle.net/rfhmxts2/ see here, click on the div to change it's background color and margins