My state changes, but does not add class when useEffect, when I scroll - reactjs

I need to change the background of a JSX element when the page goes down by 320 px, all with useEffect and useState. So far I managed to change the state, but does not add background class of another color.
I am using NODE 8.9.3, NPM 5.5.1 and REACT JS 16.9.0
import React, { useEffect, useState } from 'react'
import { useScrollYPosition } from 'react-use-scroll-position'
import { Container } from '../../styles/Container'
import { ContainerCustom, HeaderComp } from './styles'
import Logo from './Logo'
import Menu from './Menu'
import Icons from './Icons'
const ContainerBox = () => {
return (
<ContainerCustom fluid>
<Container>
<HeaderComp>
<Logo />
<Menu />
<Icons />
</HeaderComp>
</Container>
</ContainerCustom>
)
}
const Header = () => {
const [back, setBack] = useState(0)
const handleBackState = () => {
const scrollY = window.scrollY
if (scrollY > 320) {
setBack(1)
console.log(`Estado: ${back}`)
} else {
setBack(0)
console.log(`Estado após remover: ${back}`)
}
}
useEffect(() => {
window.addEventListener('scroll', handleBackState)
return () => {
window.removeEventListener('scroll', handleBackState)
}
}, [handleBackState])
return <ContainerBox className={back === 1 ? 'removeGradients' : ''} />
}
On console has the output State: 0, and after 320, State after remove:
1

Not every component also has a representation in the DOM. You need to apply the className to a component that actually has a corresponding DOM element to have your styles take any effect:
// className will not affect the DOM as this component does not render a DOM element
const WrappingComponent = ({className}) => (
<WrappedComponent className={className} />
);
// this className will be applied to the div in the DOM
const WrappedComponent = ({className}) => (
<div className={className}>Content here</div>
);

Related

useRef() returning 'undefined' when used with custom hook on initial render when using filter()

