React conditional class name setting for styling - css

I am trying to make a quiz app using React.
I am currently working on the main quiz page where I have 4 buttons, and each of the buttons denotes an answer I'm importing from a question bank.
I want the current selected button to be highlighted, and for this I am currently using a state for each button. Is there any way to just use one state and deal with all four of the buttons, as this way is too tedious and cannot be used for a large number of such buttons? Also, I want only one button, the one the user selects finally, to be highlighted. So for this reason I need to set the state of all the other buttons to null, which makes the task even more tedious.
Here is the div containing the buttons
<div>
<button className={selected1} onClick={() => dealingWithOptions("A")}>{questions[currentQuestion].optionA}</button>
<button className={selected2} onClick={() => dealingWithOptions("B")}>{questions[currentQuestion].optionB}</button>
<button className={selected3} onClick={() => dealingWithOptions("C")}>{questions[currentQuestion].optionC}</button>
<button className={selected4} onClick={() => dealingWithOptions("D")}>{questions[currentQuestion].optionD}</button>
</div>
Here is the function dealing with the options clicking
const [selected1,setSelectedButton1] = useState("")
const [selected2,setSelectedButton2] = useState("")
const dealingWithOptions = (op) => {
setOptionChosen(op);
if (op=="A") {
setSelectedButton1("selected1");
setSelectedButton2("")
setSelectedButton3("")
setSelectedButton4("")
} else if (op=='B') {
setSelectedButton1("");
setSelectedButton2("selected2")
setSelectedButton3("")
setSelectedButton4("")
} else if (op=='C') {
setSelectedButton1("");
setSelectedButton2("")
setSelectedButton3("selected3")
setSelectedButton4("");
}
else if (op=='D') {
setSelectedButton1("");
setSelectedButton2("")
setSelectedButton3("")
setSelectedButton4("selected3");
}
}

It can be solved and optimized in many ways. I am trying to give what suit your current code most.
I assume you have a state that stores choosen option.
Now update all the button like this
<button className={choosen == "A" ? "selected" : "" } onClick={() => dealingWithOptions("A")}>{questions[currentQuestion].optionA}</button>
Here choosen is the state where the selected option is being stored.
Explanation: Here what we are doing is, we are matching for each button that if it is the selected button then add the selected class else add nothing.

