Apply inline style to an element in React from a child element - javascript

I am still very new to React and I am trying to recreate a navigation bar that was originaly made with HTML, CSS and JS.
I have a Layout component and a Navbar component. In the Navbar component I have a button that when clicked should apply an inline style or a CSS class to the <main>{children}</main> element in the Layout component.
The Navbar element is used inside the Layout element, so I am wondering if this is even possible?
layout.js
import React from "react"
import { useStaticQuery, graphql} from "gatsby"
import Navbar from "./navbar"
const Layout = ({ location, title, children }) => {
const data = useStaticQuery(graphql`
{
site {
siteMetadata {
menuLinks {
link
name
}
}
}
}
`)
return (
<div>
<Navbar pages={ data.site.siteMetadata.menuLinks } />
<main>{children}</main>
</div>
)
}
export default Layout
navbar.js
import React, { useState } from "react"
import { Link } from "gatsby"
import styles from "./styling/navbar.module.less"
const Navbar = ( props ) => {
const [navbarState, setNavbarState] = useState(false);
let toggleNavbar = () => {
console.log("Navbar toggle - Activated")
setNavbarState((navbarState) => !navbarState)
}
window.addEventListener('resize', windowResized)
return (
<nav id={"navigation-bar"}>
<div className={`${styles.navLinks} ${navbarState? styles.navActive:""}`}>
{props.pages.map((page, index) => (
<Link key={page.name} className={`${styles.navLink} ${styles.navLinkHoverEffect} ${navbarState? styles.navAnimate:""}`}
style={{animationDelay: `${index / 7 + 0.5}s`}} to={page.link}>
{page.name}
</Link>
))}
</div>
<div className={`${styles.burger} ${navbarState? styles.toggle:""}`} onClick={toggleNavbar}>
<div className={styles.line1}></div>
<div className={styles.line2}></div>
<div className={styles.line3}></div>
</div>
</nav>
)
}
export default Navbar

Sure it's possible. When you want the child, Navbar, to change something in the parent (normally state) you can pass a function down. In Layout:
const [classes, setClasses] = useState('');
const setClassNames = (classnames) => {
setClasses(classnames)
}
Pass this function down to the child as a prop.
<Navbar pages={ data.site.siteMetadata.menuLinks } setClassNames={setClassNames} />
When the action occurs on the child, say an onClick event then call that function with the classes string you want to set on main.
Then in your parent component, Layout change the line to:
<main className={classes}>{children}</main>
Of note, the way it's currently setup you could just pass the setClasses to Navbar but I used a new function to just to show a more common scenario when you have to customize anything. Also you should use useCallback on the function setClassNames in Layout if you don't want Navbar to re-render every time.
Feel free to leave a comment with questions.

Related

Why is my JS function only applying CSS to a single instance of my React component?

