I am doing a project where I am trying to achieve the hover effects where the element enlarge and background color change. I made it happen toggling class but when I try to make the component I made more reusable by setting the props into the module I realize toggling class maybe not be a good option.So I tried to add inline style when hover.
I find the solution but the way to achieve it doesn't make a lot of sence to me, instead of adding lots of element.style.arribute="somthing"(if there is more than one attribute to change) I would like to just add one object with list of attributes inside.
So instead of
wrapper.current.style.backgroundColor = "red"; wrapper.current.style.color = "blue"; wrapper.current.style.fontSize = "2rem";
I believe when there is a lot of attributes to be manipulate it makes more sense if it can be like something like this:
wrapper.current.style = { backgroundColor: "red",color:blue,fontSize:2rem, };
But this just won't work for me.
snippet of this component here
import React from "react";
import { useRef } from "react";
import { useEffect } from "react";
import { useState } from "react";
// img:"../sources/computer-and-chair.jpg"
// imgName: "computer and chair"
// subtitle:"First website finnished with vanilla javascript"
function Project(props) {
const [isHover, setIsHover] = useState(false);
const wrapper = useRef(null);
useEffect(() => {
if (isHover) {
wrapper.current.classList.add("effect");
// wrapper.current.style.backgroundColor = "red"; work this way but not the way below...
wrapper.current.style = { backgroundColor: "red" };
} else {
wrapper.current.classList.remove("effect");
// wrapper.current.style.backgroundColor = "transparent"; work this way but not the way below...
wrapper.current.style = { backgroundColor: "red" };
}
}, [isHover]);
return (
<section ref={wrapper} className="single-project-container">
<div className="single-project-image-container">
<img
src={props.img}
alt={props.imgName}
className="project-image"
onMouseOver={() => {
setIsHover(true);
}}
onMouseOut={() => {
setIsHover(false);
}}
/>
</div>
<h1>{props.subtitle}</h1>
</section>
);
}
export default Project;
I have tried those:
Looked into inline-styling, no direcly answer to my question.
Looked into element.style from MDN, no direcly answer to my question.
Related
Note, I'm using MUI 4.12.3. In file A I have this (simplified) besides a functional component. Inside the functional component's return statement I also apply it to a JSX element (not shown). This works well.
const useStyles = makeStyles((t) => ({
content: {
minHeight: '100vh',
},
}));
In file B, I would like to override the CSS class content based on isDesktop. Is this possible/desirable? Something like this, but it does not work:
const useStyles = makeStyles({
content: {
minHeight: (props) => (props.isDesktop ? '100vh' : '112vh'),
},
});
//And inside my functional component:
const isDesktop = useMediaQuery(Theme.breakpoints.up('sm'));
const classes = useStyles({ isDesktop });
Note that the intent is to not render the JSX component in file B, only to override the CSS class content in file B is desirable. (classes is unused in my sample.)
This can be done with few hooks.Let say my functional componet name is "Mycomponent".my material component is "materialcomponent".we need to import useWindowSize hook.That helps us to get the window size.so that we can check our screen size is desktop or mobile.in the makestyle we have to create two classes for desktop and mobile minHeight.with the simple if else checking we can pass this two classes conditionally to materialcomponent className prop.Below is the code.
1.create two classes with the makestyles
const useStyles = makeStyles((t) => ({
contentDesktop: {
minHeight: '100vh',
},
contentMobile:{
minHeight: '110vh',
}
}));
2.import the useWindowSize hook
import useWindowSize from "hooks/useWindowSize";
3.functinal componet code.
const Mycomponent = () => {
const classes = useStyles();
let myclass="";
const width = useWindowSize();
const isDesktop = width >=1024;
const mobile= width <= 600;
if(isDesktop){
myclass=classes. contentDesktop;
}
if(mobile){
myclass=classes.contentMobile;
}
return (
<materialcomponent className={`${myclass}`}
);
}
You can export this function outside of your functional component
export const getStyles = (isDesktop) => {
return {
content: {
minHeight: isDesktop ? "100vh" : "112vh",
},
};
};
And then wherever you want your styling applied
const isDesktop = useMediaQuery(Theme.breakpoints.up('sm'));
...
<SomeMuiComponent sx={getStyles(isDekstop)} />
I have a simple app with a few pages, now I would like to change the background color based on page URL
using react js,
What is expected?:
When a pathname is /movies I want to change the background to red
Here is what I have so far
import React from 'react'
function Testing() {
const[moviesUrlBackgroundColor, setMoviesUrlBackgroundColor] = useState('green');
const getMoviesUrl = window.location.pathname;
if(getMoviesUrl == '/movies'){
setMoviesUrlBackgroundColor('red');
}else{
setMoviesUrlBackgroundColor('green');
}
return (
<div>
<Container style={{backgroundColor:moviesUrlBackgroundColor}}>
Testing
</Container>
</div>
)
}
export default Testing
const Container = styled.div`
background-color:green
`;
Unfortunately, I am getting the following URL
app.js:38323 Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop.
What do I need to do this working?
You should have an extra check to make sure you have set background color or not.Your current code is causing rerender infinte times
import React from 'react'
function Testing() {
const[moviesUrlBackgroundColor, setMoviesUrlBackgroundColor] = useState('green');
const [bgFlag, setbgFlag] = useState(false);
const getMoviesUrl = window.location.pathname;
if(!bgFlag){
setMoviesUrlBackgroundColor(getMoviesUrl == '/movies' ? 'red' : 'green')
setbgFlag(true)
}
return (
<div>
<Container style={{backgroundColor:moviesUrlBackgroundColor}}>
Testing
</Container>
</div>
)
}
export default Testing
const Container = styled.div`
background-color:green
`;
Use an useEffect block so you can perform side-effects effectively.
useEffect(() => {
if(getMoviesUrl === '/movies'){
console.log("running")
setMoviesUrlBackgroundColor('red');
}else{
setMoviesUrlBackgroundColor('green');
}
},[getMoviesUrl]);
The problem is that you call setMoviesUrlBackgroundColor without wrapping in an effect which results in getting called recursively.
In order to fix this, you just simple set state as needed in this case is your pathname has been changed:
React.useEffect(() => {
if (getMoviesUrl == '/movies'){
setMoviesUrlBackgroundColor('red');
} else{
setMoviesUrlBackgroundColor('green');
}
}, [getMoviesUrl])
Ok, I think I got it, you're creating an infinite loop on your first if statement:
if (getMoviesUrl == '/movies') {
// every time you change state, that causes your component
// to re-render, and when it re-renders again you're checking
// changing your state AGAIN, so it's an infinite loop
setMoviesUrlBackgroundColor('red');
}
I'll would recommend using react-route for this and getting the url from params, and then update the background color on componentDidMount or useEffect hook when component mounts for the first time, to prevent infinite loops.
Window.location.pathname was resetting state each render, so it needs to be placed within the useEffect hook to prevent re-renders.
Also, this would be a good use-case to pass props to your styled component.
Also included this in the code below.
Here's a link to a codesandbox I made with the solution.
https://codesandbox.io/s/musing-mirzakhani-njmsh?file=/src/random.js:0-620
import React, { useState, useEffect } from "react";
import styled from "styled-components";
const Container = styled.div`
background: ${(props) => props.backgroundColor || "green"};
`;
const Testing = () => {
const [moviesUrlBackgroundColor, setMoviesUrlBackgroundColor] = useState(
"green"
);
useEffect(() => {
const getMoviesUrl = window.location.pathname;
if (getMoviesUrl === "/movies") {
setMoviesUrlBackgroundColor("yellow");
}
}, [moviesUrlBackgroundColor]);
return (
<div>
<Container backgroundColor={moviesUrlBackgroundColor}>Test</Container>
</div>
);
};
export default Testing;
Cheers!
I have a section with a fixed height. I don't know when the component mounts (first renders) whether the content coming in will fit or not. If it does NOT fit, then I need to render a 'Read More' button.
It looks like this:
I wrote this originally as a Class component using the lifecycle methods DidMount/DidUpdate:
Class Component
import React, { createRef } from "react"
import styled from "#emotion/styled"
import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"
const StyledHeightContainer = styled.div`
max-height: 150px;
overflow: hidden;
`
class ParagraphList extends React.Component {
state = {
overflowActive: false,
}
wrapper = createRef() // so we can get a ref to the height container
isOverflowing(el) {
if (el) return el.offsetHeight < el.scrollHeight
}
componentDidMount() {
this.setState({ overflowActive: this.isOverflowing(this.wrapper.current) })
}
componentDidUpdate() {
if (this.wrapper.current && !this.state.overflowActive) {
this.setState({
overflowActive: this.isOverflowing(this.wrapper.current),
})
}
}
handleClick() {
this.setState({ overflowActive: false })
}
render() {
const { moreButtonText, titleText, paragraphs, theme } = this.props
return (
<>
<Section overflowActive={this.state.overflowActive}>
{this.state.overflowActive || !this.wrapper.current ? (
<StyledHeightContainer ref={this.wrapper}>
<Paragraphs paragraphs={paragraphs} />
</StyledHeightContainer>
) : (
<Paragraphs paragraphs={paragraphs} />
)}
</Section>
{overflowActive ?
<ButtonReadMore
onClicked={handleClick.bind(this)}
moreButtonText={moreButtonText}
theme={theme}
/>
: null}
</>
)
}
}
export default ParagraphList
My best way to explain the flow:
When the component mounts, the flag is false and we have no reference to the div so the StyledHeightContainer will try to render and thus provide a ref to it
In componentDidMount -> try to set the overflow flag (which will be false because at this point we do not yet have rendering completed so the ref will be null). But by setting the flag anyway, we queue an additional render pass
1st INITIAL rendering completes -> we have a ref to the div now
The 2nd (queued) render occurs, firing the componentDidUpdate -> we can calculate the overflow and set the flag to true when the content overflows
When the user clicks the button -> set the flag to false, which will trigger a re-render and hence the StyledHeightContainer will be removed from the DOM.
Functional Component With Hooks
Sandbox of the code
When I re-wrote this as a functional component using Hooks, I ended up with this:
import React, { createRef, useEffect, useState } from "react"
import styled from "#emotion/styled"
import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"
const StyledHeightContainer = styled.div`
max-height: 150px;
overflow: hidden;
`
const ParagraphList = ({ moreButtonText, titleText, paragraphs, theme }) => {
const [overflowActive, setOverflowActive] = useState(false)
const [userClicked, setUserClicked] = useState(false)
const wrapper = createRef(false) // so we can get a ref to the height container
const isOverflowing = el => {
if (el) return el.offsetHeight < el.scrollHeight
}
useEffect(() => {
if (!userClicked && !overflowActive && wrapper.current) {
setOverflowActive(isOverflowing(wrapper.current))
}
}, [userClicked]) // note: we only care about state change if user clicks 'Read More' button
const handleClick = () => {
setOverflowActive(false)
setUserClicked(true)
}
return (
<>
<Section theme={theme} overflowActive={overflowActive}>
{!userClicked && (overflowActive || !wrapper.current) ? (
<StyledHeightContainer ref={wrapper}>
<Paragraphs paragraphs={paragraphs} />
</StyledHeightContainer>
) : (
<Paragraphs paragraphs={paragraphs} />
)}
</Section>
{overflowActive ?
<ButtonReadMore
onClicked={handleClick.bind(null)}
moreButtonText={moreButtonText}
theme={theme}
/>
: null}
</>
)
}
export default ParagraphList
I was surprised that I needed to add another state (userClicked), which is how I force the 2nd render to occur (ie. the equivalent to the componentDidUpdate in the class solution).
Is this correct or can someone see a more concise way to write the 2nd solution?
NOTE
One of the reasons I ask is because in the console I get this warning:
48:6 warning React Hook useEffect has missing dependencies:
'overflowActive' and 'wrapper'. Either include them or remove the
dependency array react-hooks/exhaustive-deps
and I don't THINK I want to add them to the dependency array, as I don't want to trigger rendering when they change...?
I really enjoyed while solving the query.
Here is the implementation: https://codesandbox.io/s/react-using-hooks-in-section-component-5gibi?file=/src/ParagraphList.js
First of all, I was thinking of
useEffect(() => {
setOverflowActive(isOverflowing(wrapper.current));
}, [wrapper]);
But if we do this, it will again call the useEffect as when we'll click on the Read more button. Because it was comparing the reference of the wrapper and not it's value.
So, to avoid the reference comparison we have to use the useCallback hook.
const isOverflowingNode = node => {
return node.offsetHeight < node.scrollHeight;
};
const wrapper = useCallback(node => {
if (node !== null) {
setOverflowActive(isOverflowingNode(node));
}
}, []);
I came across the beautiful discussion: https://github.com/facebook/react/issues/14387
For more information:
https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
Thanks for the question :)
You could add an extra useEffect(() => (...),[]) that acts like componentDidMount(). And another useEffect(() => (...)) that acts like componentDidUpdate(). Then you should be able to get rid of userClicked.
This is a good link on how the lifestyle methods work with hooks. https://dev.to/trentyang/replace-lifecycle-with-hooks-in-react-3d4n
useEffect(() => {
setOverflowActive(isOverflowing(wrapper.current));
}, []);
useEffect(() => {
if (!overflowActive && wrapper.current) {
setOverflowActive(isOverflowing(wrapper.current))
}
});
The second one might need to be useLayoutEffect if you are wanting the update to happen after the layout.
I am adding Cards dynamically in my React functional component. Cards are stored in State. I map them and give id to each of them. OnClick on those Cards I get their id successfully. Now I want to getElementById to change Card color:
function Clicked(pressedGifId) {
if (pressedGifId === 'correctGif') CorrectMatch();
else WrongMatch();
}
function CorrectMatch(pressedGifId) {
// / THERE I GET Element: null
console.log('Element:', document.getElementById(pressedGifId));
}
function WrongMatch() {
console.log('wrong a match!');
}
export default function GameObject(props) {
const addedToGameGif = [];
const [pressedGifId, gifPressed] = useState(null);
const [photoCards, setPhotoCards] = useState([]);
useEffect(() => {
Clicked(pressedGifId);
}, [pressedGifId]);
// add randomly picked photos to addedToGameGif array
// ...
addedToGameGif.map(gifId =>
photoCards.push(
<Card id={gifId} onClick={() => gifPressed(gifId)}>
text
</Card>,
),
);
return <div>{photoCards}</div>;
}
I tried learning refs but they are only for class components. So how do I reach my element by id in React?
You can use ref in functional component as well. There is a hook called useRef.
Note: Never interact directly with DOM until or unless there is no api available in react to solve the problem for that particular use case.
In react it's not recommended to interact directly with dom. Always use react apis to interact with dom. React is designed to hide the DOM because they want to abstract the DOM away. By using the DOM directly you break the abstraction and make your code brittle to changes introduced in the library.
React is maintaining a virtual DOM if we make any changes in actual DOM directly then react will not be aware of this change and this can lead to some unexpected behavior .
import React, {useState, useRef} from 'react';
export default function GameObject(props) {
const addedToGameGif = [];
const [pressedGifId, gifPressed] = useState(null);
const [photoCards, setPhotoCards] = useState([]);
const elemRef = useRef(null);
useEffect(() => {
Clicked(pressedGifId);
}, [pressedGifId]);
// add randomly picked photos to addedToGameGif array
// ...
addedToGameGif.map(gifId =>
photoCards.push(
<Card ref={elemRef} id={gifId} onClick={() => gifPressed(gifId)}>
text
</Card>
)
);
return <div>{photoCards}</div>;
}
Example from official docs.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
I'm new to reactjs. I'm good in javascript and jQuery, but dumb in ReactJS. I have this jQuery code and I need to make it work with reactjs. This function is supposed to auto-scroll the list vertically on a loop. But I don't have any idea how to do this in react.
function autoScroll(obj) {
$(obj).find("container").animate({
marginTop: "-28px"
}, 500, function () {
$(this).css({
marginTop: "0px"
}).find("li:first").appendTo(this);
});
}
$(function () {
setInterval('autoScroll(".container")', 3000);
})
Given my component
import React from 'react'
function List(props) {
const lists = props.list
const list_div = lists.map((lists, index) => {
return (
<li key={index}>{lists}</li>
)
})
return(
<ul className="container">{list_div}</ul>
)
}
export default List
Will appreciate any help.
Step 1: Add ref to your components
//Create ref for parent component
const containerRef = React.createRef()
//Set the created Ref to the element
<ul className="container" ref={containerRef}>{list_div}</ul>
Step 2: Create refs to child components
//Create ref to child components
lists.map((list,index) => listsRef[index] = React.createRef())
Step 3: In your event (either click, load, etc), add this code to automatically scroll in one of the child component
this.containerRef.current.scrollTo({
top: listsRef[index].offsetTop,
left: 0,
behavior:'smooth'
})