Toggle visibility of two components from buttons inside of them in React - javascript

I have this structure
component 1
import React, { useState } from 'react'
export default function Component1() {
return (
<div>
<button onClick={handleChange}></button>
</div>
)
}
component 2
import React, { useState } from 'react'
export default function Component2() {
return (
<div>
<button onClick={handleChange}></button>
</div>
)
}
and the parent
import React from 'react'
export default function Parent() {
return (
<div>
<Component1 />
<Component2 />
</div>
)
}
The question is, how can I toggle visibility between the two, without having a button in the parent. Just the buttons inside each component. - The Component1 should be visible by default and when you press the button in Component1 it will hide it and show Component2 and vice-versa.
I've tried using useState hook on the Component1 button, but I'm not sure how to export the state and add it to the parent component.
const [showMini, setShowMini] = useState(false);
const handleChange = () => {
setShowMini(true);
}
Is this possible? or it's possible just with a button in the parent that control the two?
Thanks

Try this:
import React from 'react'
export default function Parent() {
const[show,setShow]=useState(false);
const handleChange=()=>{
setShow(!show);
}
return (
<div>
{show ? <Component2 handleChange={handleChange}/> : <Component1 handleChange={handleChange}/>}
</div>
)
}
and inside Component1 have this:
import React, { useState } from 'react'
export default function Component1({handleChange}) {
return (
<div>
<button onClick={handleChange}></button>
</div>
)
}
Similarly do it for Component2

You can do with state value and pass handleChange function ad props in the child component and in click on the button in child component call handleChange method under parent component and show hide based on state value.
import React from 'react'
const [showChild, setshowChild] = useState(false);
const handleChange = () => {
setshowChild(!showChild);
}
export default function Parent() {
return (
<div>
{showChild ? <Component2 handleChange = {handleChange}/> : <Component1 handleChange= {handleChange} />}
</div>
)
}

You can manage the state in the parent and pass down a handler to the children
import React, { useState } from 'react'
const [currentView, setCurrentView] = useState('component1')
const changeCurrentView = (view) => setCurrentView(view)
const renderViews = () => {
switch(currentView) {
case 'component1':
return <Component1 changeCurrentView={changeCurrentView} />
case 'component2':
return <Component2 changeCurrentView={changeCurrentView} />
default:
return <Component1 changeCurrentView={changeCurrentView} />
}
}
export default function Parent() {
return (
<div>
{renderViews()}
</div>
)
}
Other components
import React from 'react'
export default function Component1({ changeCurrentView }) {
return (
<div>
<button onClick={() => changeCurrentView('component1')}></button>
</div>
)
}
export default function Component2({ changeCurrentView }) {
return (
<div>
<button onClick={() => changeCurrentView('component2')}></button>
</div>
)
}

Your parent component should keep track of the state:
import React, {useState} from 'react'
export default function Parent() {
const [showChild, setShowChild] = useState(1);
const showNextChild = () => {
setShowChild( showChild === 1 ? 2 : 1 ); // set to 2 if already 1, else to 1
}
return (
<div>
{ showChild === 1 && <Component1 handleChange={showNextChild} /> }
{ showChild === 2 && <Component2 handleChange={showNextChild} /> }
</div>
)
}
A few notes:
Your components are identical, so the duplication is unnecessary, but I assume the example is just contrived.
This assumes toggling 2 components back and forth. If you have more than 2 components you are "looping" through, you can instead increment the previous showChild state and then reset it to 0 if higher than the # of components you have.
The syntax you see, showChild === 1 && <Component1 ... uses the behavior of the && operator which actually returns the 2nd item it is evaluating if both are true. In other words, const isTrue = foo && bar; sets isTrue to bar, not true as you might expect. (You know, however, that bar is "truthy" in this case, so isTrue still works in future if statements and such.) The component is always truthy, so the effect is that the component is returned if the first part is true, otherwise it is not. It's a good trick for conditionally showing components.

Try this. You can send information from child to parent with functions passed as a prop.
Parent Component:
const Parent = () => {
const [show, setShow] = useState(true);
const toggleVisibility = () => {
setShow(!show);
};
return (
<div>
{show ? (
<Child1 toggle={toggleVisibility}></Child1>
) : (
<Child2 toggle={toggleVisibility}></Child2>
)}
</div>
);
};
Child 1
const Child1 = (props) => {
const { toggle } = props;
return (
<div style={{ width: '100px', height: '100px' }}>
<button onClick={toggle}>Child 1's button</button>
</div>
);
};
Child 2
const Child2 = (props) => {
const { toggle } = props;
return (
<div style={{ width: '100px', height: '100px' }}>
<button onClick={toggle}>Child 2's button</button>
</div>
);
};

