Adding a css class in React through event handlers - javascript

I was trying to add a css class on mouse over just as mentioned in the react documentation but I am not sure why it is not working.
Please help
index.js:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
let className = "";
return (
<div className="App">
<h1
className={className}
onMouseOver={() => {
if (!className.includes("colorRed")) {
className += " colorRed";
}
}}
>
Hello CodeSandbox
</h1>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
style.css
.App {
font-family: sans-serif;
text-align: center;
}
.colorRed{
color:red;
}
Link: https://codesandbox.io/s/xjx72w5kkw?fontsize=14

This issue here is that you're mutating the className variable but this alone doesn't cause React to re-render your component.
You'll need to store the className in the state so that when you change it, React knows to re-render your component.
Here's a version of your sandbox making use of state: https://codesandbox.io/s/m51qoq72pp
// First we're importing useState from react
// This is part of the new Hooks mechanism
// More about hooks: https://reactjs.org/docs/hooks-intro.html
// You could also use a class and this.setState instead
import React, { useState } from "react";
// Then we set up our piece of state
const [className, setClassName] = useState("");
// Your event now uses setState
onMouseOver={() => {
if (!className.includes("colorRed")) {
setClassName(current => current + " colorRed");
}
}}

Related

Why is the keyword 'default' necessary when exporting a component withStyles

When I implement a simple React component with Mui's withStyles HOC, I have to use the keyword "default" when exporting the component. Why can't I use the HOC in the return statement within the functional component?
Is there something about Js or ReactJs that I'm missing?
Since I am forced to export this component as default, I lose the possibility to use the named import functionality, without using another import/export layer in between.
Below is the current working code:
// Card.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme => ({
card: {
margin: theme.spacing(2)
}
});
function CustomCard(props) {
const {classes} = props;
return (
<Card className={classes.card}>
Export me without being the default component.
</Card>
);
}
export default withStyles(styles)(MediaCard);
// Elsewhere.js
import CustomCard from "Card";
...
But i'd rather write something like this:
// Cards.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme =\> ({
card: {
margin: theme.spacing(2)
},
anotherCard: {
margin: theme.spacing(4)
}
});
export function CustomCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.card}>
Jeah. I'm not the default component.
</Card>
);
}
export function AnotherCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
}
// Elsewhere.js
import { CustomCard, AnotherCard } from "Cards";
...
You can do it the way you want to but you to change the way you define your components. The technical reason is that all exports except default need to be named, otherwise you can't import them and know what's what. Since withStyles() returns a statement and not a named variable/function you can't export it without a name.
export const AnotherCard = withStyles(styles)((props) => {
const {classes} = props;
return (
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
});
The downside of this is of course now your components aren't hoisted.

React useCallback() not working as expected

I'm trying to understand useCallback() a bit better by creating a simple React app and playing around with it.
I tried wrapping handleClick function in useCallback() statement and my expectation was that ItemList component should only be re-rendered if I click the count button, but not when I change the theme. However, it's rerendering on either of those button clicks and I'm not exactly sure why.
This is my code (GitHub repo available below):
index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import SebComponent from './SebComponent';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.Fragment>
<App />
<SebComponent />
</React.Fragment>
);
App.js
function App() {
return (
<div>
<p>This is App</p>
{console.log("App compoent rendered")}
</div>
);
}
export default App;
SebComponent.js
import React, { useCallback, useState } from "react";
import ItemList from "./itemList";
import "../src/sebstyle.css"
function SebComponent(){
console.log("rendering SebComponent...")
const [count, setCount] = useState(1)
const [dark, setDark] = useState(false)
let inlineStyle = {backgroundColor: dark ? "green" : "white"}
const handleClick = useCallback(() => {
setCount(count + 1)
}, [count])
return(
<div className="sebComponent" style={inlineStyle}>
<p>This is SebComponent</p>
<button onClick={handleClick}> {count} </button>
<br/>
<br/>
<button onClick={() => {setDark(x => !x)}}> change theme </button>
<ItemList count={count}/>
</div>
)
}
export default SebComponent;
itemList.js:
import React from "react";
export default function ItemList(props){
console.log("rendering item list...")
let myArray = [];
for (let index = 1; index < props.count; index++) {
myArray.push( 'item' + index);
}
console.log(myArray);
return(
<div>
<p>hello</p>
{myArray.map(
x => {
return (
<p key={"item" + x}> {x} </p>
)
}
)
}
</div>
)
}
sebstyle.css:
.sebComponent{
border: 2px black solid;
display:block;
align-items: center;
padding: 5px;
}
.sebComponent > button{
margin-left: 10px;
width: 100px;
height: 40px;
}
I tried creating something similar to what this guy did in his video. This is my GitHub repo created to play around with this.
If you want to skip rendering ItemList, then ItemList needs to use React.memo. This will make it so if ItemList's props have not changed, then ItemList will not rerender:
import { memo } from 'react';
function ItemList(props){
// ...
}
export default memo(ItemList);
The only role useCallback serves in preventing rendering is to make sure that props do not change. That in turn can allow memo to do its job. But handleClick is never being passed to ItemList, so nothing is happening to item list by memomizing handleClick
When one of those hook is invoked ( const [count, setCount] = useState(1)
const [dark, setDark] = useState(false) ) React re-render all the page. You can work-around with some library as redux that let manage way more better hook.
for more info read this: How to prevent re-rendering of components that have not changed?