I have an image slider component and a simple custom hook that gets the refElement and the width of the element using the useRef hook. -
The code sandbox is here Image Slider
When I use the slider component and just map the data in without filtering, everything works fine. If I filter and map the data then I get Uncaught TypeError: elementRef.current is undefined . (In the sandbox you have to comment out the second instance (unfiltered) of SliderTwo to recreate the error. Why does it work without the filter but not with (when rendered by itself)? More in depth explanation below.
useSizeElement()
import { useState, useRef, useEffect } from 'react';
const useSizeElement = () => {
const [width, setWidth] = useState(0);
const elementRef = useRef();
useEffect(() => {
setWidth(elementRef.current.clientWidth); // This will give us the width of the element
}, [elementRef.current]);
return { width, elementRef };
};
export default useSizeElement;
I call the hook (useSizeElement) inside of a context because I need the width to use in another hook in a different component thus:
context
import React, { createContext, useState, useEffect} from 'react';
import useSizeElement from '../components/flix-slider/useSizeElement';
export const SliderContext = createContext();
export const SliderProvider = ({children}) => {
const { width, elementRef } = useSizeElement();
const [currentSlide, setCurrentSlide] = useState();
const [isOpen, setIsOpen] = useState(false)
console.log('context - width', width, 'elementRef', elementRef)
const showDetailsHandler = movie => {
setCurrentSlide(movie);
setIsOpen(true)
};
const closeDetailsHandler = () => {
setCurrentSlide(null);
setIsOpen(false)
};
const value = {
onShowDetails: showDetailsHandler,
onHideDetails: closeDetailsHandler,
elementRef,
currentSlide,
width,
isOpen
};
return <SliderContext.Provider value={value}>{children}</SliderContext.Provider>
}
I get the width of the component from the elementRef that was passed from the context.-
Item Component
import React, { Fragment, useContext } from 'react';
import { SliderContext } from '../../store/SliderContext.context';
import ShowDetailsButton from './ShowDetailsButton';
import Mark from './Mark';
import { ItemContainer } from './item.styles';
const Item = ({ show }) => {
const { onShowDetails, currentSlide, isOpen, elementRef } =
useContext(SliderContext);
const isActive = currentSlide && currentSlide.id === show.id;
return (
<Fragment>
<ItemContainer
className={isOpen ? 'open' : null}
ref={elementRef}
isActive={isActive}
isOpen={isOpen}
>
<img
src={show.thumbnail.regular.medium}
alt={`Show title: ${show.title}`}
/>
<ShowDetailsButton onClick={() => onShowDetails(show)} />
</ItemContainer>
</Fragment>
);
};
export default Item;
The width is passed using context where another hook is called in the Slider Component:
Slide Component
import useSizeElement from './useSizeElement';
import { OuterContainer } from './SliderTwo.styles';
const SliderTwo = ({ children }) => {
const {currentSlide, onHideDetails, isOpen, width, elementRef } = useContext(SliderContext);
const { handlePrev, handleNext, slideProps, containerRef, hasNext, hasPrev } =
useSliding( width, React.Children.count(children));
return (
<Fragment>
<SliderWrapper>
<OuterContainer isOpen={isOpen}>
<div ref={containerRef} {...slideProps}>
{children}
</div>
</OuterContainer>
{hasPrev && <SlideButton showLeft={hasPrev} onClick={handlePrev} type="prev" />}
{hasNext && <SlideButton showRight={hasNext} onClick={handleNext} type="next" />}
</SliderWrapper>
{currentSlide && <Content show={currentSlide} onClose={onHideDetails} />}
</Fragment>
);
};
export default SliderTwo;
Now everything works fine if I just map the data with no filters into the slider as shown in the sandbox. But if I apply a filter to display only what I want I get -
Uncaught TypeError: elementRef.current is undefined
I do know that you can't create a ref on an element that does not yet exist and I've seen examples where you can use useEffect to get around it but I can't find the solution to get it to work.
Here is the App.js - To see the error I'm getting, comment out the second instance of . As long as I'm running one instance without filtering the data, it works, but it won't work by itself.
import { useState, useEffect, Fragment } from "react";
import SliderTwo from "./components/SliderTwo";
import Item from "./components/Item";
import shows from "./data.json";
import "./App.css";
function App() {
const [data, setData] = useState(null);
const datafunc = () => {
let filteredData = shows.filter((show) => {
if (show.isTrending === true) {
return show;
}
});
setData(filteredData);
};
useEffect(() => {
datafunc();
}, []);
console.log("Trending movies", data);
return (
<Fragment>
<div className="testDiv">
{shows && data && (
<SliderTwo>
{data && data.map((show) => <Item show={show} key={show.id} />)}
</SliderTwo>
)}
</div>
<div className="testDiv">
<SliderTwo>
{shows.map((show) => (
<Item show={show} key={show.id} />
))}
</SliderTwo>
</div>
</Fragment>
);
}
export default App;
Full code: Sandbox - https://codesandbox.io/s/twilight-sound-xqglgk
I think it may be an issue when the useSizeElement is first mounted as the useEffect will run once at the beginning of each render.
When it runs at the first instance and the ref is not yet defined so it was returning the error: Cannot read properties of undefined (reading 'clientWidth')
If you modify your code to this I believe it should work:
import { useState, useRef, useEffect } from "react";
const useSizeElement = () => {
const [width, setWidth] = useState(0);
const elementRef = useRef();
useEffect(() => {
if (elementRef.current) setWidth(elementRef.current.clientWidth); //
This will give us the width of the element
}, [elementRef]);
return { width, elementRef };
};
export default useSizeElement;
This way you are checking if the elementRef is defined first before setting the width
UPDATE:
<Fragment>
<div className="testDiv">
<SliderTwo>
{shows
.filter((show) => {
if (show.isTrending === true) {
return show;
}
return false;
})
.map((show) => (
<Item show={show} key={show.id} />
))}
</SliderTwo>
</div>
{/* <div className="testDiv">
<SliderTwo>
{shows.map((show) => (
<Item show={show} key={show.id} />
))}
</SliderTwo>
</div> */}
</Fragment>

show - hide component with hook function works only one time

I am trying to show and hide a functional component, it's works only works on load. after hide it's not shows again. i understand that, the way i use the functional component in wrong way.
any one suggest me the correct way please?
here is my code : (index.tsx)
import React, { Component, useState } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
const App = () => {
const [isBoolean, setBoolean] = useState(false);
const showComponent = () => {
setBoolean(true);
};
return (
<div>
<Hello isBoolean={isBoolean} />
<p>Start editing to see some magic happen :)</p>
<button onClick={showComponent}>Show hello component</button>
</div>
);
};
render(<App />, document.getElementById('root'));
Hello component:
import React, { useEffect, useState } from 'react';
export default ({ isBoolean }: { isBoolean: boolean }) => {
const [isShow, setIsShow] = useState(false);
useEffect(() => {
setIsShow(isBoolean);
}, [isBoolean, setIsShow]);
const shufler = () => {
setIsShow(false);
};
if (!isShow) {
return null;
}
return (
<div>
<p>hi {JSON.stringify(isShow)}</p>
<button onClick={shufler}>Hide Component</button>
</div>
);
};
Live Demo
To explain why your code isn't working:
useEffect(() => {
setIsShow(isBoolean);
}, [isBoolean, setIsShow]);
initially when you set isBoolean to true in parent, this useEffect in child runs too
Then you set isShow to false from the child component
Then again you set isBoolean to true in the parent component, but for the useEffect above, the isBoolean is true now, and it was true also in previous render, so it doesn't run anymore.
So if possible, no need to duplicate isBoolean state also in child, just pass it as props and use it directly, as in the other answer.
No need to maintain a derived state from prop in child component(Hello), you can pass callback and state as props from parent component(index) to child.
Cause of the Problem:
After hiding the component isShow was set to false , isBoolean is still true. So the next time when we click the show button isBoolean hasn't changed, it's still true which will not trigger the useEffect in the Hello.tsx , isShow was never set to true which causes the child to return null.
index.tsx
import React, { Component, useState } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
const App = () => {
const [isBoolean, setBoolean] = useState(false);
const showComponent = () => {
setBoolean(true);
};
const hideComponent = () => {
setBoolean(false);
}
return (
<div>
<Hello isBoolean={isBoolean} hideComponent={hideComponent} />
<p>Start editing to see some magic happen :)</p>
<button onClick={showComponent}>Show hello component</button>
</div>
);
};
render(<App />, document.getElementById('root'));
Hello.tsx
import React, { useEffect, useState } from 'react';
export default ({ isBoolean, hideComponent }: { isBoolean: boolean }) => {
if (!isBoolean) {
return null;
}
return (
<div>
<p>hi {JSON.stringify(isBoolean)}</p>
<button onClick={hideComponent}>Hide Component</button>
</div>
);
};

