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.
Related
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>
);
};
I am generating my state in the parent component. latestFeed generates a series of posts from my backend:
import React, { useState, useEffect } from "react";
import { getLatestFeed } from "../services/axios";
import Childfrom "./Child";
const Parent= () => {
const [latestFeed, setLatestFeed] = useState("loading");
const [showComment, setShowComment] = useState(false);
useEffect(async () => {
const newLatestFeed = await getLatestFeed(page);
setLatestFeed(newLatestFeed);
}, []);
return (
<div className="dashboardWrapper">
<Child posts={latestFeed} showComment={showComment} handleComment={handleComment} />
</div>
);
};
export default Parent;
then latestFeed gets generated into a series of components that all need to hold their own state.
import React, { useState } from "react";
const RenderText = (post, showComment, handleComment) => {
return (
<div key={post._id} className="postWrapper">
<p>{post.title}</p>
<p>{post.body}</p>
<Comments id={post._id} showComment={showComment} handleComment={() => handleComment(post)} />
</div>
);
};
const Child= ({ posts, showComment, handleComment }) => {
return (
<div>
{posts.map((post) => {
return RenderText(post, showComment, handleComment);
})}
</div>
);
};
export default Child;
In its current form, the state of RenderText's is all set at the same time. I need each child of Child to hold its own state.
Thank you!
Instead of using RenderText as a function, call it as a component:
{posts.map((post) => (
<RenderText key={post.id} post={post} showComment={showComment} />
))}
This is because when used as a component, it will have it's own lifecycle and state. If used as a function call, React does not instantiate it the same way - no lifecycle, no state, no hooks, etc.
I'm trying to get hold of ref on children component but it doesn't seem to be working. The same approach works fine with the React class component but not with hooks.
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref) // this logs null always}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
export function Test(props) {
return <div className="test" {...props}>
{props.children}
</div>
}
Your component is okay. It is probably because the are no children rendered to that component. I reproduced it with using TestContainer in App and put <h2>Ref</h2> as a child of TestContainer:
(removed the comment of course, since it has been hiding the } )
App.js
import React from "react";
import "./styles.css";
import TestContainer from "./TestContainer";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<TestContainer>
<h2>Ref</h2>
</TestContainer>
</div>
);
}
TestContainer.js
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref)}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
CodeSndbox:
HERE
I've got a few React functional Components that I would like to share a state. In this example two toggle buttons that would conditionally show/hide a searchbar and a navbar.
--Solution, based on the accepted answer, on the bottom--
I'm completely new to useContext() and I keep running into the following error in the console:
Uncaught TypeError: setSearchbarToggle is not a function This goes for both buttons.
Bellow I have a filtered example code. It is just for the example I use the states in one file. In real life I would re-use the states in multiple functional components.
This is my header.js
import React, { useState, useContext } from "react"
import "./header.sass"
import { Context } from "./HeaderContext"
export const Header = () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export default Header
And this is my HeaderContext.jsx
import React, { createContext, useState } from "react";
import PropTypes from "prop-types";
export const Context = createContext({});
export const Provider = props => {
const {
navbarToggle: initialNavBarToggle,
searchbarToggle: initialSarchbarToggle,
children
} = props;
const [navbarToggle, setNavbarToggle] = useState(initialNavBarToggle);
const [searchbarToggle, setSearchbarToggle] = useState(initialSarchbarToggle);
const headerContext = {
navbarToggle, setNavbarToggle,
searchbarToggle, setSearchbarToggle
};
return <Context.Provider value={headerContext}>{children}</Context.Provider>;
};
export const { Consumer } = Context;
Provider.propTypes = {
navbarToggle: PropTypes.bool,
searchbarToggle: PropTypes.bool
};
Provider.defaultProps = {
navbarToggle: false,
searchbarToggle: false
};
I hope you can shed some light on this for me
--edit--
This is my code based on the accepted answer.
import React, { useContext } from "react"
import { Provider,Context } from "./HeaderContext"
export const HeaderWithContext= () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export const Header = () => {
return (
<Provider>
<HeaderWithContext/>
</Provider>
)
};
One of the parent components, e.g. App, must wrap the header (or one of its ancestor components) with Context.Provider:
import { Provider } from "./HeaderContext"
...
<Provider>
<Header />
</Provider>
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>
);