I have a component named "ProjectCard" and a component named "Work". In the component Work, I have created a function get_width() that gets the width of the screen and I am running that function in the useEffect Hook. I am also using useState hook that gets set inside the useEffect Hook and I am passing the state as a prop the ProjectCard Component that I am calling inside the Work Components , I have 6 of them. In the ProjectCard components, I have a function changeStyle(dType) that gets the prop and checks if it is 'Mobile'. If the condition becomes true, It gets the html element by ID and apply the CSS to it.
Now, the problem is that I have 6 ProjectCard Components inside my Work Component , but the CSS is being applied to only one of them. Can someone kindly explain this?
Work.js
import React , {useState , useEffect} from 'react';
import Navbar from './Navbar';
import ProjectCard from './ProjectCard';
import './Work.css';
function get_width()
{
var width = window.screen.availWidth;
console.log(width);
return width;
}
function Work()
{
const [device , setDevice] = useState('Desktop');
useEffect(()=>{
if(get_width() <= 450)
{
setDevice('Mobile');
}
} , [setDevice])
return(
<>
<Navbar/>
<div id="workMain">
<h1>Our Work</h1>
<div id="workButtons">
<button>All</button>
<button>Website</button>
<button>Mobile App</button>
</div>
<div id="cards">
<ProjectCard Device = {device}/>
<ProjectCard Device = {device}/>
<ProjectCard Device = {device}/>
<ProjectCard Device = {device}/>
<ProjectCard Device = {device}/>
<ProjectCard Device = {device}/>
</div>
</div>
</>
);
}
export default Work;
ProjectCard.js
import React , {useEffect} from 'react';
import './ProjectCard.css';
function changeStyle(dType)
{
if(dType === 'Mobile')
{
var card = document.getElementById("card");
card.style.marginLeft = 0;
}
console.log(dType);
}
function ProjectCard(props)
{
useEffect(()=>{
changeStyle(props.Device);
})
return(
<>
<div id="card">
<p>Website</p>
<p>RA Traders</p>
</div>
</>
);
}
export default ProjectCard;
This statement is causing the undesired behaviour:
var card = document.getElementById("card");
With getElementById Javascript looks for an element with id "card", inside your document. This is not confined to the react component, mind you. The whole DOM is searched and the first matching instance is returned. The first matching element will be the same no matter which component the function is called from). Note, ideally id should be unique in the document.
You can use refs here (React's recommended way for DOM manipulation). With hooks you have to use useRef
function changeStyle(dType, divElement)
{
if(dType === 'Mobile')
{
divElement.current.style.marginLeft = 0;
}
console.log(dType);
}
function ProjectCard(props)
{
const divRef = useRef();
useEffect(()=>{
changeStyle(props.Device,divRef);
})
return(
<>
<div ref={divRef}>
<p>Website</p>
<p>RA Traders</p>
</div>
</>
);
}
export default ProjectCard;
divRef.current will hold the value of the DOM node and you can make changes accordingly

How to render different style css on same component?

I have react component which is a button and I render this component three times. I want to add some CSS on the second component but I don't know how. I tried to add some class names, but then I can't figure it out where to put this style in the CSS.
I can change css in element.style in dev tools but can't in project.
import './App.css';
import './flow.css';
import './neonButton.css';
import GlowBox from './GlowBox';
import NavBar from './NavBar';
function App() {
return (
<div>
<div className='divBut'>
<NavBar></NavBar>, <NavBar className='drugi'></NavBar>,<NavBar></NavBar>
</div>
<GlowBox></GlowBox>
</div>
);
}
export default App;
import styled from 'styled-components';
const NavBar = (props) => {
return (
<div>
<Container>
<a class='neon'>Neon</a>
</Container>
</div>
);
};
const Container = styled.div`
background-color: transparent;
`;
export default NavBar;
I try to add props to component
<!-- begin snippet: js hide: false console: true babel: false -->
and then add a type to a component like this
const NavBar = (type) => {
return (
<div>
<Container>
<a class={`neon ${type}`}>Neon</a>
</Container>
</div>
);
};
<NavBar></NavBar>, <NavBar type='drugi'></NavBar>,<NavBar></NavBar>
but nothing is change.
You have props that you don't use, this is a good simple read on How to Pass Props to component, you can adjust this to other needs, this is example...:
import styled from 'styled-components';
const NavBar = ({class}) => {
return (
<div>
<Container>
<a class={class}>Neon</a>
</Container>
</div>
);
};
const Container = styled.div`
background-color: transparent;
`;
export default NavBar;
...
import './App.css';
import './flow.css';
import './neonButton.css';
import GlowBox from './GlowBox';
import NavBar from './NavBar';
function App() {
const NavStyles = {
className1: 'neon',
className2: 'drugi'
};
return (
<div>
<div className='divBut'>
<NavBar class={NavStyles.className1}></NavBar>, <NavBar class={NavStyles.className2}></NavBar>,<NavBar class={NavStyles.className1}></NavBar>
</div>
<GlowBox></GlowBox>
</div>
);
}
export default App;
Edit: Given that you have edited your question I have new information for you.
1.) You can't use the reserved word class in React, because class means something different in Javascript than it does in html. You need to replace all instances of class with className.
2.) Did you notice how in the devtools on your button it says the className says: neon [object object]?
You should use a ternary operator to handle the cases where you don't pass the type prop.
ex.) class={neon ${props?.type !== undefined ? type ''}}
3.) You are trying to apply a className to a component, which does not work. The className attribute can only be applied directly to JSX tags like h1, div, etc. Use a different prop name, then you can use that to decide the elements className.

How to apply css for a class in react component?