React - Error: Invalid hook call. Hooks can only be called inside of the body of a function component

I wanted to use "React Bootstrap Hamburger Menu" HamburgerMenu copied the code from there and I get an error errorScreen
"Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem. "Here is my code
Mobile_Navbar.jsx
import React, { Component } from 'react';
import {
MDBNavbar,
MDBNavbarBrand,
MDBNavbarNav,
MDBNavItem,
MDBNavLink,
MDBNavbarToggler,
MDBCollapse,
MDBContainer
} from 'mdbreact';
import { BrowserRouter as Router } from 'react-router-dom';
class Test extends Component {
state = {
collapseID: ''
};
toggleCollapse = collapseID => () => {
this.setState(prevState => ({
collapseID: prevState.collapseID !== collapseID ? collapseID : ''
}));
};
render() {
return (
<Router>
<MDBContainer>
<MDBNavbar
color='light-blue lighten-4'
style={{ marginTop: '20px' }}
light
>
<MDBContainer>
<MDBNavbarBrand>Navbar</MDBNavbarBrand>
<MDBNavbarToggler
onClick={this.toggleCollapse('navbarCollapse1')}
/>
<MDBCollapse
id='navbarCollapse1'
isOpen={this.state.collapseID}
navbar
>
<MDBNavbarNav left>
<MDBNavItem active>
<MDBNavLink to='#!'>Home</MDBNavLink>
</MDBNavItem>
<MDBNavItem>
<MDBNavLink to='#!'>Link</MDBNavLink>
</MDBNavItem>
<MDBNavItem>
<MDBNavLink to='#!'>Profile</MDBNavLink>
</MDBNavItem>
</MDBNavbarNav>
</MDBCollapse>
</MDBContainer>
</MDBNavbar>
</MDBContainer>
</Router>
);
}
}
export default Test;
App.js
import React from 'react';
import './App.css';
import Test from './components/Mobile_Menu/Mobile_Navbar';
function App(props) {
return (
<div className="App">
<Test />
</div>
);
}
export default App;
It seems there is some kind of problem when we use the mdbootstrap Library MDBNavLink component. I didn't go deep on that to be able to explain why, though.
I solved this problem by importing the Link component from react-router-dom and use it instead with the className='nav-link'.
import { Link } from "react-router-dom";
//Snippet
// [....]
<MDBNavItem>
<Link
className='nav-link'
exact
to='/'
onClick={closeCollapse('mainNavbarCollapse')}
>
Home
</Link>
</MDBNavItem>
// [...]

How to theme components with Styled-Component and Material-UI

