styled component computing style but not applying it - reactjs

Dear genius StackOverflowians,
I am trying to write an app where users can configure questions and answers, along with defining help text for each question. I'm writing this in typescript React - which is handy when you want to define types of answers for questions.
I want to have a button next to the question that shows/hides a styled document. The button looks and works great, but the document that is hidden/shown doesn't get the generated style class that ought to be associated with it.
Here is the functional component to display the help document:
let HelpTextBody = function(props: { helpDocument: DocumentationStore }) {
return (
<div>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};
tok comes from a custom class DocumentationStore that is pretty much a wrapper around markdown-it, a handy js library for working with md files, which I would like my users to write their helptext in (and store it that way).
So I do this (in a different module for DocumentationStore class):
toReallySimple(): MdJson[] {
let bigParsed = this.md_.parse(this.Text, null).filter(
t => return t.type == "inline" || t.type.indexOf("open") > 0
});
Later on, I style HelpTextBody with:
const StyledHelpDocument = styled(HelpTextBody)`
background-color: lightslategray;
`;
Keeping it simple now so I can just see if it's working...
I then include it in a component with the button that I export:
class HelpText extends React.Component<helpProps, helpState> {
constructor(props: helpProps) {
super(props);
this.state = {
hidden: true
};
}
swapHidden() {
this.setState({
hidden: !this.state.hidden
});
}
render() {
if (this.state.hidden) {
return (
<span>
<StyledButton
itemScope={this.state.hidden}
onClick={() => this.swapHidden()}
>
Need Help?
</StyledButton>
</span>
);
} else {
return (
<span>
<StyledButton onClick={() => this.swapHidden()}>
Hide Help
</StyledButton>
<StyledHelpDocument helpDocument={this.props.helpDocument} />
</span>
);
}
}
So I webpack it all and get stuff into the browser, and what I get back is this style tag (after clicking the button), which looks right:
<style data-styled-components="">
/* sc-component-id: sc-bdVaJa */
.sc-bdVaJa {} .gscXTZ{background:red;color:white;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}.iwtdKP{background:white;color:red;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}
/* sc-component-id: sc-bwzfXH */
.sc-bwzfXH {} .hAvMqj{background-color:lightslategray;}</style>
But my html for the document is missing the reference to the class (.hAvMqj I guess?)
<span>
<button class="sc-bdVaJa iwtdKP">Hide Help</button>
<div><p>Here the text is grey</p></div>
<!-- ^This^ is the StyledHelpDocument... no class!-->
</span>
So where am I going wrong? I don't understand why it generates the style, and the component's HTML renders... but the class isn't applied to the component! What do you think?

Your styled-components class isn't being applied because you're styling a custom component, but you haven't included className as a prop. Add className as an optional prop in the component you're styling, and also be sure to apply className somewhere in the render method for that component. For your case, it should be added like so:
let HelpTextBody = function(props: { helpDocument: DocumentationStore, className: string }) {
return (
<div className={props.className}>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};

Related

Why is my SVG not appearing when passed as prop?

I've looked through some of the other, similar questions on here, but none of the solutions seem to work for me.
I have a class component that represents a cell on a grid, and for each cell, I want there to be a specific background image that is revealed with the cell is hovered. The default component looks like this:
const Home = () => {
return (
<div id="main">
<Box title='About' svg={ BG1 } />
<Box title='Work' svg={ BG2 } />
<Box title='Portfolio' svg={ BG3 } />
<Box title='Resume' svg={ BG4 } />
</div>
);
};
I want to pass SVG components as background images, but can't seem to figure it out. I've imported each background image like this:
import { ReactComponent as BG1 } from './bg1.svg';
In the Box component, I've added state to handle some of the child elements, and the title prop seems to work out well, but the svg prop doesn't seem to work for me:
class Box extends React.Component {
constructor(props) {
super(props);
this.state = {
hovered: false,
};
}
hoverOn = e => {
this.setState({ hovered: true });
};
hoverOff = e => {
this.setState({ hovered: false });
};
render() {
const { hovered } = this.state;
const style = hovered ? { transform: 'translateY(-20%)', opacity: '.05' } : {};
return(
<section onMouseEnter={this.hoverOn} onMouseLeave={this.hoverOff}>
<a>
<div className="overlay" style={ style }></div>
<div className="bg" style={{ backgroundImage: `url(${this.props.svg})` }} ></div>
<div className="innerContent">
<h2>
{this.props.title}
</h2>
</div>
</a>
</section>
);
};
};
Whenever I inspect my developmental site, I just see <div class='bg'></div>. Any suggestions?
It's not working because BG1 is being set to the SVG file contents; not a link to the SVG file. I think you can just reference the filename in url("./bg1.svg") and it will expand it at build time. However, that probably won't work when you just pass in the filename as a prop. So - you could put the SVGs into the public folder and do something like this: {process.env.PUBLIC_URL + '/img/logo.png'}. Escape hatch doc.
With Vite you can do this: import modelGLB from './static/model.glb?url' to get the URL, but I forget the create-react-app equivalent.

To change color on onclick event using react

I need to change the colour of a div tag which I have designed when it is clicked. I need to change the colour of the div and I need to add a class to the div when it is clicked, which I need answer in react
Like others mentioned, a little more code details would be nice from your side.
One of the ways to achieve what you want: https://jsfiddle.net/hawk939393/Ly0912nf/
class TodoApp extends React.Component {
state = {
divClicked: false
};
getClassname = () => {
return !this.state.divClicked ?
"toDoApp" : "toDoApp-isClicked"
};
render() {
return (
<div
className={this.getClassname()}
onClick={() => this.setState(prevState => ({divClicked: !prevState.divClicked}))}>
Hello world!
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
^ Have a state that tracks whether a div's className has been changed.

Change all Components with className="theme" from green to blue on an event in React

In App.css, I have
.theme {
color: green;
}
And I have className="theme" scattered in multiple components.
Is there a way to change the theme color from green to blue on an event?
If not, how should I design my code?
Well, You can create 2 classes named .blue-theme and .green-theme
Whenever, some event occurs,
onClick = (themeCol) => {
this.setState({theme:thmeCol})
}
render(){
return(
<button onClick={()=>onClick('blue-theme')}>Blue theme</button>
<button onClick={()=>onClick('green-theme')}>Green theme</button>
<div className={this.state.theme}> Sample </div>
)
}
You can pass the value of theme.
you can try
const themeClass = "";
if (event-as-wanted){
themeClass="theme";
}
className={themeClass}
also you can use style insted of className in same file
const theme ={
color: '';
};
style={theme}
and change it with events like
if (event-as-wanted){
theme.color = "green";
}
You can conditionally render the <style/> tag to override style definition for the class in the whole document.
class App extends React.Component {
state = {
red: true,
}
render() {
return (
<div>
<div className="foo">Foo</div>
<button onClick={() => this.setState({ red: !this.state.red })}>
Toggle Red
</button>
{this.state.red && <style>{".foo { color: red }"}</style>}
</div>
);
}
}
Keep in mind that inside JSX tags, curly brackets will be picked up by the interpreter and may break the parser. To avoid that, you should put your CSS inside a string like in the example above.
Adding a <style/> tag to CSS document will override any equally specific CSS rules that came before that. Once the condition is no longer met, the style tag will be removed and the original styling will be restored.
in react.js just set the state of color to whatever and on a click event toggle the color
class App extends React.Component {
constructor(props) {
super(props);
this.state = {color: green};
this.changeColor = this.changeColor.bind(this);
}
changeColor() {
const newcolor = this.state.color == green ? blue : green;
this.setState({ color: newcolor});
}
render() {
return (
<div class="theme" style={{color: this.statecolor}}>
<button onClick={this.changeColor}>
</button>
//put all html withing the parent DOM of element with class theme accordingly or it wont render.
</div>
);
}
Make two class, .green-theme{
color:'green'} and similarly, blue theme.
Mantain a REDUX STATE, CURRENT_THEME. Upon event fire, change the redux state accordingly and everywhere, where you want to use CURRENT_THEME, use it using mapStateToProps.
I would rather try to use almost pure CSS solution:
in App.css
#App.first-theme .theme { color:green; }
#App.second-theme .theme { color:blue; }
in App's render:
<div id="App" className={this.state.currentTheme}>
<AnotherComponent1 />
<AnotherComponent2 />
</div>
All you need to do is to change this.state.currentTheme appropriately. You can even use prop injected from the redux.
Almost all other solutions posted here have the same flaw: you have to adapt all your components to use the theme. Using this solution, you are able to change app's appearance without additional code in your components.
Trust me, injecting the same property from redux store/react context for every component will give you headaches and a lot of unnecessary code.
You should also try to avoid generating additional <style> tags - you will end up having plenty of !important and HTML, logic, and CSS in one file. What a mess! Imagine, what would happen if you would like to use SCSS in the future...

Conditional rendering class with map for React

I am not even sure if what I am trying is possible but here goes. For starters, I have an array of objects where each object has two key:value pairs. One key is "name", and the second key is a "genre".
Each of these objects is mapped into a div, using its genre in the class, which is then placed into a container div. What I am trying to do is control the class of each individual div by its class name which I control via the component's state. An idea of what I have is as follows:
constructor(props) {
super(props);
this.state = {
fineArt: false,
game: false,
literature: false,
movie: false,
music: false,
tv: false
}
<div className="inspiration-container">
{inspirations.map((item) => (`${this.state}.item.genre`) ? <div className={`inspiration ${item.genre}__selected`} key={item.name}>{item.name}</div> :
<div className={`inspiration ${item.genre}`} key={item.name}>{item.name}</div>)}
</div>
An example using a "game" would be for it to map the div out, and to check the value for "this.state.game". If that value is true it renders the div with the class of "inspiration game__selected" and if it is false it would just render "inspiration game".
I know this sort of thing is possible because I have already used it as:
{this.state.game ?
<a className="inspiration-button" onClick={this.toggleGame}><div className="game-button__selected"></div>Game</a> :
<a className="inspiration-button" onClick={this.toggleGame}><div className="game-button__unselected"></div>Game</a>}
My two trains of thought are that either 1) I am doing something wrong or 2) I am unable to access the state in this manner (because of how the mapping works). If I am doing something wrong, I assume it would be somewhere in the beginning of my ternary operator. The current result I am getting is the ternary operation is always true.
Any help is appreciated and thank your for reading this and any help you can provide.
You are accessing your state incorrectly
(`${this.state}.item.genre`)
This outputs to [Object object].item.genre since you're trying to convert an object to a string.
What you want to do is access it by this.state[item.genre] which accesses the property in the state
So your code should be
{ inspirations.map((item) =>
this.state[item.genre] ?
<div className={`inspiration ${item.genre}__selected`} key={item.name}>
{item.name}
</div> :
<div className={`inspiration ${item.genre}`} key={item.name}>
{item.name}
</div>
)
}
Even better would be to use the package classnames mentioned in comments by #Bryan Downing
https://github.com/JedWatson/classnames
https://www.npmjs.com/package/classnames
This would be the ideal way to use classnames (Be careful of conflicting classnames though if you use common classnames)
import classnames from 'classnames';
// render function
{ inspirations.map((item) =>
<div
className={
classnames('inspiration', item.genre, {
[`__selected`]: item.genre
})
}
key={item.name}
>
{item.name}
</div>
)
}
// CSS
.inspiration {
... // Your css
}
// If css not the same as .selected then you can do
// .game:not(.selected)
.game {
// Your css
}
.game.selected {
// Your css
}
.movie {
// Your css
}
.movie.selected {
// Your css
}
Instead of conditionally returning two separate elements, you can conditionally create just the className instead:
{ inspirations.map( item => {
const className = `inspiration ${item.genre}${this.state[item.genre] ? '__selected' : ''}`;
return <div className={className} key={item.name}>
{item.name}
</div>
}}

In React, Is it good practice to search for certain element in DOM?

Is it good to just specify className for element so i could find it later in the DOM through getElementsByClassName for manipulations?
Adding a class to find the DOM element? Sure you can do that, but refs are probably the better solution.
Manipulating the DOM element? That's an absolute no-go. The part of the DOM that is managed by React should not be manipulated my anything else but React itself.
If you come from jQuery background, or something similar, you will have the tendency to manipulate element directly as such:
<div class="notification">You have an error</div>
.notification {
display: none;
color: red;
}
.show {
display: block;
}
handleButtonClick(e) {
$('.notification').addClass('show');
}
In React, you achieve this by declaring what your elements (components) should do in different states of the app.
const Notification = ({ error }) => {
return error
? <div className="notification">You have an error</div>
: null;
}
class Parent extends React.Component {
state = { error: false };
render() {
return (
<div>
<Notification error={this.state.error} />
<button onClick={() => this.setState({ error: true })}>
Click Me
</button>
}
}
The code above isn't tested, but should give you the general idea.
By default, the state of error in Parent is false. In that state, Notification will not render anything. If the button is clicked, error will be true. In that state, Notification will render the div.
Try to think declaratively instead of imperatively.
Hope that helps.
When using React, you should think about how you can use state to control how components render. this.setState performs a rerender, which means you can control how elements are rendered by changing this.state. Here's a small example. I use this.state.show as a boolean to change the opacity of the HTML element.
constructor(props) {
super(props)
this.state = {
show: true
}
}
handleClick() {
this.setState({show: false})
}
render() {
const visibility = this.state.show ? 1 : 0
return (
<button style={{opacity: visibility} onClick={() => this.handleClick()}>
Click to make this button invisible
</button>
)
}

Resources