Same component, multiple times used - applying different css styles - javascript

I want box to get coloured but only the one i click on and the rest should have default one. Then, when i click on the next one the previous box should go back to the default color. Currently, when i click on one of them, all of the boxes get unwanted background color. I know that i basically pass "color" prop to each of them so all get coloured because of the same prop and state. But how to do it properly with only one state and without changing prop name?
const {useState} = React;
const App = () => {
const [color, setColor] = useState("");
const firstBox = () => {
setColor("firstBox");
};
const secondBox = () => {
setColor("secondBox");
};
const thirdBox = () => {
setColor("thirdBox");
};
return (
<div className="container">
<Box setColor={firstBox} color={color} />
<Box setColor={secondBox} color={color} />
<Box setColor={thirdBox} color={color} />
</div>
);
}
const Box = ({ setColor, color }) => {
return (
<div
onClick={setColor}
className={`box ${
color === "firstBox" || color === "secondBox" || color === "thirdBox"
? "active"
: ""
}
`}
></div>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
display: flex;
gap: 2rem;
}
.box {
width: 250px;
height: 250px;
background: lightblue;
}
.active {
background: red;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Multiple ways to do this, but this is what I'd do:
const {useState} = React;
const App = () => {
const [color, setColor] = useState("");
return (
<div className="container">
<Box setColor={() => setColor('First Box')} selected={color === 'First Box'} />
<Box setColor={() => setColor('Second Box')} selected={color === 'Second Box'} />
<Box setColor={() => setColor('Third Box')} selected={color === 'Third Box'} />
</div>
);
}
const Box = ({ setColor, selected }) => {
return (
<div
onClick={setColor}
className={selected ? 'active box' : 'box'}
/>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);

Since you asked you didn't want to use any extra prop or state.
All you have to do is change your Box component code to this,
const Box = ({ setColor, color }) => {
return (
<div
onClick={setColor}
className={`box ${setColor.name === color ? "active" : ""}
`}
></div>
);
};

Related

React.js/Next.js Loader is showing in the wrong place on the page

I want to show a Loader on top of everything while fetching data from my API. I asked this question and implemented the answer, and it is working, but it shows it not on top level, but inside the page itself. When looking at the html tree I can see that it is on the top level.
This is what happens when btnAll is clicked:
After scrolling down:
This is the HTML tree:
Why does it put it inside the page?
loading.provider.js
import { createContext, useContext, useState } from "react";
const LoadingContext = createContext({
loading: false,
setLoading: null
});
export function LoadingProvider({ children }) {
const [loading, setLoading] = useState(false);
const value = { loading, setLoading };
console.log(`LoadingProvider: ${loading}`);
return (
<LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>
);
};
export function useLoading() {
const context = useContext(LoadingContext);
console.log(`Using LoadingContextProvider`);
if (!context) {
throw new Error('useLoading must be used within LoadingProvider');
}
return context;
};
app.js
export default function App({ Component, pageProps }) {
return (
<>
<CssBaseline />
<AppStateProvider>
<LoadingProvider>
<Loader />
<Layout>
<Component {...pageProps} />
</Layout>
</LoadingProvider>
</AppStateProvider>
</>
);
};
Loader.js
import { useLoading } from "#/Providers/loading.provider";
import { useEffect } from "react";
import LoadingScreen from "./LoadingScreen";
const Loader = () => {
const { loading } = useLoading();
useEffect(() => {
console.log(`[app.js/#useEffect]: useLoading() value changed to: ${loading}`);
}, [loading]);
return loading && <LoadingScreen loading={true} bgColor='#fff' spinnerColor={'#00A1FF'} textColor='#676767'></LoadingScreen>;
};
export default Loader;
LoadingScreen.js
import styles from './LoadingScreen.module.css';
export default function LoadingScreen() {
return (
<div className={styles.loading}>
<div className={styles.dot}></div>
<div className={styles.dot}></div>
<div className={styles.dot}></div>
<div className={styles.dot}></div>
<div className={styles.dot}></div>
</div>
);
}
This is the css part of style.loading inside LoadingScreen.module.css:
.loading {
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99;
background-color: #FFFFFF;
}
I am calling it inside [identifier].js page like that:
import { useLoading } from "#/Providers/loading.provider";
export default function MainPage({ response }) {
const { loading, setLoading } = useLoading();
const btnClickedAll = async (data) => {
setLoading(true);
};
return (
<Fragment>
<Button sx={{ ml: 2 }} key={'btnAll'} onClick={btnClickedAll} variant="contained">All</Button>,
</Fragment>
);
}
Updated:
Try set the style .loading position: fixed;
Old:
Try moving down the <Loader /> in app.js
...
<LoadingProvider>
<Layout>
<Component {...pageProps} />
</Layout>
<Loader />
</LoadingProvider>
...
Or it's better to put Loader in loading.provider.js and remove <Loader /> from app.js
...
<LoadingContext.Provider value={value}>
{children}
<Loader />
</LoadingContext.Provider>
...

It is necessary that when the component is removed, it does not just disappear, but leaves behind component with button

thank you in advance, however, before answering the question, read carefully what I ask for help with all due respect. What i need:
I need that when the delete button is clicked, the component is not only deleted, but also leaves behind another button, by clicking on which, the remote component is rendered back
Functionality that already works: rendering a component on click, as well as deleting by a button
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
interface IParams {
id: number;
}
interface IBlock {
deleteBlock(blockToDelete: number) : void
id: number
}
function App() {
const [newBlock, setNewBlock] = useState([] as IParams[])
const createOnClick = () => {
const newId = {
id: newBlock.length + 1
}
setNewBlock([...newBlock, newId])
}
const deleteBlock = (blockToDelete: number) => {
setNewBlock(
newBlock.filter((x) => {
return x.id !== blockToDelete
})
)
}
const FunctionalBlock: React.FC<IBlock> = ({id, deleteBlock}) => {
return (
<div style={{display: 'flex', maxWidth: '300px', justifyContent: 'space-between'}}>
<div>i was created {id} times</div>
<button onClick={() => {
deleteBlock(id)
}}>now delete me</button>
</div>
)
}
return (
<div className="App">
<button onClick={createOnClick}>
New block
</button>
{
newBlock.map((x) => (
<FunctionalBlock id={x.id} key={x.id} deleteBlock={deleteBlock}/>
))
}
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Why not just set a state on the block?
const {useState} = React
const FunctionalBlock = ({ id, idx, isDeleted, toggleBlockState }) => {
return (
<div
style={{
display: "flex",
maxWidth: "300px",
justifyContent: "space-between"
}}
>
{
!isDeleted
? <React.Fragment>
<div>i was created {idx} times</div>
<button
onClick={toggleBlockState}
>
now delete me
</button>
</React.Fragment>
: <button onClick={toggleBlockState}>REVIVE BLOCK</button>
}
</div>
)
;
};
const getNewBlock = (idx) => ({
id: Date.now(),
idx,
isDeleted: false,
})
const toggleIsDeletedById = (id, block) => {
if (id !== block.id) return block
return {
...block,
isDeleted: !block.isDeleted
}
}
const App = () => {
const [newBlock, setNewBlock] = useState([])
const createOnClick = () => {
const block = getNewBlock(newBlock.length + 1)
setNewBlock([...newBlock, block])
}
const toggleBlockStateById = (id) => {
setNewBlock(newBlock.map((block) => toggleIsDeletedById(id, block)))
}
return (
<div>
<div>NEW BLOCK</div>
<div><button onClick={createOnClick}>ADD NEW BLOCK +</button></div>
<div>
{
newBlock.map(block => <FunctionalBlock {...block} toggleBlockState={() => toggleBlockStateById(block.id)}/>)
}
</div>
</div>
);
};
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>

framer.motion animation is instant instead of animating

I have several boxes that I want to animate through,
Here's a simple app example (Also a codesandbox here)
Each "box" should fade in and fade out, however, in this example, the animation happens isntantly.
const Box = styled.div`
width: 100px;
height: 100px;
background: green;
`;
const Test = ({ isActive }) => {
return (
<motion.div
animate={isActive ? { opacity: 1 } : { opacity: 0 }}
transition={{ duration: 3 }}
>
<Box>hello world</Box>
</motion.div>
);
};
export default function App() {
const [currentIndex, setCurrentIndex] = useState(0);
const boxes = [
{
component: ({ isActive }) => <Test isActive={isActive} />
},
{
component: ({ isActive }) => <Test isActive={isActive} />
}
];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div onClick={() => setCurrentIndex(currentIndex === 0 ? 1 : 0)}>
{boxes.map((box, index) => {
const isActive = index === currentIndex;
return <box.component isActive={isActive} />;
})}
</div>
</div>
);
I have never used framer.motion before, but looking at their documentation, I think you can use variants, to achieve what you need. https://www.framer.com/api/motion/examples/
I've slightly refactored your code, to get it working:
import "./styles.css";
import { motion } from "framer-motion";
import styled from "styled-components";
import { useEffect, useState } from "react";
const Box = styled.div`
width: 100px;
height: 100px;
background: green;
`;
const variants = {
open: { opacity: 1 },
closed: { opacity: 0 }
};
const Test = ({ index, currentIndex }) => {
return (
<motion.div
animate={index === currentIndex ? "open" : "closed"}
variants={variants}
transition={{ duration: 3 }}
>
<Box>hello world</Box>
</motion.div>
);
};
export default function App() {
const [currentIndex, setCurrentIndex] = useState(0);
const boxes = ["a", "b"];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>{currentIndex}</h2>
<div onClick={() => setCurrentIndex(currentIndex === 0 ? 1 : 0)}>
{boxes.map((box, i) => {
return <Test index={i} currentIndex={currentIndex} />;
})}
</div>
</div>
);
}
the currentIndex is passed as props to the child Test components, they check themselves whether their index matches the currentIndex and update their animations accordingly.
Edited codesandbox here: https://codesandbox.io/s/suspicious-austin-tymvx
In framer motion, you have useCycle properties. Here is an example.
Code in example:
import * as React from "react";
import { render } from "react-dom";
import { Frame, useCycle } from "framer";
import "./styles.css";
export function MyComponent() {
const [animate, cycle] = useCycle(
{ scale: 1.5, rotate: 0, opacity: 1 },
{ scale: 1.0, rotate: 90, opacity: 0 }
);
return (
<Frame
animate={animate}
onTap={() => cycle()}
size={150}
radius={30}
background={"#fff"}
/>
);
}
const rootElement = document.getElementById("root");
render(<MyComponent />, rootElement);
and some simple css:
body {
margin: 0;
padding: 0;
}
#root {
font-family: sans-serif;
text-align: center;
width: 100vw;
height: 100vh;
display: flex;
place-content: center;
place-items: center;
background: rgba(0, 85, 255, 1);
margin: 0;
padding: 0;
perspective: 1000px;
}
I don't recommend you to use this type of construction: animate={index === currentIndex ? "open" : "closed"} , because you might have some lagging/breaking animation.
Try always to search examples/elements of MotionAPI lib. You will have less code lines and mostly "clean" code with no useless variables.

Is it possible to implement an inheritance like mechanism for React components in order to reduce nearly duplicate code?

I find that I am having to reimplement a lot of the same functionality. For example, with the next 3 components I am implementing the same code for style, className and id. I know that I can have {...props} as the argument instead hear, and then pass {...props} to the container inside the return function, but I am not able to do this if I want these components to each have their own classNames and styles that are always assigned to each instance of these classes. I looked into higher-order-components a bit, but couldn't wrap my head around how I would use them in this case
const styles = {
container:{
position: 'absolute'
},
}
const Modal = ({className, style, id, hidden, children}) => {
return (
<div
className={`modal ${className}`}
style={...{styles.container}, ...{style}}
id={id}
>
{!hidden && <ExtraContent />}
{children}
</div>
)
}
const styles = {
container:{
margin: 10
}
}
const VoteButton = ({className, style, id, pressed}) => {
let img
if (pressed){
img = './img_pressed.jpg'
}else{
img = './img_not_pressed.jpg'
}
return (
<div
className={`voteButton ${className}`}
style={...{styles.container}, ...{style}}
id={id}
>
<img src={img}>
</div>
)
}
const styles = {
container:{
display: 'flex'
}
}
const Navbar = ({className, style, id, links, children, ...props}) => {
return (
<nav
{...props}
className={`navbar ${className}`}
style={...{styles.container}, ...{style}}
id={id}
>
{links.map(lk => <a href={lk.href}>{lk.text}</a>
{children}
</nav>
)
}
To be clear, I am looking for a way to avoid having to define and modify className, style and id for each component. It would be good if I could do this once. I understand this might be especially hard for the third component considering it's a nav instead of a div.
One approach would be to have a function that returns your component function:
function createComponentClass(Ele, name, styles, children) {
return (props) => {
const { className, style, id } = props;
return (
<Ele className={`${name} ${className}`} style={...{styles.container}, ...{style}} id={id}>
{children(props)}
</Ele>
);
};
}
const Modal = createComponentClass('div', 'modal', { container: { ... } }, (props) => {
return (
<>
{!props.hidden && <ExtraContent />}
{props.children}
</>
);
});
const VoteButton = createComponentClass('div', 'voteButton', { container: { ... } }, (props) => {
let img = pressed ? './img_pressed.jpg' : './img_not_pressed.jpg';
return <img src={img} />;
});
const Navbar = createComponentClass('nav', 'navbar', { container: { ... } }, (props) => {
return (
<>
{links.map(lk => <a href={lk.href}>{lk.text}</a>
{children}
</>
);
});

React onClick add class to clicked element but remove from the others

so what i try to achieve here is very similar to what is done here Transition flex-grow of items in a flexbox
But what i wonder how this could be done with React say i have this code
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
classNameToUse: ''
};
this.onElementClicked = this.onElementClicked.bind(this);
}
onElementClicked() {
this.setState({ classNameToUse : 'big-size'})
}
render() {
return (
<div>
<div className={this.state.classNameToUse} onClick={this.onElementClicked} >
something
</div>
<div className={this.state.classNameToUse onClick={this.onElementClicked} >
something else
</div>
</div>
);
}
}
This would of course add the className to them both but what i want to achieve is that one of them grows big with animation and the other collapse. And it sohuldnt matter if i have 2 or 10 elements
You can set active index on click:
// State
this.state = {
activeIndex: null
};
// event
onElementClicked(e) {
this.setState({ activeIndex: e.target.index })
}
// class to use
className={this.index === this.state.activeIndex ? 'big-size' : ''}
const { useState, useEffect } = React;
const App = () => {
const [divs,] = useState(['blue', 'green', 'black']);
const [selected, setSelected] = useState(null);
const onClick = (id) => {
setSelected(id);
}
return <div className="container">
{divs.map(pr => <div key={pr} style={{background: pr}} className={`box ${pr === selected ? 'selected' : ''}`} onClick={() => onClick(pr)}></div>)}
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.container {
display: flex;
height: 200px;
}
.box {
flex: 1;
cursor: pointer;
transition: all .3s ease-in;
}
.selected {
flex: 2;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script>
<script src="https://unpkg.com/material-ui-lab-umd#4.0.0-alpha.32/material-ui-lab.development.js"></script>
<div id="root"></div>

Categories