import React from "react";
import "./SideBarPanel.css";
import AddIcon from "#mui/icons-material/Add";
import ExpandMoreIcon from '#mui/icons-material/ExpandMore';
const SideBarIcon = () => {
return (
<div class="sidebar-icon group">
<AddIcon/>
</div>
);
};
const SideBarPanel = () => {
return (
<div className="sidebar-icons">
<SideBarIcon />
<SideBarIcon />
</div>
);
};
export default SideBarPanel;
I want to pass the ExpandMoreIcon into the second SideBarIcon component.
You should be able to pass a component down to a child component via props just like a normal variable. There is one caveat though: when you render it in the child component, you have to ensure it is named with first letter a capital letter.
import React from "react";
import "./SideBarPanel.css";
import AddIcon from "#mui/icons-material/Add";
import ExpandMoreIcon from '#mui/icons-material/ExpandMore';
const SideBarIcon = (props) => {
const {icon: Icon} = props;
return (
<div class="sidebar-icon group">
{Icon && <Icon />}
<AddIcon/>
</div>
);
};
const SideBarPanel = () => {
return (
<div className="sidebar-icons">
<SideBarIcon />
<SideBarIcon icon={ExpandMoreIcon} />
</div>
);
};
export default SideBarPanel;
Here I assign icon to a new variable Icon while destructuring. This is important because <icon /> will not work because the first letter is not capitalized.
Sandbox
Source: https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized
I would write <Icon /> as a child of <SideBarIcon />. You can have it as optional child. Also in this case you would approach component composition, which is recommended by React.
Related
I am having two buttons in a component thats under App component. Even though it is dispatching actions and store is updated, App is not re-rendered. Ideally on button press, I add some dummy name that will be put inside array and then a different component is rendered based on number items inside this array.
Can someone tell what is going wrong, I am a beginner to react and redux
Sandbox: https://codesandbox.io/s/panel-item-with-redux-hxmihq?file=/src/PanelAdder.js
App.jsx
import React from "react";
import "./styles.css";
import { Panel } from "./Panel";
import PanelAdder from "./PanelAdder";
import { useSelector } from "react-redux";
export default function App() {
const panels = useSelector((state) => state.panelItems);
return (
<>
<PanelAdder />
<div className="cards-container">
{panels || [].map((name) => <Panel {...{ name }} />)}
</div>
</>
);
}
PanelAdder.jsx
import React from "react";
import { useDispatch } from "react-redux";
import { addPanel } from "./actions/counterActions";
export default function PanelAdder() {
const dispatch = useDispatch();
const handleClick = (name) => {
dispatch(addPanel(name));
};
return (
<>
<button onClick={() => handleClick("Panel 1")}> Add Panel 1</button>
<button onClick={() => handleClick("Panel 2")}> Add Panel 2</button>
</>
);
}
Panel.jsx
import React from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faSquareMinus,
faWindowMaximize,
faRectangleXmark
} from "#fortawesome/free-solid-svg-icons";
export const Panel = ({ name }) => {
return (
<>
<div className="card">
<div className="card-actions">
<FontAwesomeIcon icon={faSquareMinus} />
<FontAwesomeIcon icon={faWindowMaximize} />
<FontAwesomeIcon icon={faRectangleXmark} />
</div>
<div className="card-body">{name}</div>
</div>
</>
);
};
You have 2 issues:
First in your combineReducers function you are adding the reducer called panels. Redux will add this to the state object. So when you are referencing it you need to refrence the panels object that is nested inside your state object. You need to get the following in your useSelector useSelector((state) => state.panel.panelItems)
Second you are doing the following in your app component {panels || [].map((name) => <Panel {...{ name }} />)}. So what you are doing here is displaying the panels array or mapping through an empty array but you never actually map through the panels array. But what you need to do is map through the panels array. So you can just map the panels array {panels.map((name) => <Panel {...{ name }} />)}
So your app component should look like the following:
import React from "react";
import "./styles.css";
import { Panel } from "./Panel";
import PanelAdder from "./PanelAdder";
import { useSelector } from "react-redux";
export default function App() {
const panels = useSelector((state) => state.panel.panelItems);
return (
<>
<PanelAdder />
<div className="cards-container">
{panels.map((name) => <Panel {...{ name }} />)}
</div>
</>
);
}
Failed to compile. Attempted import error: 'withRouter' is not exported from 'react-router-dom'.
My code like, also I have installed react-router-dom an react-route and I have respin the app 10 time now
import React from 'react';
import {withRouter} from 'react-router-dom';
import './menu-item.scss';
const MenuItem = ({ title, imageUrl, size, history }) => (
<div className={`${size} menu-item`}>
<div
className='background-image'
style={{
backgroundImage: `url(${imageUrl})`,
}}
/>
<div className='content'>
<h1 className='title'>{title.toUpperCase()}</h1>
<span className='subtitle'>SHOP NOW</span>
</div>
</div>
);
export default withRouter(MenuItem);
If you accidentally installed react-router-dom v6 then the withRouter HOC no longer exists. Either revert back to v5 (run npm install -s react-router-dom#5), or roll your own custom withRouter HOC to inject the props you need or convert the components to function components and use the React hooks.
Create custom withRouter Higher Order Component
From the FAQ: What happened to withRouter I need it
import {
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
Convert to function component
There is now also no longer a history object to use, it was replaced by a navigate function accessed via the useNavigate hook. It's not clear where history was being used previously, but if you need to imperatively navigate the following is how you access the navigation. If staying with v6 then the following is how to access the navigate function.
import React from 'react';
import { useNavigate } from 'react-router-dom';
import './menu-item.scss';
const MenuItem = ({ title, imageUrl, size }) => {
const navigate = useNavigate();
// navigate("/targetPath")
return (
<div className={`${size} menu-item`}>
<div
className='background-image'
style={{
backgroundImage: `url(${imageUrl})`,
}}
/>
<div className='content'>
<h1 className='title'>{title.toUpperCase()}</h1>
<span className='subtitle'>SHOP NOW</span>
</div>
</div>
)
};
export default MenuItem;
You can use useLocation hook and destructure 'pathname' property from it to make the navigated link in the onClick() function dynamic.
Here's a sample of the code that worked for me:
import React from "react";
import { useLocation, useNavigate } from "react-router-dom";
import "./menu-item.styles.scss";
const MenuItem = ({ title, imageUrl, size, linkUrl, match }) => {
let navigate = useNavigate();
// destructured "pathname" property from useLocation()
let { pathname } = useLocation();
// consoloe.log(pathname)
return (
<div className={`${size} menu-item`} onClick={() => navigate(`${pathname}${linkUrl}`)}>
<div
className="background-image"
style={{
backgroundImage: `url(${imageUrl})`,
}}
/>
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
);
};
export default MenuItem;
thanks to Drew Reese -great shout-, and similar to Switch ( will throw the same error ) here is the downgrade command, just make sure you are in the right directory
npm install react-router-dom#5.0.0
try this it worked for me
import React from "react";
import { useNavigate } from "react-router-dom";
import "./menu-item.styles.scss";
const MenuItem = ({ title, imageUrl, size, history, linkUrl, match }) => {
const navigate = useNavigate();
return (
<div className={`${size} menu-item`} onClick={() => navigate(`hats`)}>
<div
className="background-image"
style={{
backgroundImage: `url(${imageUrl})`,
}}
></div>
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
);
};
export default MenuItem;
in menu-item.component.jsx you can use the useNavigate just like this one
import React from "react";
import { useNavigate } from "react-router-dom";
import "./menu-item.styles.scss";
const MenuItem = ({ title, imageUrl, size, history, linkUrl, match }) => {
let navigate = useNavigate();
return (
<div className={`${size} menu-item`} onClick={() => navigate(`${linkUrl}`)}>
<div
className="background-image"
style={{
backgroundImage: `url(${imageUrl})`,
}}
></div>
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
);
};
export default MenuItem;
Also, in App.js you should import Routes and Route from react-router-dom and make your code like this:
import React from "react";
import { Routes, Route } from "react-router-dom";
import "./App.css";
import HomePage from "./pages/homepage/homepage.component";
const HatsPage = () => (
<div>
<h1>Hats page</h1>
</div>
);
function App() {
return (
<div>
<Routes>
<Route exact path="/" element={<HomePage />} />
<Route path="/hats" element={<HatsPage />} />
</Routes>
</div>
);
}
export default App;
As for me, this works.
I am trying to render a different component in my App.js based on an OnClick placed in a button nested inside my Home component. Essentially I want the Home component to initially be rendered and then my "Restaurants" component to replace it when I click on the "Explore Restaurants" (which changes the state from true to false) button which is nested in my Home component. I am new to react so I'm sure there is an easy way to do this but I'm feeling a bit lost.
Here is the code
**App.js:**
import React, {useState} from "react";
import './App.css';
import Home from "./home"
import Footer from "./footer"
import Header from "./header"
import Resturants from "./resturants";
function App() {
var [count, setCount] = useState(false);
return (
<div>
<Header/>
{count ? <Home /> : <Resturants/> }
<Footer/>
</div>
);
}
export default App;
**Home.js**
import React, {useState} from "react";
import Button from "./button";
import {Stack} from "react-bootstrap";
import Promotions from './promotions'
//This component purely renders the home page when the user opens the app.
function Home (){
function changePage (){
setCount(count = true)
}
return (
<Stack>
<Promotions />
<Button className ="homeButtons" buttonText="Login" />
<Button className ="homeButtons" buttonText="Signup" />
<Button onClick ={changePage} className ="homeButtons" buttonText="Explore Resturants" />
</Stack>
)
}
export default Home
**Button.js**
import React from "react"
function Button (props){
return (<button onClick={props.onClick} style={props.style} className ={props.className} type = {props.type}> {props.buttonText} </button>
)
}
export default Button
You should pass the setCount method down to the Home component as a prop:
function App() {
var [count, setCount] = useState(false);
return (
<div>
<Header />
{count ? <Home setCount={setCount} /> : <Resturants />}
<Footer />
</div>
);
}
And then, retrieve it in the Home component:
function Home({ setCount }) {
function changePage() {
setCount((oldCount) => !oldCount);
}
I have a problem with sending state from Child to Parent.
After clicking on Menu in Main component I want to change state active and send this active to Sidebar component, because I want to hide/show Sidebar depends on active class from css. It is easy in Vanilla JS but in React I am little confused.
Main:
import Menu from "./Menu";
import "./Styles.css";
import teamsdb from "./teamsdb";
const Main = ({ name }) => {
return (
<div>
<Menu />
<h1>Main</h1>
<h4>{teamName}</h4>
</div>
);
};
Menu:
const Menu = () => {
const [active, setActive] = useState(false);
const changeMenu = () => {
setActive(!active);
};
return <button onClick={changeMenu}>Menu</button>;
};
export default Menu;
Sidebar:
const Sidebar = () => {
const [searchTerm, setSearchTerm] = React.useState("");
const handleChange = e => {
setSearchTerm(e.target.value);
console.log("Search: ", e.target.value);
};
return (
<div className={active ? 'sidebar active' : 'sidebar'}>
<div className="sidebar__header">
Header
<button>Colors</button>
</div>
<div className="sidebar__search">
<input
type="text"
placeholder="Search"
value={searchTerm}
onChange={handleChange}
/>
</div>
<Teams search={searchTerm} />
</div>
);
};
App.js
import React from "react";
import "./App.css";
import Sidebar from "./components/Sidebar";
import Main from "./components/Main";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
function App() {
return (
<div className="App">
<div className="app__body">
<Router>
<Sidebar />
<Switch>
<Route path="/team/:teamId">
<Main />
</Route>
<Route exact path="/">
<Main />
</Route>
</Switch>
</Router>
</div>
</div>
);
}
You should lift your state up the first common ancestor component, in your case: App. This way you can use it in all its descendants by passing the state and the mutation function (setActive) as prop:
App // Put active state here, pass down active and setActive as prop
^
|
+----+-----+
| |
+ +
Sidebar Main // Pass down active and setActive as prop
^
|
|
Menu // Use setActive to modify state in App
This is explained in React documentation: https://reactjs.org/docs/lifting-state-up.html
With the context api and hooks prop drilling isn’t required anymore. You can simply wrap the parent and child in a context provider and leverage react’s useContext hook for managing state across the 2 components. kent c dodds has a good article with examples here
Sorry if the question is stupid, I'm new to React.
Let's say I have such a component:
import React from 'react';
import Container from '../Miscellaneous/Container/Container'
import MaskedImgBlock from '../MaskedImgBlock/MaskedImgBlock'
import HeaderImg from 'assets/img/header/header-img.jpg'
import styles from './Header.module.scss'
const header = (props) => {
return(
<header className={styles.mainHeader}>
<Container>
<div className={styles.mainHeader_inner}>
... {/* some other code here */}
<MaskedImgBlock
src={HeaderImg}
alt="Team members photo"/>
</div>
</Container>
</header>
)
};
export default header;
And I have in it a reusable MaskedImgBlock component:
import React from 'react';
import styles from './MaskedImgBlock.module.scss'
const maskedImgBlock = ({ src, alt }) => {
return (
<div className={styles.imgBlock}>
<div className={styles.imgBlock_clipped}>
<img className={styles.imgBlock_img}
src={src}
alt={alt} />
</div>
</div>
)};
export default maskedImgBlock;
This MaskedImgBlock component I want to use inside multiple components in my app, and it must keep its structure and most of styles, but some styles of it's inner elements must be changed according to the component's position.
For example, when this component is inside Header component, one of its inner divs must have a green background-color, and if it's inside a footer component, other inner div must be of yellow background color.
How can I achieve this effect in the most nice way?
Similarly to what's done in the Material-UI framework for React, you could introduce a new prop for the MaskedImgBlock component, like in the following example:
const MaskedImgBlock = ({
src,
alt,
classes = {}
}) => {
const {
root = '',
imgWrapper = '',
img = '',
} = classes
return (
<div className={`${styles.imgBlock} ${root}`}
<div className={`${styles.imgBlock_clipped} ${imgWrapper}`>
<img className={`${styles.imgBlock_img} ${img}`}></igm>
</div>
</div>
)
}
Then, assuming you want to style root and img, you could do the following:
const Header = (props) => {
return (
<header>
<Container>
<div>
<MaskedImgBlock
src={HeaderImg}
alt="Team members photo"
classes={{
root: styles.mainHeader_maskedImgBlock_root,
img: styles.mainHeader_maskedImgBlock_img
}}
/>
</div>
</Container>
</header>
)
}
Or, if you just want the default styling, you don't pass any extra props to your component:
const Header = (props) => {
return (
<header>
<Container>
<div>
<MaskedImgBlock
src={HeaderImg}
alt="Team members photo"
/>
</div>
</Container>
</header>
)
}
This way, you only pass the classes that you want and everything else will default to existing styles.
How do you define styles for MaskedImgBlock classes in parent components?
You use css modules in MaskedImgBlock, so its class names will be generated according with your style/css/or*other-loader config.
If you need couple different representation - it better to add some prop to MaskedImgBlock component and path it from parent like
import React from 'react';
import Container from '../Miscellaneous/Container/Container'
import MaskedImgBlock from '../MaskedImgBlock/MaskedImgBlock'
import HeaderImg from 'assets/img/header/header-img.jpg'
import styles from './Header.module.scss'
const header = (props) => {
return(
<header className={styles.mainHeader}>
<Container>
<div className={styles.mainHeader_inner}>
... {/* some other code here */}
<MaskedImgBlock
theme={'green'}
src={HeaderImg}
alt="Team members photo"/>
</div>
</Container>
</header>
)
};
export default header;
If you want to customize your MaskedImgBlock component with many styles from different parent compoments - the best approach is adding className (or etc.) prop to your MaskedImgBlock component, where you can pass class name from parent component (like your Header component).
import React from 'react';
import Container from '../Miscellaneous/Container/Container'
import MaskedImgBlock from '../MaskedImgBlock/MaskedImgBlock'
import HeaderImg from 'assets/img/header/header-img.jpg'
import styles from './Header.module.scss'
const header = (props) => {
return(
<header className={styles.mainHeader}>
<Container>
<div className={styles.mainHeader_inner}>
... {/* some other code here */}
<MaskedImgBlock
className={styles.maskedImg}
src={HeaderImg}
alt="Team members photo"/>
</div>
</Container>
</header>
)
};
export default header;
And new MaskedImgBlock
import React from 'react';
import styles from './MaskedImgBlock.module.scss'
const maskedImgBlock = ({ src, alt, className = '' }) => {
return (
<div className={`${styles.imgBlock} ${className}`}>
<div className={styles.imgBlock_clipped}>
<img className={styles.imgBlock_img}
src={src}
alt={alt} />
</div>
</div>
)};
export default maskedImgBlock;