Related

How to call child1(class component) method from child2(class component) via a parent(functional component) in React JS (V 2015)? [duplicate]

Let's say I have a component tree as follows
<App>
</Header>
<Content>
<SelectableGroup>
...items
</SelectableGroup>
</Content>
<Footer />
</App>
Where SelectableGroup is able to select/unselect items it contains by mouse. I'm storing the current selection (an array of selected items) in a redux store so all components within my App can read it.
The Content component has set a ref to the SelectableGroup which enables me to clear the selection programatically (calling clearSelection()). Something like this:
class Content extends React.Component {
constructor(props) {
super(props);
this.selectableGroupRef = React.createRef();
}
clearSelection() {
this.selectableGroupRef.current.clearSelection();
}
render() {
return (
<SelectableGroup ref={this.selectableGroupRef}>
{items}
</SelectableGroup>
);
}
...
}
function mapStateToProps(state) {
...
}
function mapDispatchToProps(dispatch) {
...
}
export default connect(mapStateToProps, mapDispatchToProps)(Content);
I can easily imagine to pass this clearSelection() down to Contents children. But how, and that is my question, can I call clearSelection() from the sibling component Footer?
Should I dispatch an action from Footer and set some kind of "request to call clear selection" state to the Redux store? React to this in the componentDidUpdate() callback in Content and then immediately dispatch another action to reset this "request to call clear selection" state?
Or is there any preferred way to call functions of siblings?
You can use ref to access the whole functions of Content component like so
const { Component } = React;
const { render } = ReactDOM;
class App extends Component {
render() {
return (
<div>
<Content ref={instance => { this.content = instance; }} />
<Footer clear={() => this.content.clearSelection() } />
</div>
);
}
}
class Content extends Component {
clearSelection = () => {
alert('cleared!');
}
render() {
return (
<h1>Content</h1>
);
}
}
class Footer extends Component {
render() {
return (
<div>Footer <button onClick={() => this.props.clear()}>Clear</button>
</div>
);
}
}
render(
<App />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I think the context API would come handy in this situation. I started using it a lot for cases where using the global state/redux didn't seem right or when you are passing props down through multiple levels in your component tree.
Working sample:
import React, { Component } from 'react'
export const Context = React.createContext()
//***************************//
class Main extends Component {
callback(fn) {
fn()
}
render() {
return (
<div>
<Context.Provider value={{ callback: this.callback }}>
<Content/>
<Footer/>
</Context.Provider>
</div>
);
}
}
export default Main
//***************************//
class Content extends Component {
render() {
return (
<Context.Consumer>
{(value) => (
<div onClick={() => value.callback(() => console.log('Triggered from content'))}>Content: Click Me</div>
)}
</Context.Consumer>
)
}
}
//***************************//
class Footer extends Component {
render() {
return (
<Context.Consumer>
{(value) => (
<div onClick={() => value.callback(() => console.log('Triggered from footer'))}>Footer: Click Me</div>
)}
</Context.Consumer>
)
}
}
//***************************//
Assuming content and footer and in there own files (content.js/footer.js) remember to import Context from main.js
According to the answer of Liam , in function component version:
export default function App() {
const a_comp = useRef(null);
return (
<div>
<B_called_by_a ref={a_comp} />
<A_callB
callB={() => {
a_comp.current.f();
}}
/>
</div>
);
}
const B_called_by_a = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
f() {
alert("cleared!");
}
}));
return <h1>B. my borther, a, call me</h1>;
});
function A_callB(props) {
return (
<div> A I call to my brother B in the button
<button onClick={() => { console.log(props); props.callB();}}>Clear </button>
</div>
);
}
you can check it in codesandbox
One way I use to call the sibling function is to set a new date.
Let me explain more:
In their parent we have a function that set new date in a state (the state's name is something like "refresh date" or "timestamp" or something similar).
And you can pass state to sibling by props and in sibling component you can use useEffect for functional components or componentDidUpdate for class components and check when the date has changed, call your function .
However you can pass the new date in redux and use redux to check the date
const Parent = () => {
const [refreshDate, setRefreshDate] = useState(null);
const componentAClicked = () => setRefreshDate(new Date())
return (
<>
<ComponentA componentAClicked={componentAClicked}/>
<ComponentB refreshDate={refreshDate}/>
</>
}
const ComponentA = ({ componentAClicked}) => {
return (
<button onClick={componentAClicked}>click to call sibling function!!<button/>
)
}
const ComponentB = ({ refreshDate }) => {
useEffect(()=>{
functionCalledFromComponentA()
},[refreshDate]
const functionCalledFromComponentA = () => console.log("function Called")
return null
}
Functional components & TypeScript
Note 1: I've swapped useRef for createRef.
Note 2: You can insert the component's prop type in the second type parameter here: forwardRef<B_fns, MyPropsType>. It's confusing because the props & ref order are reversed.
type B_fns = {
my_fn(): void;
}
export default function App() {
const a_comp = createRef<B_fns>();
return (
<div>
<B_called_by_a ref={a_comp} />
<A_callB
callB={() => {
a_comp.current?.my_fn();
}}
/>
</div>
);
}
const B_called_by_a = forwardRef<B_fns>((props, ref) => {
useImperativeHandle(ref, () => ({
my_fn() {
alert("cleared!");
}
}));
return <h1>B. my borther, a, call me</h1>;
});
function A_callB(props) {
return (
<div> A I call to my brother B in the button
<button onClick={() => { console.log(props); props.callB();}}>Clear </button>
</div>
);
}

How to click programmatically a child component? react

I have two components, the parent and child. Currently I have these codes below. But unfortunately it returns an error:
TypeError: Cannot read property 'click' of null
For some reasons I want when button is click the Item component also will be click. But these codes below produces an error above. Anyone does know how to achieve it?
import React, { useRef } from 'react';
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
{dynamicBoolean ? (
<button onClick={() => itemRef.current.click()}>
click item
</button>
) : (
//more codes here
<Item ref={itemRef} />
)}
</div>
);
};
export default App;
Child component would look like below (demonstration purposes, the code is very lengthly)
import React from 'react';
const Item = (props) => {
return (
<div>
//some design here
</div>
);
};
export default Item;
You need useRef and you have to forward this ref to the Item component.
import React, { forwardRef, useRef } from 'react';
const Item = forwardRef((props, ref) => {
return <li {...props}
onClick={() => alert('clicked on Item')}
ref={ref} >MyItem</li>
})
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
<button onClick={() => itemRef.current.click()}>
click item
</button>
<Item ref={itemRef} />
</div>
);
};
export default App;
import React, { createRef } from "react";
const Hello = (props) => {
const itemRef = createRef();
const hello = () => {
itemRef.current.click();
};
return (
<div>
<button onClick={() => hello()}>click item</button>
<Item ref={itemRef} />
</div>
);
};
const Item = React.forwardRef((props, ref) => {
const myClick = () => {
console.log("this is clicked");
};
return (
<button ref={ref} className="FancyButton" onClick={myClick}>
{props.children}
</button>
);
});
export default Hello;