I've tried to follow exactly what's documented here:
How to theme components with styled-components and Material-UI?
import React from "react";
import { withTheme } from "#material-ui/core/styles";
import styled from "styled-components";
const StyledDiv = withTheme(styled.div`
background: ${props => props.theme.palette.primary.main};
color: ${props => props.theme.palette.primary.contrastText};
`);
export default function App() {
return (
<StyledDiv>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</StyledDiv>
);
}
But instead of using a "standard" HTML component like above I tried against a Material-UI Button with no luck. Can someone please help me with what I am doing wrong?
This is what I am trying:
styles.ts
import styled from 'styled-components';
import { withTheme } from "#material-ui/core/styles";
import { Button } from '#material-ui/core';
export const StyledInnerSignInButton = withTheme(styled(Button)`
margin: ${props => props.theme.spacing(3, 0, 2)};
`)
index.tsx
import { StyledInnerSignInButton } from './styles';
[...]
<StyledInnerSignInButton
type="submit"
fullWidth
variant="contained"
color="primary"
>
Sign In
</StyledInnerSignInButton>
I'm stuck here and still a React newbie. Any help is greatly appreciated!
I managed to resolve my issue. It was due to specificity. There are two ways to mitigate that, one is by wrapping the new component style with &&, for example:
export const StyledInnerSignInButton = withTheme(styled(Button)`
&& { margin: ${props => props.theme.spacing(3, 0, 2)}; }
`)
Or by manipulating the CSS Injection Order, as documented here
In your "master" index.tsx file, you will set up your code this way:
import React from 'react';
import { render } from 'react-dom';
import { StylesProvider } from '#material-ui/core/styles';
import Home from './pages/Home';
render(
<React.StrictMode>
<StylesProvider injectFirst>
{/* component name */}
<Home />
</StylesProvider>
</React.StrictMode>,
document.getElementById('root')
)
I hope that helps someone with the same problem as me.

How can Enzyme check for component visibility?

I've attached a cut down version of an issue I am having. I have a simple Checkbox which I hide using opacity : 0 depending on what is passed into the component containing the Checkbox (in this case MyCheckbox)
MyCheckBox.js
import React from "react";
import "./styles.css";
import { Checkbox, makeStyles } from "#material-ui/core";
const useStyles = makeStyles({
checkboxHiddenStyle: {
opacity: 0
}
});
export default function MyCheckbox(props) {
const styles = useStyles(props);
return (
<div>
<Checkbox
{...props}
className={props.data.length === 0 && styles.checkboxHiddenStyle}
/>
</div>
);
}
I have a component which uses MyCheckbox called MyCheckboxesInUse which results in one checkbox showing and the other being hidden.
How do I check the visibility of each of these in a Jest/Enzyme test ? I've looked at lots of posts but can't find something that works!
Code and test running here on CodeSandbox
MyCheckBoxesInUse.js
import React from "react";
import MyCheckbox from "./MyCheckbox";
import "./styles.css";
export default function MyCheckboxesInUse() {
const arrayWithNothing = [];
const arrayWithSomething = [1];
return (
<div className="App">
<h1>Hidden Checkbox</h1>
<MyCheckbox data={arrayWithNothing} />
<h1>Visible Checkbox</h1>
<MyCheckbox data={arrayWithSomething} />
</div>
);
}
MyCheckbox.test.js
import React from "react";
import Enzyme, { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import MyCheckboxesInUse from "./MyCheckboxesInUse";
import MyCheckbox from "./MyCheckbox";
Enzyme.configure({ adapter: new Adapter() });
test("Check that one checkbox is hidden and the other is visible", () => {
const wrapper = mount(<MyCheckboxesInUse />);
const checkboxes = wrapper.find(MyCheckbox);
expect(checkboxes).toHaveLength(2);
//HOW DO I CHECK THAT ONE IS VISIBLE AND THE OTHER IS NOT ?
});
You can tryout jest-dom from testing-library, but if you want to see how they implement what you want, check out their code: https://github.com/testing-library/jest-dom/blob/master/src/to-be-visible.js

Categories