Let's say i have a file with themes:
themes.js:
import {createMuiTheme} from "#material-ui/core/styles";
export const myTheme = createMuiTheme({
palette: {
text: {
color: "#545F66",
},
},
});
And file with App.js, where render looks something like this:
return (
<MuiThemeProvider theme={myTheme}>
<CssBaseline />
<MyComponent />
</MuiThemeProvider>
);
Now i know that i can access the theme via withStyles:
const StyledMyComponent = withStyles(theme => ({
something: {
color: theme.palette.text.color
}
}))(props => <MyComponent {...props} />);
But what i want to achieve is something different.
MyComponent is a very big component and has for example class called "react-table-1"
And the thing i want is to set the class color "react-table-1" to theme.palette.text
so something like this:
const StyledMyComponent = withStyles(theme => ({
"react-table-1": {
color: theme.palette.text
}
}))(props => <MyComponent {...props} />);
But obviously it doesn't work.
Does anyone know if this is possible ? And how can i achieve that.
I can set "react-table-1" color in css file but i want to change it inside
react via button, and that's why i need something like this.
Live demo:
https://stackblitz.com/edit/react-jt9xs1
You may want to try nesting-selectors for className
I found that you can't simply add className to ReactDataGrid, it's probably related to this lib, you can make a workaround for it.
Some notice points:
If you check the DOM structure, you would find out that the ReactDataGrid Root class is react-grid-Container, not react-grid-Main
Material-UI withStyles is used as a HOC for component, for specific usage plz refer to the link at the bottom.
The problem in the post is rarely related to Theme, you can use your Theme as normal.
If you want to bind your button with styles, make an outer layer of styles hooks and pass the state down to makeStyles would be fine.
import React, { useState } from "react";
import ReactDataGrid from "react-data-grid";
import { makeStyles } from "#material-ui/core";
const columns = [
{ key: "id", name: "ID" },
{ key: "title", name: "Title" },
{ key: "complete", name: "Complete" }
];
const rows = [
{ id: 0, title: "Task 1", complete: 20 },
{ id: 1, title: "Task 2", complete: 40 },
{ id: 2, title: "Task 3", complete: 60 }
];
const useStyles = makeStyles(theme => ({
root: {
"& div.react-grid-Container": {
color: "red",
// color: theme.palette.text.color
}
}
}));
const App = () => {
const classes = useStyles();
const [row, setRow] = useState([]);
const onBrutForce = e => {};
return (
<div className={classes.root}>
<ReactDataGrid
columns={columns}
rowGetter={i => rows[i]}
rowsCount={3}
enableCellSelect={true}
/>
<br />
This is what i want to achieve but with "ChangeTheme" button. <br />
Because i want to set the style to other components too. <br />
<button onClick={onBrutForce} style={{ margin: "10px" }}>
(click me)
</button>
</div>
);
};
export default App;
Related styling QA:
Functional and classical styling
Convert functional to classical
Related
So, I have been attempting to read about this error ( Objects are not valid as a React child. If you meant to render a collection of children, use an array instead and here https://www.g2i.co/blog/understanding-the-objects-are-not-valid-as-a-react-child-error-in-react ), and I (think I) understand it's an issue of passing a 'complex' object to react's child component/passing a JS object to a component. (am I correct?)
I have located where my issue lays in my code, but I cannot really understand why would the variable be interpreted as a complex object in the first place.
The issue occurs when I'm trying to create at TodoApp.js, addToDo() function.
This function is later on transferred to a child component 'TodoForm.js', so it could, on handleSubmit, call his father's function addToDo():
In addToDo(), if I edit, where I add a new element, the field txt 's value to a string for example, it works.
TodoApp.js (in code's comments I have pointed out where is exactly the issue)
import React, {useState} from "react";
import TodoList from './TodoList';
import Paper from '#mui/material/Paper';
import List from '#mui/material/List';
import TextField from '#mui/material/TextField';
import ListItem from '#mui/material/ListItem';
import Divider from '#mui/material/Divider';
import ListItemText from '#mui/material/ListItemText';
import { AppBar, Toolbar, Typography } from "#mui/material";
import { fontWeight } from "#mui/system";
import TodoForm from "./TodoForm";
export default function () {
let tasks = [
{id: 1, txt: "thisisTask1", completed: true},
{id: 2, txt: "thisisITask2", completed: false},
{id: 3, txt: "SO ORIGINAL", completed: false}
]
let [tasksVal, tasksEdit] = useState(tasks);
const tasksTxts = tasksVal.map((task) =>
<><ListItem><ListItemText>{task.txt}</ListItemText></ListItem> <Divider/></>
)
//issue seems to lay here - if I change txt: "texthere" - no errors (but doesn't add the task either)
let addToDo = (taskTxt) => { // a function to later on send to TodoForm for when a Submit occures.
tasksEdit([...tasksVal, { id: 4, txt: {taskTxt}, completed: false }])
};
return(
<div>
<Paper style={{padding: 0, margin: 0, height: "100vh", backgroundColor: "whitesmoke"}}>
<AppBar position='static' style={{height: "64px"}} color="primary">
<Toolbar>
<Typography style={{letterSpacing: "3px", fontSize: "40px"}}>Todos with Hooks!</Typography>
</Toolbar>
</AppBar>
<TodoList tasksTxts={tasksTxts}/>
<TodoForm AddToDo={addToDo}/>
</Paper>
</div>
)
}
TodoForm.js
import React from "react";
import useInputStatee from "./hooks/useInputStatee";
import { Paper, TextField } from "#mui/material";
export default function ({AddToDo}) {
const [inputVal, inputChange, inputReset] = useInputStatee("");
console.log(inputVal)
return (
<div>
<Paper>
<form onSubmit={e => {
AddToDo(inputVal);
// inputReset();
}}>
<TextField value={inputVal} onChange={inputChange}/>
</form>
<p>{inputVal}</p>
</Paper>
</div>
)
}
useInputStatee.js
import React, {useState} from "react";
export default function (initVal) {
let [value, setValue] = useState(initVal);
let handleChange = (e) => { // when we get from a form ... we get an e.target.value
setValue(e.target.value);
}
let reset = () => { setValue(""); }
return [value, handleChange, reset];
}
TodoList.js
import React from "react";
import Paper from '#mui/material/Paper';
import List from '#mui/material/List';
import TextField from '#mui/material/TextField';
import ListItem from '#mui/material/ListItem';
import ListItemText from '#mui/material/ListItemText';
export default function ({tasksTxts}) {
return(
<div>
<Paper>
<List>
{tasksTxts}
</List>
</Paper>
</div>
)
}
Thanks in advance!
The issue is in tasksTxts, it's rendering an object instead of valid JSX.
const tasksTxts = tasksVal.map((task) =>
<>
<ListItem>
<ListItemText>
{task.txt} // <-- task.txt is object { taskTxt }
</ListItemText>
</ListItem>
<Divider/>
</>
);
...
let addToDo = (taskTxt) => {
tasksEdit([
...tasksVal,
{
id: 4,
txt: { taskTxt }, // <-- caused here
completed: false
}
])
};
The tasksTxts needs to access into the additional taskTxt property:
const tasksTxts = tasksVal.map((task) =>
<>
<ListItem>
<ListItemText>
{task.txt.taskTxt}
</ListItemText>
</ListItem>
<Divider/>
</>
);
Or the task.txt just needs to be the value you want to display:
let addToDo = (taskTxt) => {
tasksEdit(tasksVal => [
...tasksVal,
{ id: 4, txt: taskTxt, completed: false }
])
};
Unrelated Suggestion
You will want to also add a valid React key to the mapped tasks.
Example:
const tasksTxts = tasksVal.map((task) =>
<React.Fragment key={task.id}> // <-- Valid React key
<ListItem>
<ListItemText>
{task.txt}
</ListItemText>
</ListItem>
<Divider/>
</React.Fragment>
);
You are correct, the error is inside the addToDo() function:
let addToDo = (taskTxt) => {
tasksEdit([...tasksVal, { id: 4, txt: {taskTxt}, completed: false }])
// ^^^^^^^^^ the error is here
};
taskTxt is being wrapped inside another object instead of just being put on directly.
So the correct version of this code is:
tasksEdit([...tasksVal, { id: 4, txt: taskTxt, completed: false }])
// ^^^^^^^ note no wrapping braces any more
I am making a simple Reactjs accordion application in which collapse and expanding of each individual items are done.
Requirement:
A simple requirement is that I am in the need to toggle the text of the heading as Expand or Shrink based on the click.
If we click any of the item then the content will gets displayed in that case the text changes to Shrink as because the accordion gets opened so the Shrink title is given for closing of the accordion.
Complete working example:
https://codesandbox.io/s/react-accordion-forked-lcyr0
In the above example I have used the following code to change the text,
Accordion.js
import React, { useState } from "react";
import Text from "./text";
import Heading from "./heading";
import getAccordion from "./GetAccordion";
const Accordion = getAccordion(1);
const accordionData = [
{
id: 1,
content: "This is a first content"
},
{
id: 2,
content: "This is a second content"
},
{
id: 3,
content: "This is a third content"
}
];
const NormalAccordion = () => {
const [toggleValue, setToggleValue] = useState(-1);
const toggleHandler = (index) => {
setToggleValue(index);
};
return (
<div>
{accordionData.map((item, index) => (
<Accordion>
<Heading>
<div
style={{ padding: "10px", cursor: "pointer" }}
className="heading"
onClick={() => toggleHandler(index)}
>
{toggleValue !== index ? `Expand` : `Shrink`}
</div>
</Heading>
<Text>{item.content}</Text>
</Accordion>
))}
</div>
);
};
export default NormalAccordion;
And this line {toggleValue !== index ? `Expand` : `Shrink`} changes the text once but after that it doesn't make any changes on further toggle over the title(Expand/Shrink).
Kindly help me to achieve the result of toggling the text between Expand and Shrink based on the respective clicks.
You should reset the toggleValue if the item is already clicked :
const toggleHandler = (index) => {
index===toggleValue?setToggleValue(-1): setToggleValue(index);
};
then use condition to render the current content:
<Text>{toggleValue === index && item.content}</Text>
and simplify the Text component to this one :
<div style={{ ...this.props.style }}>
<div className={`content ${this.props.text ? "open" : ""}`}>
{this.props.children}
</div>
</div>
You should use boolean value as an accordion can have 2 values.
Idea:
Create a standalone accourdion component which defines a behavior of expand/ collapse.
Create a wrapper view component that calls this this component.
Pass your data as a prop from this view instead of using a local hard coded object.
Benefit of this approach is that you have 2 modular components that work only on props. These can be exported later to their own standalone library.
Ideally, a component should be dumb and should only know its own behavior based on state and props.
Updated sandbox
Accoirdion component changes
const [toggleValue, setToggleValue] = useState(false);
const toggleHandler = () => {
setToggleValue(!toggleValue);
};
Accordion view changes
const AccordionView = ({ accordionData }) => {
return (
<div>
{accordionData.map((item) => (
<NormalAccordion content={item.content} />
))}
</div>
);
};
Constants: I create a folder called constants that will hold hardcoded objects and then we pass it using props from index.js
export const accordionData = [{
id: 1,
content: "This is a first content"
},
{
id: 2,
content: "This is a second content"
},
{
id: 3,
content: "This is a third content"
}
];
Changes in index.js
import { accordionData } from '../constants/accordion-data'
...
<NormalAccordion accordionData={ accordionData } />
I have an intersectionObserver that watches some sections and highlights the corresponding navigation item. But I've only managed to get the "main sections Microsoft, Amazon working, but not the subsections Define, Branding, Design, Deduction. As seen in the gif below:
The reason why I want it structured this way is so that I can highlight the "main" sections if the subsections are in view.
Semi working demo: https://codesandbox.io/s/intersection-with-hooks-fri5jun1344-fe03x
It might seems that I might be able to copy and paste the same functionality with the subsections as well. But I'm having a hard time wrapping my head around how to deal with nested data + useRef + reducer. I was wondering if someone could give me a pointer in the right direction.
Here is an gif of the desired effect. Notice the main title (Loupe, Canon) are still highlighted if one of the subsections are in view:
It all starts with an data array
const data = [
{
title: "Microsoft",
id: "microsoft",
color: "#fcf6f5",
year: "2020",
sections: ["define", "branding", "design", "deduction"]
},
{
title: "Amazon",
id: "amazon",
color: "#FFE2DD",
year: "2018",
sections: ["define", "design", "develop", "deduction"]
},
{
title: "Apple",
id: "apple",
color: "#000",
year: "2020",
sections: ["about", "process", "deduction"]
}
];
App.js padding data object into reduce to create Refs
const refs = data.reduce((refsObj, Case) => {
refsObj[Case.id] = React.createRef();
return refsObj;
}, {});
My components passing in the props
<Navigation
data={data}
handleClick={handleClick}
activeCase={activeCase}
/>
{data.map(item => (
<Case
key={item.id}
activeCase={activeCase}
setActiveCase={setActiveCase}
refs={refs}
data={item}
/>
))}
Case.js
export function Case({ data, refs, activeCase, setActiveCase }) {
const components = {
amazon: Amazon,
apple: Apple,
microsoft: Microsoft
};
class DefaultError extends Component {
render() {
return <div>Error, no page found</div>;
}
}
const Tag = components[data.id] || DefaultError;
useEffect(() => {
const observerConfig = {
rootMargin: "-50% 0px -50% 0px",
threshold: 0
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.target.id !== activeCase && entry.isIntersecting) {
setActiveCase(entry.target.id);
}
});
}, observerConfig);
observer.observe(refs[data.id].current);
return () => observer.disconnect(); // Clenaup the observer if unmount
}, [activeCase, setActiveCase, refs, data]);
return (
<React.Fragment>
<section
ref={refs[data.id]}
id={data.id}
className="section"
style={{ marginBottom: 400 }}
>
<Tag data={data} />
</section>
</React.Fragment>
);
}
I've tried mapping the subsections like this but I get stuck at this part:
const subRefs = data.map((refsObj, Case) => {
refsObj[Case] = React.createRef();
return refsObj;
}, {});
Working Example
I've found a solution while trying to keep most of your logic intact. Firstly what you need to do is to store the subrefs (the sections ref) in the same object as your Case ref. So you will need an extra reduce function to create those inside the refs object:
App.js
const refs = data.reduce((refsObj, Case) => { // Put this outside the render
const subRefs = Case.sections.reduce((subrefsObj, Section) => {
subrefsObj[Section] = React.createRef();
return subrefsObj;
}, {});
refsObj[Case.id] = {
self: React.createRef(), // self is the Case ref, like Apple, Microsoft...
subRefs // This is going to be the subrefs
};
return refsObj;
}, {});
Then you add an extra state to handle which sub section is active, like const [activeSection, setActiveSection] = React.useState(); And you put it anywhere you also use the activeCase. You need that because you said that the Case and Sections need to work independently. (Both active at the same time).
Case.js
You will need to pass along the subrefs to the child components, so you do:
<Tag data={data} subRefs={refs[data.id].subRefs} />
And you will also need the intersection observer for each of the subrefs. So your useEffect will look like:
useEffect(() => {
const observerConfig = {
rootMargin: "-50% 0px -50% 0px",
threshold: 0
};
const observerCallback = (entries, isCase) => {
const activeEntry = entries.find(entry => entry.isIntersecting);
if (activeEntry) {
if (isCase) setActiveCase(activeEntry.target.id);
else setActiveSection(activeEntry.target.id);
} else if (isCase) {
setActiveCase(null);
setActiveSection(null);
}
};
const caseObserver = new IntersectionObserver(
entries => observerCallback(entries, true),
observerConfig
);
caseObserver.observe(refs[data.id].self.current);
const sectionObserver = new IntersectionObserver(
entries => observerCallback(entries, false),
observerConfig
);
Object.values(refs[data.id].subRefs).forEach(subRef => {
sectionObserver.observe(subRef.current);
});
return () => {
caseObserver.disconnect();
sectionObserver.disconnect();
}; // Clenaup the observer if unmount
}, [refs, data]);
Then in your amazon/index.js ,microsoft/index.js and apple/index.js files. You pass along the ref again:
<Template
data={this.props.data}
caseSections={caseSections}
subRefs={this.props.subRefs}
/>
Finally, in your template.js file you will have the following so you can assign the right ref:
const Template = props => {
return (
<React.Fragment>
<div
sx={{
background: "#eee",
transition: "background ease 0.5s"
}}
>
{props.data.sections &&
props.data.sections.map(subItem => (
<Container
ref={props.subRefs && props.subRefs[subItem]}
id={`${props.data.id}--${subItem}`}
key={subItem}
className="article"
>
<Section sectionId={subItem} caseSections={props.caseSections} />
</Container>
))}
</div>
</React.Fragment>
);
};
I believe most of it is covered in the post. You can check your forked working repo here
You can simplify your code. You don't really need refs or intersectionObservers for your use case. You can simply scrollIntoView using document.getElementById (you already have ids to your navs.
You can do setActiveCase very well in handleClick.
Working demo
Modify handleClick like this
const handleClick = (subTabId, mainTabName) => {
//console.log("subTabName, mainTabName", subTabId, mainTabName);
setActiveCase({ mainTab: mainTabName, subTab: subTabId.split("--")[1] }); //use this for active tab styling etc
document.getElementById(subTabId).scrollIntoView({
behavior: "smooth",
block: "start"
});
};
Navigation.js Call handleClick like this.
{item.sections &&
item.sections.map(subItem => (
<div
className={`${styles.anchor}`}
key={`#${item.title}--${subItem}`}
sx={{ marginRight: 3, fontSize: 0, color: "text" }}
href={`#${item.title}--${subItem}`}
onClick={e => {
handleClick(`${item.id}--${subItem}`, item.id);
e.stopPropagation();
}}
>
{toTitleCase(subItem)}
</div>
))}
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
I am working on a homepage that consists of eight different components representing various aspects of the page content. I want to have three of them be editable using a custom built InlineEditor component that uses Draft.js. When I tried to do this initially, the editing toolbars for the second and third component would only work on the first component. So this is a Draft.js issue. I was able to get everything to work by creating three separate InlineEditor components and then using each one individually when necessary, but that seems redundant.
I'm thinking there has to be a better way to do this using HOCs or render props but I'm struggling to set that up properly. Any ideas on what might be a better way to go about this?
Here's the initial InlineEditor component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { EditorState, convertFromRaw } from 'draft-js'
import Editor from 'draft-js-plugins-editor'
import createUndoPlugin from 'draft-js-undo-plugin'
import createToolbarPlugin, { Separator } from 'draft-js-static-toolbar-plugin'
import createToolbarLinkPlugin from 'draft-js-toolbar-link-plugin'
import {
ItalicButton,
BoldButton,
UnderlineButton,
CodeButton,
HeadlineOneButton,
HeadlineTwoButton,
HeadlineThreeButton,
UnorderedListButton,
OrderedListButton,
BlockquoteButton,
CodeBlockButton
} from 'draft-js-buttons'
import { Flex, Box } from 'rebass'
import FontAwesome from 'react-fontawesome'
import {
ToolbarNav,
EditPanel,
StaticToolbar,
TextInfo,
DefaultButton,
SuccessButton,
InlineLink
} from 'styles'
// Set up Draft.js toolbar and plugins
const undoPlugin = createUndoPlugin()
const toolbarLinkPlugin = createToolbarLinkPlugin({
inputPlaceholder: 'Insert URL here...'
})
const { LinkButton } = toolbarLinkPlugin
const { UndoButton, RedoButton } = undoPlugin
const toolbarPlugin = createToolbarPlugin({
structure: [
BoldButton,
ItalicButton,
UnderlineButton,
CodeButton,
Separator,
HeadlineOneButton,
HeadlineTwoButton,
HeadlineThreeButton,
Separator,
UnorderedListButton,
OrderedListButton,
BlockquoteButton,
CodeBlockButton,
LinkButton,
Separator,
UndoButton,
RedoButton
]
})
const { Toolbar } = toolbarPlugin
const plugins = [toolbarPlugin, toolbarLinkPlugin, undoPlugin]
class InlineEditor extends Component {
displayName = 'inline editor component'
constructor(props) {
super(props)
this.state = {
editorState: EditorState.createWithContent(
convertFromRaw(this.props.rawContent)
),
showURLInput: false,
urlValue: '',
readOnly: true
}
}
onChange = editorState => this.setState({ editorState })
focus = () => this.refs.editor.focus()
onEdit = e => {
e.preventDefault()
this.setState({
readOnly: false
})
}
onSave = () => {
// save new content
this.setState({
showURLInput: false,
urlValue: '',
readOnly: true
})
}
onCancel = () => {
// cancel editing
this.setState({
editorState: EditorState.createWithContent(
convertFromRaw(this.props.rawContent),
this.decorator
),
showURLInput: false,
urlValue: '',
readOnly: true
})
}
renderToolbar = () => {
return (
<ToolbarNav>
<StaticToolbar>
<Toolbar />
</StaticToolbar>
</ToolbarNav>
)
}
render() {
const { editorState, readOnly } = this.state
const { auth } = this.props
return (
<EditPanel>
<Flex wrap>
<Box w={'90%'}>{!readOnly && this.renderToolbar()}</Box>
<Box mt={1}>
<Editor
editorState={editorState}
onChange={this.onChange}
plugins={plugins}
ref="{(element) => { this.editor = element }}"
readOnly={readOnly}
/>
{auth.isAuthenticated &&
readOnly && (
<TextInfo>
<InlineLink onClick={this.onEdit} title="Edit">
<FontAwesome name="pencil" /> Edit
</InlineLink>
</TextInfo>
)}
</Box>
<Box width={'40%'} mr={1} mt={1}>
{!readOnly && (
<DefaultButton
className={`block`}
type="button"
onClick={this.onCancel}>
Cancel
</DefaultButton>
)}
</Box>
<Box width={'40%'} mt={1}>
{!readOnly && (
<SuccessButton
className={`block`}
type="button"
onClick={this.onSave}>
Save
</SuccessButton>
)}
</Box>
</Flex>
</EditPanel>
)
}
}
const mapStateToProps = state => {
return {
auth: state.auth
}
}
export default connect(mapStateToProps)(InlineEditor)
Currently this is being accessed by components such as About like so:
return (
<InlineEditor
auth={ this.props.auth }
rawContent={ about }/>
)
I was able to solve this problem by creating one InlineEditor component that passed in the toolbar plugins inside of its constructor, i.e.
constructor(props) {
super(props)
this.undoPlugin = createUndoPlugin()
this.toolbarLinkPlugin = createToolbarLinkPlugin({
inputPlaceholder: "Insert URL here...",
})
// etc...
}