I would suggest having one state, that stores the chosen option (so either A, B, C or D) and then in your JSX part you have a condition that assigns the classname "selected" to the appropiate button.
If the selection changes so will the state, which triggers a rerender (so you don't even have to take away the "selected" class, since it will just be assinged to (and only) the right one on rerender

I'd suggest you set one piece of state to maintain the selected button.
setSelectedButton('A')
Or undefined if none is selected
Then,
<button className={selectedButton === 'A' ? 'selected1' : ''}>...
That said for conditional classes I'd use something like clsx rather than building class strings manually
e.g.
<button className={clsx({selected1: selectedButton === 'A'})}>...

Several ways to achieve this. You can set one state: const [selectedId, setSelectedId] = useState("")
Define the function: const selectHandler = (e) => setSelectedId(e.target.name)
On your buttons call the function and set the state to the Id: <button className={selectedId === "a" && "selected"} name="a" onClick={() => setSelectedId("a")}>Button A</button> For the rest of the buttons, change the name as well as the parameter passed to the function. Also the string inside the className comparison

Related

Dynamically and conditionaly changing css of a button in reactJs

In a react application how to dynamically change css of a button when a form input validation fails(There are lot of input fields which accept marks)
You can do it like this
<button style={{backgroundColor: this.state.error ? 'red' : 'green'}}>Save</button>
or change the class based on the error state and style in the css file like this
<button className={this.state.error && 'error'}>Save</button>
Hope this helps
PS this.state.error assumes you are using a class component
Also you can give className conditionally. If you don't want to set anything when condition is false, then just leave as empty string ''
<button className={ this.state.something ? 'first-class' : 'second-class' }>Clicker</button>
If you want to add a className attribute conditionally, then
<button { ...(booleanValue && { className: 'some-class' }) }>Clicker</button>

Conditional CSS Reactjs in Tab Navigation

I have my custom tab navigation in React.js. I want to change the background color of the active tab using conditional rendering or state change. I tried passing state for color but nothing is changing in CSS. Here is my code link:
https://stackblitz.com/edit/reacttabs
Please help!
you have to conditionally style the li element
first define a variable for active tab style
var active = Object.assign({},tabStyles);
active.backgroundColor = '#000';
then inside the render conditionally call the desired style
<li style={this.state.active == '1' ? active : tabStyles} onClick={() => {this.toggle('1')}}>A</li>
<li style={this.state.active == '2' ? active : tabStyles} onClick={() => {this.toggle('2')}}>B</li>
I fixed some errors, so here is a final working result
RESULT

position absolute - float within screen limits (React)

I'm trying to create an App with a global dictionary; so that when a word that appears in the dictionary is hovered than a small box appears next to it with a definition.
The problem is that the text in the dictionary can appear any where on the screen, and I need to align the floating box so that it will not be displayed out side of the screen
Similar to this
only that I need to be able to style the floating box, like this
Note that the box display outside of the screen:
I tired to use ui material ToolTip
but it throws
TypeError
Cannot read property 'className' of undefined
I solved a similar problem before with jQuery, where I dynamically calculated the position of the box, relative to the screen and the current element.
but I don't know how to do it in react, mainly since I don't know how to get the position of the current element dynamical.
Please help
To give an idea where to start, have a look at useCallback and refs for React. With the given information from node.getBoundingClientRect(), you could calculate if your tooltip is outside the visible area of the browser.
// edit: useCallback won't work in this case, because the visibility is triggered by a css hover and the dimensions are not yet available for the hidden tooltip. Here is a possible solution with useRef and use useEffect though:
function ToolTip({ word, description }) {
const [left, setLeft] = useState(0);
const [hover, setHover] = useState(false);
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
const { right } = ref.current.getBoundingClientRect();
if (window.innerWidth < right) {
setLeft(window.innerWidth - right);
}
}
}, [hover]);
return (
<span
desc={description}
className="dashed"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => {
setHover(false);
setLeft(0);
}}
>
{word}
<div ref={ref} className="explain" style={{ left }}>
<h2>{word}</h2>
{description}
</div>
</span>
);
}
Codepen example: https://codesandbox.io/s/lk60yj307
I was able to do it with
https://www.npmjs.com/package/#syncfusion/ej2-react-popups
But I still wonder what is the correct way to do it in code.

How to get access to the children's css values from a styled component?

