show - hide component with hook function works only one time - reactjs

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>
);
};

Related

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 useState() & useContext() - TypeError: setState is not a function - What am I doing wrong?

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>

allow component to detect route change in Gatsby

In a Gatsby project I have a header component which is persistent on every page. The header has a modal to display the navigation. I need to set the isOpen state to false whenever the route changes so that the nav modal closes. Since the route can change not just by clicking links in the modal but also by using the back button on the browser I don't want use an event on the links to close the modal.
In Gatsby I can use the onRouteUpdate in gatsby-browser.js to detect route changes and this works well. But I need to pass the event to my component and this is where I am having difficulty. I have simplified the code below to show the setup.
gatsby-browser.js:
import React from "react"
import Layout from "./src/components/layout"
export const wrapPageElement = ({ element, props }) => {
return <Layout {...props}>{element}</Layout>
}
export const onRouteUpdate = () => {
console.log("onRouteUpdate") // this works
}
layout.js:
import React from "react"
import Header from "./header"
import Footer from "./footer"
const Layout = ({ children }) => (
<>
<Header />
<main>
{children}
</main>
<Footer />
</>
)
export default Layout
header.js:
import React, { useState } from "react"
const Header = () => {
const [isOpen, setIsOpen] = useState(null)
const toggleState = ({ props }) => {
let status
if (props) status = props.status
else status = !isOpen
setIsOpen(status)
}
return (
<header>
<div>This is the header</div>
<button onClick={toggleState}>Toggle Open/Close</button>
<button onClick={toggleState({ status: false })}>This will always close</button>
/* logic here uses isOpen state to determine display */
</header>
)
}
export default Header
My preferred approach to solving this is to use the undocumented globalHistory from #reach/router, which Gatsby uses.
import { globalHistory } from '#reach/router'
useEffect(() => {
return globalHistory.listen(({ action }) => {
if (action === 'PUSH') setIsOpen(false)
})
}, [setIsOpen])
Now whenever you switch routes, the above effect will fire.
Source.
I have come up with a solution to my own question so I thought I would share. Any comments/improvements are always welcome.
First, we do not need to use "onRouteUpdate" in gatsby-broser.js so let's remove that:
/* gatsby-browser.js */
import React from "react"
import Layout from "./src/components/layout"
export const wrapPageElement = ({ element, props }) => {
return <Layout {...props}>{element}</Layout>
}
Then, in layout.js make sure to pass the location to the header:
/* layout.js */
import React from "react"
import Header from "./header"
import Footer from "./footer"
const Layout = ({ children, location }) => (
<>
<Header location={location} />
<main>
{children}
</main>
<Footer />
</>
)
export default Layout
Finally, in header.js the location is stored on a reference to the header element by utilizing the useRef hook. The useEffect hook will get fired on route changes so we can use that to compare:
/* header.js */
import React, { useState, useEffect, useRef } from "react"
const Header = () => {
const [isOpen, setIsOpen] = useState(null)
const myRef = useRef({
location: null,
})
useEffect(() => {
// set the location on initial load
if (!myRef.current.location) myRef.current.location = location
// then make sure dialog is closed on route change
else if (myRef.current.location !== location) {
if (isOpen) toggleState({ status: false })
myRef.current.location = location
}
})
const toggleState = ({ props }) => {
let status
if (props) status = props.status
else status = !isOpen
setIsOpen(status)
}
return (
<header ref={myRef}>
<div>This is the header</div>
<button onClick={toggleState}>Toggle Open/Close</button>
<button onClick={toggleState({ status: false })}>This will always close</button>
</header>
)
}
export default Header
Hopefully this helps anyone looking for similar functionality.

React: Cannot update a component from inside the function body of a different component

i'm trying to only render the component <IntercomClient /> after a user clicks "Accept" on a cookie consent banner. Clicking accept changes the GlobalLayout's intercomIsActive state to true and thereby renders the IntercomClient. This is working but the warning concerns me.
How can I workaround the child/parent state change? I've been looking around but don't really understand.
import React, { useState } from 'react'
import { CookieBanner } from '#palmabit/react-cookie-law'
import IntercomClient from '../components/intercomClient'
const GlobalLayout = ({ location, children }) => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
)}
IntercomClient
import React from 'react';
import Intercom from 'react-intercom'
const IntercomClient = ({ active }) => {
return active ? <div><Intercom appID="XXXXXX" /></div> : null
}
export default IntercomClient;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
</Layout>
);
};
export default Example;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
{
intercomIsActive &&
<IntercomClient active={intercomIsActive}/>
}
...
</Layout>
);
};
export default Example;

Pass input data from component A to component B using React context

My request is very simple:
Could you please provide me with an example where an input data is passed from component A to component B using context API.
Requirements: there should be an input value entered in component A. We send the input value over to component B using context.
A and B are sibling components.
You can do this way.
Make a function in Context.js state which set the state of your input field.
//context.js state
state = {
inputFieldName: null,
setInputField: () => {
this.setState() //set value for inputFieldName here
}
}
Call that setInputField function on onChange in component A using Context and you can get inputFieldName state from Context in component B.
The required properties of the context:
1. A string property which stores the user's input
2. A method which updates the user's input.
And in your case the component A produces the input and calls the method to update it to context. The component B consumes the changes of the input from the context. it ends up with such a prototype.
import React, { useContext, useState } from "react";
import ReactDOM from "react-dom";
const MyContext = React.createContext(null);
function A() {
const { onChange } = useContext(MyContext);
const [input, setInput] = useState(null);
return (
<input
type="text"
value={input}
onChange={e => {
setInput(e.target.value);
onChange(e.target.value);
}}
/>
);
}
function B() {
const { input } = useContext(MyContext);
return <div>{input}</div>;
}
function App() {
const [input, setInput] = useState(null);
return (
<MyContext.Provider value={{ input, onChange: setInput }}>
<div>
<A />
<B />
</div>
</MyContext.Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is my try, hope it will help you
componentA.jsx
import React from "react";
const ComponentA = props => {
return (
<div>
<h1>{`ComponentA: ${props.data}`}</h1>;
<button onClick={() => props.onValueChange("value changed by ComponentA")}>
click
</button>
</div>
);
};
export default ComponentA;
componentB.jsx
import React from "react";
const ComponentB = props => {
return <h1>{`ComponentB: ${props.data}`}</h1>;
};
export default ComponentB;
App.js
import React, { Component } from "react";
import ComponentB from "./componentA";
import ComponentA from "./componentB";
class App extends Component {
state = {
data: "common value from parent"
};
handleChange = input => {
this.setState({ data: input });
};
render() {
return (
<div>
<ComponentB data={this.state.data}></ComponentB>
<ComponentA
data={this.state.data}
onValueChange={this.handleChange}
></ComponentA>
</div>
)}
}

Resources