logging unmount for JSX element instead of components

how we can observe if a JSX element mounted or not. for example I have a simple component with useEffect on. it inside of my App.js I can mount and unmount my component and the useEffect inside of that component will log if it is mounted or unmounted.
but I wonder if there is way to that with JSX elements. for example , can we implement that for an h2 tag inside of an App.js without creating component ?
App.js
import React, { useState } from "react";
import "./App.css";
import Mycomponent from "./Mycomponent";
const App = () => {
const [mount, setMount] = useState(true);
return (
<div>
<b>Mounting and Unmounting</b>
<button
onClick={() => {
setMount(!mount);
}}
>
{mount ? "click to unmount" : "click to mount"}
</button>
{mount && <Mycomponent />}
</div>
);
};
export default App;
Mycomponent.js :
import React, { useEffect } from "react";
const Mycomponent = () => {
useEffect(() => {
console.log("mounted");
return () => {
console.log("unmounted");
};
}, []);
return (
<div>
<h1>component mounted</h1>
</div>
);
};
export default Mycomponent;
I think you can use callback refs for that:
export default function App() {
const [counter, setCounter] = React.useState(0);
const measuredRef = (node) => {
if (node == null) {
console.log('I was removed');
} else {
console.log('I was mounted');
}
};
return (
<div
onClick={() => {
setCounter(counter + 1);
}}
>
{counter % 2 == 0 && <h1 ref={measuredRef}>Hello, world</h1>}
<p>Start editing to see some magic happen :)</p>
</div>
);
}
There is a somewhat related example in the docs about that:
In this example, the callback ref will be called only when the
component mounts and unmounts, since the rendered <h1> component stays
present throughout any rerenders.

