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
Related
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>
);
}
See codesandbox here
I am trying to pass the state value of a child component up to its parent by using React's useImperativeHandle. However, it appears that my parent component is not receiving the updated state value of the child component when it console logs the child's component value; console.log(componentRef.current.state) always is logged as false.
Why is this not working and how can I accurately receive the mutated state value of my child component in my parent component by passing the necessary ref? Thanks!
index.tsx:
import React from "react";
import ReactDOM from "react-dom";
const Component = React.forwardRef((props, ref) => {
const [state, set] = React.useState(false);
React.useImperativeHandle(ref, () => ({
state
}));
const handleClick = () => {
set(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{state ? "On" : "Off"}</h1>
</>
);
});
const App = () => {
const componentRef = React.useRef(null);
React.useEffect(() => {
console.log(componentRef.current.state);
}, [componentRef]);
return <Component ref={componentRef} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If you just want that functionality you can use something like:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = props => {
const [state, set] = React.useState(false);
useEffect(() => props.callback(state), [state])
const handleClick = () => {
set(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1 >{state ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
return <Component callback={val => console.log(val)} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
But if you really need to use ref, just comment it so I'll remove this answer.
Is it possible I can dynamic import based on the props from Parent in React?
For my code:
Parent
<Parent>
<Child icon="svg1" />
<Child icon="svg2" />
<Child icon="svg3" />
</Parent>
Child
import svg1 from './svg1.js'
import svg2 from './svg2.js'
import svg3 from './svg3.js'
switch (props.icon)
{
case 'svg1':
output = <svg1/>
break
case 'svg2':
ouput = <svg2/>
break
default:
ouput = <svg3/>
}
return (
<div>
{output}
</div>
)
How can I use a dynamic icon value to import svg element based on the icon value from parent rather than swtich?
If you want to avoid doing a switch, you could build an object with your imports:
const icons = {
svg1,
svg2,
svg3,
};
const Child = (props) => {
const Component = icons[props.icon];
return <Component />;
};
You could try using the image tag, like this;
import svg1 from './svg1.js'
import svg2 from './svg2.js'
import svg3 from './svg3.js'
<Parent>
<Child icon={svg1} />
<Child icon={svg2} />
<Child icon={svg3} />
</Parent>
Child
return (
<div>
<img src={output} />
</div>
)
Based on this one - https://sung.codes/blog/2017/12/03/loading-react-components-dynamically-demand/
I finally get the solution:
const { icon } = props
const [components, setComponents] = useState([])
const addComponent = async (icon) => {
import(`./${icon}.js`)
.then((component) => {
setComponents(components.concat(component.default))
})
.catch((error) => {
console.error(`"${icon}" not yet supported`)
})
}
const getElement = async (icon) => {
await addComponent(icon)
}
useEffect(() => {
console.log("icon", icon)
getElement(icon)
}, [])
const componentsElements = components.map((Component) => <Component />)
...
return <div>{componentsElements}</div>
I have a very heavy (computationally) functional component (Parent) which doesn't have a state and has few Child sub-components with local state. Children are dependent only on props send from the Parent.
I pass a function to one of the children (ChildA) to change the value of a variable on the Parent.
This variable is one of the props of a different Child component (ChildB) which has a state based on that prop and updates it in useEffect hook.
The ChildB component does not re-render when the value passed as prop changes on the Parent component.
Sure, introducing state (useState hook) on Parent fixes this but re-renders the parent over and over and kills the performance as Parent has 500+ nested components which all get re-rendered.
Introducing some kind of a Store (Redux, MobX) would probably solve the issue but that would be an overkill.
A simplified example:
import React, { useEffect, useState } from "react";
export default function App() {
return <Parent />
}
const ChildA = ({ onAction }) => {
return <button onClick={onAction}>CLICK</button>;
};
const ChildB = ({ coreValue }) => {
const [value, setValue] = useState(0);
useEffect(() => {
setValue(coreValue);
}, [coreValue]);
return <div>My value: {value}</div>;
};
const Parent = () => {
let calculatedValue = 0;
const changeValue = () => {
calculatedValue += Math.random();
};
return (
<div>
<ChildA onAction={changeValue} />
<ChildB coreValue={calculatedValue} />
</div>
);
};
You can test the code here: https://codesandbox.io/s/vigilant-wave-r27rg
How do I re-render only ChildB on props change?
You have to store the value in parent component state and just send the parent component state value to the ChildB, there you don't need to maintain state and useEffect hook to catch the change. See the code here: https://codesandbox.io/s/adoring-chatelet-sjjfs
import React, { useState } from "react";
export default function App() {
return <Parent />;
}
const ChildA = ({ onAction }) => {
return <button onClick={onAction}>CLICK</button>;
};
const ChildB = ({ coreValue }) => {
return <div>My value: {coreValue}</div>;
};
const Parent = () => {
const [value, setValue] = useState(0);
const changeValue = () => {
setValue(value + Math.random());
};
return (
<div>
<ChildA onAction={changeValue} />
<ChildB coreValue={value} />
</div>
);
};
React's useCallback and memo prevent's unnecessary re-rendering. Note that ChildA doesn't re-render regardless of the number of times the Parent or ChildB state's changes. Also, your current example doesn't need useState / useEffect in ChildB
https://codesandbox.io/s/usecallback-and-memo-ptkuj
import React, { useEffect, useState, memo, useCallback } from "react";
export default function App() {
return <Parent />;
}
const ChildA = memo(({ onAction }) => {
console.log("ChildA rendering");
return <button onClick={onAction}>CLICK</button>;
});
const ChildB = memo(({ coreValue }) => {
const [value, setValue] = useState(0);
useEffect(() => {
setValue(coreValue);
}, [coreValue]);
return <div>My value: {value}</div>;
});
const Parent = () => {
const [calculatedValue, setCalculatedValue] = useState(0);
const changeValue = useCallback(() => {
setCalculatedValue(c => (c += Math.random()));
}, []);
return (
<div>
<ChildA onAction={changeValue} />
<ChildB coreValue={calculatedValue} />
</div>
);
};
I have a top level context Provider, followed by a Parent class component follow by a functional stateless Child.
I can update the my context value from the Child, but not from the parent, even though the value updates in the parent.
How can I update and share state between both components using context?
import React from "react";
import ReactDOM from "react-dom";
const Context = React.createContext();
const Provider = ({ children }) => {
const [value, setValue] = React.useState(0);
return (
<Context.Provider value={{ value, setValue }}>{children}</Context.Provider>
);
};
const Child = () => {
const { value, setValue } = React.useContext(Context);
return <div onClick={() => setValue(value + 1)}>Plus plus!!</div>;
};
class Parent extends React.Component {
render() {
const { value, setValue } = this.context;
return (
<div>
<div onPress={() => setValue(value - 1)}>MINUS MINUS!</div>
<div>{this.props.children}</div>
<h1>{value}</h1>
</div>
);
}
}
Parent.contextType = Context;
function App() {
return (
<Provider>
<Parent>
<Child />
</Parent>
</Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
https://codesandbox.io/s/thirsty-oskar-ocmxr
Change the "onPress" to "onClick" will work. I have tested it.