Getting state from child to parent

how would I get the state from the child so that the parent recognise the state from that child has changed?
const grandParent = () => (
<parent>
<child/>
</parent>
);
const child = () => {
const [isOpen, setOpen] = useState(false)
return (
<button onClick={()=>setOpen(!isOpen)}>Open</button>
)};
const grandParent = () => {
const [ isOpen, setOpen ] = useState(false)
return (
<parent>
<child onHandlerClick={ () => setOpen(!isOpen) }/>
</parent>
);
};
const child = (onHandlerClick) => {
// Note I removed the local state. If you need the state of the parent in the child you can pass it as props.
return (
<button onClick={ () => onHandlerClick() }>Open</button>
);
};
When you need to keep the state in the parent and modify it inside the children, no matter child state. You pass a handler in props from the parent where it's defined to modify the state. The child execute this handler.
This pattern is called state hoisting.
I think I would do something like that:
function GrandParent(){
return <Parent />
}
function Parent() {
const [isOpen, setOpen] = useState(false);
const handleToggle = useCallback(() => {
setOpen(!isOpen);
}, [isOpen, setOpen]);
return <Child handleToggle={handleToggle} />;
}
function Child(props) {
return <button onClick={() => props.handleToggle()}>Open</button>;
}
You can do the following using functional component. Write the Child component as below:
import React, {useEffect, useState} from 'react';
function Child(props) {
const {setStatus} = props;
const [isOpen, setOpen] = useState(false);
function clickHandler() {
setOpen(!isOpen);
setStatus(`changes is ${!isOpen}`);
}
return (
<div>
<button onClick={clickHandler}>Open</button>
</div>
);
}
export default Child;
Write the GrandParent component as below:
import React, {useEffect, useState} from 'react';
import Child from "./Child";
function GrandParent(props) {
function setStatus(status) {
console.log(status);
}
return (
<div>
<Child setStatus={setStatus}></Child>
</div>
);
}
export default GrandParent;
Use GrandParent component in App.js as below:
import React from "react";
import GrandParent from "./GrandParent";
class App extends React.Component {
render() {
return (
<GrandParent/>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
export default App;
You can add props to child and call onChange each time the state is changed
const grandParent = () => (
function handleChange() {}
<parent>
<child onChange={handleChange} />
</parent>
);
const child = (props) => {
const [isOpen, setOpen] = useState(false);
function onChange() {
setOpen(prevOpen => {
props.onChange(!prevOpen);
return !prevOpen;
});
}
return (
<button onClick={()=>setOpen(!isOpen)}>Open</button>
)};
You can do something like this:
const grandParent = () => {
const [isOpen, setOpen] = useState(false)
return (
<parent isOpen>
<child isOpen onChangeState={() => setOpen(!isOpen)} />
</parent>
)
}
const child = (props) => {
return (
<button
onClick={() => {
props.onChangeState()
}}>
Open
</button>
)
}
Explanation:
You manage the state in the grandParent component and passing it in the parent component (and also at the child if you need it).
The child has a prop which is called when the button is clicked and leads to the update of the grandParent state

How to keep track of selected component?

I'm working on a coding challenge and I'm having problems to navigate back and forth between my components. Here's what a very simplified version of my parent component looks like:
import React, { useState } from 'react';
function App() {
const [component, setComponent] = useState({
shoppingBasket: true,
contactDetails: false,
paymentOptions: false,
checkout: false,
summary: false
});
const switchState = input => {
const { name, value } = input.target;
setComponent({
...component,
[name]: value
});
};
return (
<>
{
component.shoppingBasket &&
<ShoppingBasket
shoppingBasket={component.shoppingBasket}
switchState={switchState}
/>
}
{
component.contactDetails &&
<ContactDetails
contactDetails={component.contactDetails}
switchState={switchState}
/>
}
{
component.paymentOptions &&
<PaymentOptions
paymentOptions={component.paymentOptions}
switchState={switchState}
/>
}
{
component.checkout &&
<Checkout
checkout={component.checkout}
switchState={switchState}
/>
}
{
component.summary &&
<Summary
summary={component.summary}
switchState={switchState}
/>
}
</>
);
}
export default App;
It's supposed to be the checkout screen of an e-commerce page and it starts with the <ShoppingBasket /> component. When clicking "Continue", it shows the next component, when clicking "Back" it goes back to the previous one. They should appear in the order shown in the code.
My first attempt was to show the next component only when the previous one(s) evaluate true but at the end it was showing all of the components, so that didn't work. Note: I passed the switchState function and the respective state as prop to the child component.
I guess the smartest way is to show only the component that is currently selected but how do I do that? I assume working with IDs?
And would it be still necessary to keep track of the previous components evaluating to true, when it's only showing the selected one anyway?
SOLUTION:
Parent component (simplified but working):
import React, { useState } from 'react';
// COMPONENTS
import ShoppingBasket from './components/ShoppingBasket';
import PaymentOptions from './components/PaymentOptions';
import ContactDetails from './components/ContactDetails';
import Checkout from './components/Checkout';
import Summary from './components/Summary';
export default function App() {
const [component, setComponent] = useState(0);
const switchComponent = (index) => {
setComponent(index);
};
return (
<>
{
{
0: <ShoppingBasket switchComponent={switchComponent} />,
1: <ContactDetails switchComponent={switchComponent} />,
2: <PaymentOptions switchComponent={switchComponent} />,
3: <Checkout switchComponent={switchComponent} />,
4: <Summary />,
}[component]
}
</>
);
}
Child component with index 3 (also simplified):
import React from 'react';
export default function Checkout({ switchComponent }) {
return (
<>
<button onClick={() => switchComponent(2)}>BACK</button>
<button onClick={() => switchComponent(4)}>CONTINUE</button>
</>
);
}
Update:
import React, { useState } from 'react';
function App() {
const [component, setComponent] = useState(0);
const switchComponent = index => {
setComponent(index);
};
return (
<>
{
// this act like switch case js function
{
0:
(<ShoppingBasket
//shoppingBasket={component.shoppingBasket} // no need for this
componentIndex={component}
switchState={switchComponent}
/>),
1:
(<ContactDetails
// contactDetails={component.contactDetails}
componentIndex={component}
switchState={switchComponent}
/>),
2:
(<PaymentOptions
// paymentOptions={component.paymentOptions}
componentIndex={component}
switchState={switchComponent}
/>),
3:
(<Checkout
// checkout={component.checkout}
componentIndex={component}
switchState={switchComponent}
/>),
4:
(<Summary
// summary={component.summary}
componentIndex={component}
switchState={switchComponent}
/>)
}[component]
}
</>
);
}
export default App;
ShoppingBasket.js
const ShoppingBasket = props => {
return (
// your ShoppingBasket component .....
)
}
** ContactDetails.js**
const ContactDetails = props => {
return (
// your ContactDetails component .....
)
}
....... the same for other components
App.js
import React, { useState } from 'react';
function App() {
const [component, setComponent] = useState(0);
const switchComponent = index => {
setComponent(index);
};
return (
<>
{
// this act like switch case js function
//
{
0:
(<ShoppingBasket/>),
1:
(<ContactDetails/>),
2:
(<PaymentOptions/>),
3:
(<Checkout/>),
4:
(<Summary/>)
}[component]
}
// navigation buttons .... always visible
<NavigateButtons
componentIndex={component}
switchState={switchComponent}
/>
</>
);
}
export default App;
----------------------------------***----------------
Note : make sure the buttons next and previous are just one splited component so you can use it in diffrent other components
const NavigateButtons = (props) => (
<div>
<Button
disabled={componentIndex == 4}
onClick={()=> props.switchState(componentIndex + 1)}>next
</Button>
<Button
disabled={componentIndex == 0}
onClick={()=> props.switchState(componentIndex - 1)}>previous
</Button>
</div>
)

How change child components value which is passed from parent without state

I can't solve this case.
It's from parent
<ChildDiv key={el} width={i} value={' '}></ChildDiv>
It's from Child
<MyContext.Consumer>
{context => (
<div onClick={() => test(value, context.ChangeTurn)} className="row">
</div>
)}
</MyContext.Consumer>
let test = (value, context) => {
if (value !== "X" && val !== "O") {
value = "laslda";
}};
So I want change value from local function.
If you want context consumers to be able to update the context, it's better to expose a method within the context to perform the update; the component provider can then pass a new context object to consumers, provoking a re-render.
Here's what the code could look like:
Context (./context.js):
import { createContext } from 'react';
const Context = createContext({ value: null, setValue: () => {} });
export default Context;
Provider (./provider.js):
import { useState } from 'react';
import Context from './context';
const Provider = ({ value: startingValue, children }) => {
const [value, setValue] = useState(startingValue);
return (
<Context.Provider value={{ value, setValue }}>
{children}
</Context.Provider>
);
};
Sample consumer (./consumer.js):
import Context from './context';
export default Context.Consumer;
Now you can do this at your root (./app.js):
import Provider from './provider';
import ChildComponent from './child-component';
export default function App() {
return (
<Provider value={0}>
<ChildComponent/>
</Provider>
);
}
...and this in child components (using hooks, ./child-component.js):
import { useContext } from 'react';
import Context from './context';
export default function ChildComponent() {
const { value, setValue } = useContext(Context);
return (
<div>
<h1>Counter: {value}</h1>
<button onClick={e => { e.preventDefault(); setValue(value - 1) }}>-</button>
<button onClick={e => { e.preventDefault(); setValue(value + 1) }}>+</button>
</div>
);
}
...or without hooks:
import React from 'react';
import Consumer from './consumer';
export default function ChildComponent() {
return (
<Consumer>
({ value, setValue }) => (
<div>
<h1>Counter: {value}</h1>
<button onClick={e => { e.preventDefault(); setValue(value - 1) }}>-</button>
<button onClick={e => { e.preventDefault(); setValue(value + 1) }}>+</button>
</div>
);
</Consumer>
);
}
In react, props like the value you passing, are Read-Only.
Remember that props are readonly. They should not be modified in any way.
You may want to provide a setter:
<MyContext.Provider value={{ dispatch: this.setState }}>
<ChildDiv key={el} width={i} value={value}></ChildDiv>
</MyContext.Provider>
<MyContext.Consumer>
{({ dispatch }) => (
<button onClick={() => dispatch({value: 'New Value'})}>Button</button>
)}
</MyContext.Consumer>

Categories