React - Display Div when click

I would like to show a component when i'm clicking to another one. I set a value "showPanelInfo" to false at the beginning and when I click to the "CharacterCard" component, I tried to change value of setShowPanelInfo to true but it's not working.
import React from "react";
import { useQuery, gql } from "#apollo/client";
import styled from "styled-components";
import { QueryResult } from "../components";
import CharacterCard from "../containers/characters/character-card";
import CharacterPanelCard from "../components/characters/character-panel-card";
import CreatePlanetModal from "../components/characters/create-character-modal";
const Characters = () => {
const { loading, error, data } = useQuery(GET_CHARACTERS);
// SHOW HIDE PLANET PANEL
const [showPanelInfo, setShowPanelInfo] = React.useState(false);
const displayPanelInfo = () => setShowPanelInfo(true);
const hidePanelInfo = () => setShowPanelInfo(false);
// CREATE ID IN STATE - DEFAULT 1
const [characterId, setCharacterId] = React.useState(1);
return (
<PageContainer>
<ResultContainer>
<QueryResult error={error} loading={loading} data={data}>
{/* Grid of characters */}
{data?.characters?.map((character) => (
<CharacterCard
key={`${character.id}`}
character={character}
onClick={() => {
displayPanelInfo();
setCharacterId(character.id);
}}
/>
))}
</QueryResult>
{/* Panel Container */}
{showPanelInfo ? (
<CharacterPanelCard characterId={characterId} />
) : null}
</PageContainer>
);
};
export default Characters;
Any Ideas?
Thank you!

Call method from child (class based component) in parent (functional component)

I have been developing a project for three months. I need to call a method from the child component (class-based component) in the parent component (functional component). I used ref for this but it didn't work.
Here is the parent component:
import React, { useState, useEffect, useRef, createRef } from "react";
import CityPicker from "../../components/public/cityPicker";
import Chip from "../../components/forms/chip";
import Sidebar from "./sidebar";
import { Router } from "../../routes";
import NextRouter, { withRouter } from "next/router";
const Search = props => {
const [filterItem, setFilterItem] = useState();
const deleteFilterItem = createRef();
const deleteChipHandler = event => {
filterItem
? deleteFilterItem.current.onDeleteSearchableFilterItem(
event,
"stateOrProvince",
"selectedStateOrProvince"
)
: "";
};
return (
<>
<div className={filterItem ? "filters-display" : ""}>
{filterItem
? filterItem.map((item, index) => {
return (
<Chip
id={item.id}
key={index + "selected city"}
label={item.value}
onDelete={e => deleteChipHandler(e)}
/>
);
})
: ""}
</div>
<Sidebar ref={deleteFilterItem} />
</>
);
};
export default withRouter(Search);
The onDeleteSearchableFilterItem method belong to the child component.
Write the function like this in your child component. data is the data you get from your state of that component or a state from reducer if you are using redux(like this.props.data):
const onDelete = () => {
this.props.onDelete(data);
}
then call it in the parent:
import { Router } from "../../routes";
import NextRouter, { withRouter } from "next/router";
const Search = props => {
const [filterItem, setFilterItem] = useState();
const deleteFilterItem = createRef();
const deleteChipHandler = (event, data) => {
filterItem
? deleteFilterItem.current.onDeleteSearchableFilterItem(
event,
data
)
: null;
};
return (
<>
<div className={filterItem ? "filters-display" : ""}>
{filterItem
? filterItem.map((item, index) => {
return (
<Chip
id={item.id}
key={index + "selected city"}
label={item.value}
onDelete={deleteChipHandler}
/>
);
})
: ""}
</div>
<Sidebar ref={deleteFilterItem} />
</>
);
};
export default withRouter(Search);

Resources