My project is separated into parts, and some parts got so big I even separated them using composition on sub components.
The problem is, the compose components are rendering, but aren't interactive, for instance, here's a menu component that contains a header with a button:
import React from 'react';
export default function Menu() {
// This is responsible of creating a custom button with bunch of things in it, that button should rotate 180deg when hovered.
function IconButton() {
return (
<div class='rotating_button'>
// Bunch of code...
<button></button>
// Bunch of other code...
</div>
);
}
return (
<div class='menu'>
<IconButton />
// The rest of the Menu Code.
</div>
);
}
Everything till now seems fine, but when I wanna make a compose component interactive through something like CSS or other JavaScript file, it doesn't animate, or it doesn't render as expected.
Here's my CSS my code that in this case should make the custom button rotatable when hovered, and it's so simple it doesn't work:
.rotating_button {
/* All of this works. */
background_color: red;
width: 50px;
height: 50px;
transform: rotate(0deg);
/* But this does not. */
transition: 0.5s;
}
.rotating_button:is(:hover, :focus) {
transform: rotate(180deg);
}
To summarize, it rotates, but with no transition applied on it.
But when it's not a compose component, and it's directly put in the return section of the Menu Component, it works just write.
Suchlike this code:
import React from 'react';
export default function Menu() {
return (
<div class='menu'>
<div class='rotating_button'>
// Bunch of code...
<button></button>
// Bunch of other code...
</div>
// The rest of the Menu Code.
</div>
);
}
Related
I am following these docs in order to style a material ui component (Paper) within a component (Menu) I am using.
I am using CSS modules to style my components (with Webpack as a bundler) :
// menu.js
import React from 'react';
import { StyledEngineProvider } from '#mui/material/styles';
...
import styles from './styles.module.css';
import Menu from '#mui/material/Menu';
import MenuItem from '#mui/material/MenuItem';
const MyMenu = (props) => {
...
return (
<StyledEngineProvider injectFirst>
<div id="my-menu">
<Button id="button-react-component" onClick={handleClick}>
My Menu
</Button>
<Menu
id="menu-react-component"
...
className={styles.menu}
>
<MenuItem ...>
<span> Example 1 <span>
</MenuItem>
</Menu>
</div>
);
}
// styles.module.css
.menu {
color: white;
}
.menu .MuiPaper-root {
background-color: red
}
// Also tried :
.menu .root {
background-color: red
}
My goal is to have the MuiPaper component have a given background-color. MuiPaper is a component that comes from the Menu component, but I am not using MuiPaper directly as I am only declaring the parent (<Menu>).
Ideally I want to use .css files for styling. I use webpack to bundle my css files into modules.
Here's what I see in my browser :
Notice how the background-color "red" is not applied on that last screenshot.
Thanks :)
CSS modules can't override a style from another CSS module (or elsewhere). There's a few ways to get around this:
Add another class specifically for the .menu paper, e.g. .menuPaper, and add it via PaperProps on the Menu component:
.menuPaper {
background-color: blue;
}
<Menu
id="menu-react-component"
...
className={styles.menu}
PaperProps={{ className: styles.menuPaper }}
>
Add the :global selector to your css selector:
.menu :global .MuiPaper-root {
background-color: red;
}
CSS modules work by "modulifying" CSS classnames by adding a unique ID to the end of them. The :global selector can be used to disable this and preserve the classname instead.
The difference between these two methods is that if you had multiple Menu components in your MyMenu component, using the :global method would give all the Menu instances inside of MyMenu the same background. With the PaperProps method only specific Menus with PaperProps={{ className: styles.menuPaper }} would get the styles applied.
css-loaderdocs: https://github.com/webpack-contrib/css-loader#scope
MUI Menu docs: https://mui.com/api/menu/#props (also see Popover component)
I am developing an e-commerce solution using Vue.js (with Vuex and Vue Router). I have noticed that, on seemingly random occasions, my header and footer components will not load either after a page refresh, or when loading the page.
Here is my App.vue
<template>
<div id="app">
<VGLHeader></VGLHeader>
<div class="site-view">
<transition name="slither">
<router-view :key="$route.params" />
</transition>
</div>
<VGLFooter></VGLFooter>
</div>
</template>
<script>
// import { mapActions } from 'vuex';
import VGLHeader from '#/components/VGLHeader.vue'
import VGLFooter from '#/components/VGLFooter.vue'
export default {
name: 'App',
components: {
VGLHeader,
VGLFooter
}
}
</script>
<style>
#app {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.site-view {
flex:1;
}
.slither-enter-active, .slither-leave-active {
transition: transform 300ms;
}
.slither-enter, .slither-leave-to {
transform: translateX(-100%);
}
.slither-enter-to, .slither-leave {
transform: translateX(0);
}
</style>
VGLHeader and VGLFooter have several nested components themselves, while they also fetch data from my API (such as contact data that I want the client to be updating in the database via an admin panel, hence why I am fetching data in them). And yes, I know I'm not "supposed" to put $route.params in the "key" part, but it's just a temporary makeshift patch to solve another issue that I'm keeping until I find a better solution later on.
I have a sidebar component that works similar to a modal. When a button is clicked, the sidebar translates into the viewport with nav links. These nav links are actually router-links that are wired up to a vue-router.
What I'm trying to accomplish
When I click on a router-link that is inside my sidebar component, I want the sidebar to transition off the viewport and I want the clicked router-link's component to render without the page reloading.
What's currently happening
When I click on the router-link, the sidebar is removed instantly from the DOM. It does not translate off the screen as intended. Also, the page is reloaded.
What else have I tried
I also tried moving the <transition> wrapper inside TheSidebar.vue component along with the associated CSS classes, and I passed sidebarIsVisible as a prop from App.vue to TheSidebar.vue.
My code
A Codesandbox demo can be found here
App.vue
<template>
<router-view></router-view>
<button #click="toggleSidebar" class="toggleBtn">Toggle Sidebar</button>
<transition name="sidebar">
<the-sidebar
v-if="sidebarIsVisible"
#link-clicked="toggleSidebar"
></the-sidebar>
</transition>
</template>
<script>
import TheSidebar from "./components/TheSidebar.vue";
export default {
components: {
TheSidebar,
},
data() {
return {
sidebarIsVisible: false,
};
},
methods: {
toggleSidebar() {
this.sidebarIsVisible = !this.sidebarIsVisible;
},
closeSidebar() {
this.sidebarIsVisible = false;
},
},
};
</script>
<style>
/* basic styling */
.toggleBtn {
position: fixed;
top: 5px;
left: 5px;
}
.sidebar-enter-active {
animation: slide-sidebar 0.3s ease;
}
.sidebar-leave-active {
animation: slide-sidebar 0.3s ease reverse;
}
#keyframes slide-sidebar {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
</style>
TheSidebar.vue
<template>
<div class="sidebar">
<nav>
<ul>
<li>
<router-link #click="$emit('link-clicked')" to="/link1">
Link 1
</router-link>
</li>
<li>
<router-link #click="$emit('link-clicked')" to="/link2">
Link 2
</router-link>
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
emits: ["link-clicked"],
};
</script>
<style scoped>
/* basic styling */
</style>
main.js
import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import App from "./App.vue";
import LinkOne from "./components/LinkOne.vue";
import LinkTwo from "./components/LinkTwo.vue";
const app = createApp(App);
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/link1", component: LinkOne },
{ path: "/link2", component: LinkTwo }
]
});
app.use(router);
app.mount("#app");
There are a couple of things I'm unsure about here but I'll try and explain what I think is happening.
Firstly, the click event on the router-link is what's causing the page to reload, but I can't find anything in the docs mentioning this as expected behaviour (it might be worth opening an issue on the GitHub repo).
The fix for this is to use event-delegation by moving the event-handler onto the ul and creating a method to determine if a link has been clicked (example below).
Secondly, and this is where things get weird, in VSCode, using kebab-case in the child components' emitted event seems to prevent anything from being emitted, so changing them to camelCase fixes this. But, trying this in CodeSandbox simply doesn't work, and ESLint complains that the emitted event should be kebab-case. So, in CodeSandbox, the opposite is true: the emitted event names should be kebab-case and the listener should be camelCase! Absolutely no idea why as this goes against what the docs say on casing:
...we recommend using kebab-cased event listeners when you are using in-DOM templates.
Again, I can't find anything in the docs explicitly saying you need to use camelCase when emitting an event, it just says kebab-case is preferred when listening for an event.
So, all in all, for your code to work in VSCode and in a way which follows what is recommended by the docs, you need to change it to this:
<template>
<div class="sidebar">
<nav>
<!-- Move event here -->
<ul #click="handleClick($event)">
<li>
<router-link to="/link1">
Link 1
</router-link>
</li>
<li>
<router-link to="/link2">
Link 2
</router-link>
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
emits: ['linkClicked'], // Use camelCase for emitting
methods: {
handleClick(e) {
// Check the target is a link being clicked
if (e.target.localName !== 'a') return
this.$emit('linkClicked')
}
}
}
</script>
Keep App.vue exactly as you have it already and it should work.
For your code to work in CodeSandbox, swap the casing:
...
emits: ['link-clicked'], // Use kebab-case for emitting
...
this.$emit('link-clicked')
...
App.vue:
#linkClicked="toggleSidebar"
Working example.
If anyone could shed some light on this, it'd be great as I'm completely stumped on what's happening here.
I'm currently creating my custom implementation of a modal. All works perfectly fine so far but I can't seem to animate it and I can't get my head around it.
This is my Modal component
import React from 'react'
import Slider from './Slider'
import {IoIosCloseCircleOutline} from "react-icons/io"
import styled from "styled-components";
export default function Modal(props) {
const Modal = styled.div `
transform: translateX(${({animateSlideInRight}) => (animateSlideInRight ? "0" : "100vw")});
transition: transform 1s;
width: 1000px;
height: 650px;
z-index: 100;
position: fixed;
background: white;
transition: all 1.1s ease-out;
box-shadow:
-2rem 2rem 2rem rgba(black, 0.2);
visibility: visible;
display: flex;
border-bottom-right-radius: 100px;
`
const closeModal = () => {
props.setShow(false)
}
const data = props.data
if (!props.show) {
return null
}
return (
<div className="modalWrapper">
<Modal className="modal" id="modal" animateSlideInRight = {props.show}>
<div className="modalHeaderWrapper">
<IoIosCloseCircleOutline className="modalCloseCross" onClick={closeModal}/>
<img src={data[0].logo} alt="logo" />
<h2>{data[0].title}</h2>
</div>
<div className="modalRightFlex">
<Slider
images={[data[0].image1Carrousel, data[0].image2Carrousel, data[0].image3Carrousel]}
/>
<div className="modalRightDescription">
<h1>Description</h1>
<p>{data[0].description}</p>
<h1>Technologies</h1>
<div className="modalTechnologiesWrapper">
{data[0].technologiesUsed.map((image) => {
return <img src={image}/>
})}
</div>
</div>
</div>
</Modal>
</div>
)
}
As you see my modal is a styledComponent that defines whether to translate in X or not depending on the show state. In my scenario I had to lift up state since I'm opening this modal from clicking on a card which in itself is a different component, so their ancestor is taking care of the states.
My current CSS for modal is as seen in the styled div.
Things I have tried
1-tried having a regular div and handle the animation through CSS with keyframes --> It works for sliding in but it doesn't when I close (in that instance I had my show state defining a class name for the modal with a different animation for each of them)
2-tried to set a animate state and define the className based on whether that state is true or false. It works the first time when I close (despite having to introduce a timeout of the animation duration between setting animate to false and show to false) but then it goes bonkers and starts flickering everywhere.
Anyway someone can see the issue? Many thanks
edit
Sanbox link: https://codesandbox.io/s/trusting-shape-vxujw
You should define Modal in the outer scope of the component rendering it, the animation does not complete and you resetting it by redefining it on the next render.
Also resetting an animation should be done with none instead of giving an actual length.
Moreover, there might be more CSS bugs related that can hide your modal animation like z-index and position, if your question is focused on an animation problem you should remove all the noise around it.
See working example:
const Animation = styled.div`
transform: ${({ animate }) => (animate ? "none" : "translateX(500px)")};
transition: transform 1s;
`;
function Modal(props) {
return <Animation animate={props.show}>hello</Animation>;
}
function Component() {
const [show, toggle] = useReducer((p) => !p, false);
return (
<>
<Modal show={show} />
<button onClick={toggle}>show</button>
</>
);
}
Also, you shouldn't return null when you don't want to animate, you will lose the close animation.
// remove this code
if (!props.show) {
return null;
}
I just recently started getting into using React.js and to make it easier for myself I'm trying to recreate projects I've made in the past, but instead of using jQuery like I did in the past I'm completely avoiding jQuery and using only React.
I tend to do animations where a div would fade in as another fades out like this:
$("#start").click(function() {
$("#h1").fadeOut(750);
$("#h2").delay(500).fadeIn(750);
$("#h1").css("z-index", 0);
$("#h2").css("z-index", 1);
});
I was wondering how can I reproduce this fade in and out effect without jQuery
(I know CSS animations could change the opacity, but the opacity isn't the only thing I'm trying to change, this affects the display property as well).
A simple way is to use CSS transitions. Basically you just add a class to an element, and define a transition in the CSS and it does the rest for you
There is a tutorial here
https://www.w3schools.com/css/css3_transitions.asp
Which does a good job of explaining how it all works with examples and a playground for you to try your own
The CSS Transition group add-on might help, it let's you define transitions like this:
JS:
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{items}
</ReactCSSTransitionGroup>
CSS:
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
One option would be to use a framework, like react-bootstrap, which includes a lot of the UI components you need for any given project. It includes a Fade component. Documentation can be found here: https://react-bootstrap.github.io/components.html#utilities
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {};
}
render() {
return (
<div>
<Button onClick={()=> this.setState({ open: !this.state.open })}>
click
</Button>
<Fade in={this.state.open}>
<div>
<Well>
THIS CONTENT WILL FADE
</Well>
</div>
</Fade>
</div>
);
}
}
ReactDOM.render(<Example/>, mountNode);