I am a new react-js learner and I am having a hard time adding css to my classes that I have inside my react component.
Here is the current code:
import React from 'react';
const Home = () => {
return (
<>
<div class="container">
<h1 class="mainHeader">Home</h1>
<h2>helloo</h2>
</div>
</>
);
};
.container {
// CSS would go here
}
export default Home;
In just HTML and CSS, I was able to apply css on the container div class by just using '.' and whatever the class name was. However, this is giving me an error.
Put the css in its own file, with a .css extension, then import it. Assuming you used create-react-app to set up your project, it will already have appropriate configuration for importing css files. Additionally, you need to use className for the prop, not class
// In a new file home.css:
.container {
// css goes here
}
// In the file you've shown:
import React from 'react';
import './home.css';
const Home = () => {
return (
<>
<div className="container">
<h1 className="mainHeader">Home</h1>
<h2>helloo</h2>
</div>
</>
);
};
export default Home;
Or you can declare it in json format or like you would an object, not in CSS form. Treat it as you are writing in js, which you actually are. See the edit below:
import React from 'react';
const Home = () => {
return (
<>
<div style={container}>
<h1 className="mainHeader">Home</h1>
<h2>helloo</h2>
</div>
</>
);
};
const container = {
// CSS would go here
color: 'red',
background: 'blue'
}
export default Home;

how to scroll from one component to an element of another component?

I have 2 components, Component A and Component B which are rendered in the same page. What I need is to scroll to a div of the Component B once I click on a button in the Component A.
Component B:
import React, { FC, useRef } from 'react';
const Footer: FC<Props> = ({ children }) => {
const commentSection = useRef(null);
const gotoCommentSection = () => window.scrollTo({top: commentSection.current.offsetTop, behavior: "smooth"})
return (
<>
<div className="footerClass" ref={commentSection}>
<div className="container">{children}</div>
</div>
</>
);
};
export default Footer;
if I insert this button in Component B, the scrolling function is working. But how can I achieve that from the Component A?
<button type="button" onClick={gotoCommentSection}>Scroll to Area</button>
I think it will work to move useRef up the component tree to Component A along with your scroll function. Then use React.forwardRef to apply that ref to the element in Component B. Looks like this is a decent example: https://stackoverflow.com/a/49832065/9325243

How can I customize the style of a React component shared between lazy-loaded pages?