I am using a REACT BIG CALENDAR and I want to get access to the css values in one of my functions.
I created a style component and override the library
const StyledCalendar = styled(Calendar);
Now for example there is a div inside of the Calendar with the class = "hello",
How would I access the css values of "hello" in a function? Similar to property lookup say in stylus.
I have tried window.getComputedStyle(elem, null).getPropertyValue("width") but this gives the css of the parent component.
If you know the class name, you should be able to select that and give that element to getComputedStyle instead of giving it StyledCalendar. Something like:
const childElement = document.getElementsByClassName('hello')[0];
const childWidth = getComputedStyle(childElement).getPropertyValue('width');
(this assumes that there's only one element with the class 'hello' on the page, otherwise you'll have to figure out where the one you want is in the node list that's returned by getElementsByClassName)
You can do it using simple string interpolation, just need to be sure that className is being passed to Calendar's root element.
Like this:
const StyledCalendar = styled(Calendar)`
.hello {
color: red;
}
`
Calendar component
const Calendar = props => (
// I don't know exact how this library is structured
// but need to have this root element to be with className from props
// if it's possible to pass it like this then you can do it in this way
<div className={props.className}>
...
<span className="hello"> Hello </span>
...
</div>
)
See more here.

Updating element CSS on PageA from button on PageB

I am using tabs for an app. I want a user button which when clicked on tab-detail.html to update the CSS of an element on its parent tab page tab.html
.controller('TabCtrl', function($scope,Tabs) {
$scope.tabs = Tabs.all() ;
// this populates the "tab.html" template
// an element on this page is: <span id="tab_selected_1">
// when user selects a listed item on tab.html
// it calls tab-detail.html
})
.controller('TabDetailCtrl', function($scope,$stateparams,Tabs) {
$scope.tabs = Tabs.get($stateparams.tabID) ;
// on tab-detail.html is a button <button ng-click="tabSelect()">
$scope.tabSelect = function(thisID) {
// update css on TabCtrl elementID
document.getElementById('tab_selected_1').style.color = "green" ;
}
})
The only way to get to tab-detail.html is via tab.html, thus tab.html must be loaded. But no matter what method I try I can't seem to find a way to access the element that is on another controller's page.
I have tried:
var e = angluar.element('tab_selected_1');
or
var e = angluar.element(document.querySelector('tab_selected_1') ;
e.style.color = "green" ;
The approach you are doing will never do a JOB for you as the DOM you want isn't available. You could achieve this by creating a sharable service that will maintain all of this variable in it and it will be used on UI. For ensuring binding of them your service variable should be in object structure like styleData OR you could also achieve this by creating angular constant.
app.constant('constants', {
data: {
}
});
Then you could inject this constant inside you controller & modify it.
.controller('TabCtrl', function($scope, Tabs, constants) {
$scope.constants = constants; //make it available constants on html
$scope.tabs = Tabs.all() ;
// this populates the "tab.html" template
// an element on this page is: <span id="tab_selected_1">
// when user selects a listed item on tab.html
// it calls tab-detail.html
})
.controller('TabDetailCtrl', function($scope,$stateparams,Tabs, constants) {
$scope.tabs = Tabs.get($stateparams.tabID) ;
$scope.constants= constants; //make it available constants on html
// on tab-detail.html is a button <button ng-click="tabSelect()">
$scope.tabSelect = function(thisID) {
// update css on TabCtrl elementID
$scope.constants.data.color = "green" ;
}
})
Markup
<div id="tab_selected_1" ng-style="{color: constants.data.color || 'black'}">
one way to do this is ....
1) Create a service
2) set a value to a variable in service on button click(tab-detail.html)
3) use that service variable value in tab.html
(Correction update at bottom)
#pankajparkar solution does work, however it does not work with IONIC as the IONIC Framework somehow overrides the DOM settings. Via the DOM Element inspector an see: style='color:green' being added inline to the ITEM/SPAN and can see the element defined as: element.style{ color: green}...but the color of the rendered HTML does not change....it stays black.
Further research shows this is somehow an IONIC problem as other users have the same problem. Other SOFs and blogs indicate that there appears to be a work around but I have yet to see it work.
The above is reformatted for others future use (even though it doesn't work with IONIC), thus I am still looking for a solution to work with IONIC:
.constant('constants', {
tabColors: {
curID:0,
},
})
.controller('TabCtrl', function($scope,Tabs,constants) {
$scope.constants = constants;
}
.controller('TabDetailCtrl', function($scope,$stateparams,Tabs,constants) {
$scope.constants = constants;
$scope.setItem= function(thisID) {
$scope.constants.tabColors.oldID = $scope.constants.tabColors.curID ;
delete $scope.constants.tabColors['tabID_'+$scope.constants.tabColors.curID] ;
$scope.constants.tabColors.curID = thisID ;
$scope.constants.tabColors['tabID_'+thisID] = 'green' ;
}
// HTML in Tab.html
<span id='tab_tabID_{{tab.tabID}}' ng-style="{color: constants.tabColors['tabID_'+tab.tabID] || 'black'}">
Some Text Here
</span>
//HTML in TabDetail.html
<button id="tab_button" class="button button-small button-outline button-positive" ng-click="setItem({{tab.tabID}});">
Select This Item
</button>
Correction: This method does work and does work with IONIC. The problem with IONIC is every element embedded within an ionic tag <ion-item>... <ion-nav>
...etc inherits its own properties from predefined classes...so you must either update the class (not optimal) or have ID tags on every element and/or apply CSS changes (using above method) to every child element. This is not optimal however it will work.
In my case my HTML actually looked like:
<span id='tab_tabID_{{tab.tabID}}' ng-style="{color: constants.tabColors['tabID_'+tab.tabID] || 'black'}">
<h2>Header Text Here</h>
<p>More text here</p>
</span>
The above CSS method works with this:
<span id='tab_tabID_{{tab.tabID}}'>
<h2 ng-style="{color: constants.tabColors['tabID_'+tab.tabID] || 'black'}">
Header Text Here
</h>
<p ng-style="{color: constants.tabColors['tabID_'+tab.tabID] || 'black'}">
More text here
</p>
</span>

Resources