I'm building a React application and I started using CRA. I configured the routes of the app using React Router. Pages components are lazy-loaded.
There are 2 pages: Home and About.
...
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
...
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/about" component={About} />
<Route path="/" component={Home} />
</Switch>
</Suspense>
...
Each page uses the Button component below.
import React from 'react';
import styles from './Button.module.scss';
const Button = ({ children, className = '' }) => (
<button className={`${styles.btn} ${className}`}>{children}</button>
);
export default Button;
The Button.module.scss file just sets the background color of the button to red.
.btn {
background: red;
}
The Button component accepts a className prop which is then added to the rendered button. This is because I want to give freedom to the consumer of the component. For example, in some pages margins could be needed or the background should be yellow instead of red.
To make it simple, I just want to have a different background color for the Button based on the current page, so that:
Home page => Blue button
About page => Yellow button
Each page is defined as below:
import React from 'react';
import Button from './Button';
import styles from './[PageName].module.scss';
const [PageName] = () => (
<div>
<h1>[PageName]</h1>
<Button className={styles.pageBtn}>[ExpectedColor]</Button>
</div>
);
export default [PageName];
where [PageName] is the name of the page and [ExpectedColor] is the corresponding expected color based on the above bullet list (blue or yellow).
The imported SCSS module, exports a class .pageBtn which sets the background property to the desired color.
Note: I could use a prop on the Button component which defines the variant to display (Blue/Yellow) and based on that prop add a class defined in the SCSS file. I don't want to do that since the change could be something that doesn't belong to a variant (e.g. margin-top).
The problem
If I run the application using yarn start, the application works fine. However, if I build the application (yarn build) and then I start serving the application (e.g. using serve -s build), the behavior is different and the application doesn't work as expected.
When the Home page is loaded, the button is correctly shown with a blue background. Inspecting the loaded CSS chunk, it contains:
.Button_btn__2cUFR {
background: red
}
.Home_pageBtn__nnyWK {
background: blue
}
That's fine. Then I click on the navigation link to open the About page. Even in this case, the button is shown correctly with a yellow background. Inspecting the loaded CSS chunk, it contains:
.Button_btn__2cUFR {
background: red
}
.About_pageBtn__3jjV7 {
background: yellow
}
When I go back to the Home page, the button is now displayed with a red background instead of yellow. That's because the About page has loaded the CSS above which defines again the Button_btn__2cUFR class. Since the class is now after the Home_pageBtn__nnyWK class definition, the button is displayed as red.
Note: the Button component is not exported on the common chunk because its size is too small. Having that in a common chunk could solve the problem. However, my question is about small shared components.
Solutions
I have thought to 2 solutions which, however, I don't like too much:
Increase selectors specificity
The classes specified in the [PageName].module.scss could be defined as:
.pageBtn.pageBtn {
background: [color];
}
This will increase the selector specificity and will override the default Button_btn__2cUFR class. However, each page chunk will include the shared components in case the component is quite small (less than 30kb). Also, the consumer of the component has to know that trick.
Eject and configure webpack
Ejecting the app (or using something like react-app-rewired) would allow specifying the minimum size for common chunk using webpack. However, that's not what I would like for all the components.
To summarize, the question is: what is the correct working way of overriding styles of shared components when using lazy-loaded routes?
You can use the following logic with config file for any pages. Also, You can send config data from remote server (req/res API) and handle with redux.
See Demo: CodeSandBox
create components directory and create files like below:
src
|---components
|---Button
| |---Button.jsx
| |---Button.module.css
Button Component:
// Button.jsx
import React from "react";
import styles from "./Button.module.css";
const Button = props => {
const { children, className, ...otherProps } = props;
return (
<button className={styles[`${className}`]} {...otherProps}>
{children}
</button>
);
};
export default Button;
...
// Button.module.css
.Home_btn {
background: red;
}
.About_btn {
background: blue;
}
create utils directory and create AppUtils.js file:
This file handle config files of pages and return new object
class AppUtils {
static setRoutes(config) {
let routes = [...config.routes];
if (config.settings) {
routes = routes.map(route => {
return {
...route,
settings: { ...config.settings, ...route.settings }
};
});
}
return [...routes];
}
static generateRoutesFromConfigs(configs) {
let allRoutes = [];
configs.forEach(config => {
allRoutes = [...allRoutes, ...this.setRoutes(config)];
});
return allRoutes;
}
}
export default AppUtils;
create app-configs directory and create routesConfig.jsx file:
This file lists and organizes routes.
import React from "react";
import AppUtils from "../utils/AppUtils";
import { pagesConfig } from "../pages/pagesConfig";
const routeConfigs = [...pagesConfig];
const routes = [
...AppUtils.generateRoutesFromConfigs(routeConfigs),
{
component: () => <h1>404 page not found</h1>
}
];
export default routes;
Modify index.js and App.js files to:
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
rootElement
);
...
react-router-config: Static route configuration helpers for React
Router.
// App.js
import React, { Suspense } from "react";
import { Switch, Link } from "react-router-dom";
import { renderRoutes } from "react-router-config";
import routes from "./app-configs/routesConfig";
import "./styles.css";
export default function App() {
return (
<div className="App">
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<Suspense fallback={<h1>loading....</h1>}>
<Switch>{renderRoutes(routes)}</Switch>
</Suspense>
</div>
);
}
create pages directory and create files and subdirectory like below:
src
|---pages
|---about
| |---AboutPage.jsx
| |---AboutPageConfig.jsx
|
|---home
|---HomePage.jsx
|---HomePageConfig.jsx
|
|---pagesConfig.js
About Page files:
// AboutPage.jsx
import React from "react";
import Button from "../../components/Button/Button";
const AboutPage = props => {
const btnClass = props.route.settings.layout.config.buttonClass;
return (
<>
<h1>about page</h1>
<Button className={btnClass}>about button</Button>
</>
);
};
export default AboutPage;
...
// AboutPageConfig.jsx
import React from "react";
export const AboutPageConfig = {
settings: {
layout: {
config: {
buttonClass: "About_btn"
}
}
},
routes: [
{
path: "/about",
exact: true,
component: React.lazy(() => import("./AboutPage"))
}
]
};
Home Page files:
// HomePage.jsx
import React from "react";
import Button from "../../components/Button/Button";
const HomePage = props => {
const btnClass = props.route.settings.layout.config.buttonClass;
return (
<>
<h1>home page</h1>
<Button className={btnClass}>home button</Button>
</>
);
};
export default HomePage;
...
// HomePageConfig.jsx
import React from "react";
export const HomePageConfig = {
settings: {
layout: {
config: {
buttonClass: "Home_btn"
}
}
},
routes: [
{
path: "/",
exact: true,
component: React.lazy(() => import("./HomePage"))
}
]
};
...
// pagesConfig.js
import { HomePageConfig } from "./home/HomePageConfig";
import { AboutPageConfig } from "./about/AboutPageConfig";
export const pagesConfig = [HomePageConfig, AboutPageConfig];
Edited section:
With HOC Maybe this way: CodeSandBox
create hoc dir and withPage.jsx file:
src
|---hoc
|---withPage.jsx
...
// withPage.jsx
import React, { useEffect, useState } from "react";
export function withPage(Component, path) {
function loadComponentFromPath(path, setStyles) {
import(path).then(component => setStyles(component.default));
}
return function(props) {
const [styles, setStyles] = useState();
useEffect(() => {
loadComponentFromPath(`../pages/${path}`, setStyles);
}, []);
return <Component {...props} styles={styles} />;
};
}
And then pages like below:
src
|---pages
|---about
| |---About.jsx
| |---About.module.css
|
|---home
|---Home.jsx
|---Home.module.css
About.jsx file:
// About.jsx
import React from "react";
import { withPage } from "../../hoc/withPage";
const About = props => {
const {styles} = props;
return (
<button className={styles && styles.AboutBtn}>About</button>
);
};
export default withPage(About, "about/About.module.css");
About.module.css file:
// About.module.css
.AboutBtn {
background: yellow;
}
Home.jsx file:
// Home.jsx
import React from "react";
import { withPage } from "../../hoc/withPage";
const Home = props => {
const { styles } = props;
return <button className={styles && styles.HomeBtn}>Home</button>;
};
export default withPage(Home, "home/Home.module.css");
Home.module.css file:
// Home.module.css
.HomeBtn {
background: red;
}
I would suggest instead of adding both the default styles and the consumer styles, use the consumer's styles over yours and use your as a callback if not supplied. The consumer can still compose your defaults with the composes keyword.
Button.js
import React from 'react';
import styles from './Button.module.scss';
const Button = ({ children, className}) => (
<button className={className ?? styles.btn}>{children}</button>
);
export default Button;
SomePage.module.scss
.pageBtn {
// First some defaults
composes: btn from './Button.module.scss';
// And override some of the defautls here
background: yellow;
}
If you wish, use sass #extends or #mixin instead
EDIT: Haven't tested it, but could it be that just by using composes webpack will make sure to bundle the defaults only once? Thus you're no longer needed to change your Button.js code with the ??
Solution 1
I know this is very obvious, but would work anyway:
Set !important on your overwriting css rules, thus bypassing specificity:
[PageName].module.scss:
.btn {
color: yellow !important;
}
However, most of the strict devs I know would avoid this keyword at all cost.
Why ?
Because when you start to have a lot of !important your css is a nightmare to debug. If you start writing !important rules with higher specificity, you know you have gone too far
It is only meant for corner-cases like yours, you might as well use it.
Solution 2
fix CRA config to enforce style tags order.
It is open-source after all :)
You can give your input on this bug here (upvote might give it more visibility):
https://github.com/facebook/create-react-app/issues/7190
Solution 3 (Update)
You could create a SCSS mixin in a new customButton.scss file, to generate css rules with higher specificity:
// customButton.scss
#mixin customBtn() {
:global {
.customBtn.override {
#content;
}
}
}
We will use two static class names (using the :global selector), because that way their name won't change based on where they are imported from.
Now use that mixin in your pages' SCSS:
// [pageName].module.scss
#import 'customButton.scss';
#include customBtn {
color: yellow;
}
css output should be:
.customBtn.override {
// put everything you want to customize here
color: yellow;
}
In Button.jsx: apply both class names to your button in addition to styles.btn:
// Button.jsx
const Button = ({ children, className = '' }) => (
<button className={`${styles.btn} customBtn override ${className}`}>
{children}
</button>
);
(Note that these are not referenced through the styles object, but the classname directly)
The main drawback is these are not dynamic class names, so you have to watch out to avoid conflicts yourself like we use to do before.
But I think it should do the trick

Categories

Resources