Change react hook state from parent component - reactjs

I have a hook component like this:
import React, { useState} from "react";
const MyComponent = props => {
const [value, setValue] = useState(0);
const cleanValue = () => {
setValue(0);
};
return (<span><button onClick={()=>setValue(1)}/>{value}<span>)
}
I want to reset value from the parent component. How I can call clean value from parent component? the parent component is a stateful component.

If the parent has to control the child state then probably the state must reside in the parent component itself. However you can still update child state from parent using ref and exposing a reset method in child. You can make use of useImperativeHandle hook to allow the child to only expose specific properties to the parent
const { useState, forwardRef, useRef, useImperativeHandle} = React;
const Parent = () => {
const ref = useRef(null);
return (
<div>
<MyComponent ref={ref} />
<button onClick={() => {ref.current.cleanValue()}} type="button">Reset</button>
</div>
)
}
const MyComponent = forwardRef((props, ref) => {
const [value, setValue] = useState(0);
const cleanValue = () => {
setValue(0);
};
useImperativeHandle(ref, () => {
return {
cleanValue: cleanValue
}
});
return (<span><button type="button" onClick={()=>setValue(1)}>Increment</button>{value}</span>)
});
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>

From the React documentation about Fully uncontrolled component with a key:
In order to reset the value ..., we can use the special React attribute called key. When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.
In this case, we can use a simple counter to indicate the need for a new instance of MyComponent after pressing the Reset button:
const { useState } = React;
const Parent = () => {
const [instanceKey, setInstanceKey] = useState(0)
const handleReset = () => setInstanceKey(i => i + 1)
return (
<div>
<MyComponent key={instanceKey} />
<button onClick={handleReset} type="button">Reset</button>
</div>
)
}
const MyComponent = () => {
const [value, setValue] = useState(0)
return (
<span>
<button type="button" onClick={()=>setValue(v => v + 1)}>{value}</button>
</span>
)
};
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>

You can't / shouldn't. Using hooks instead of stateful class components doesn't change the fact that if you want the parent to own the state, you need to declare the state in the parent.
It should look something like this, depending on when you want to reset the value (here I used another button):
const MyButton = (props) = (
// Whatever your button does, e.g. styling
<span>
<button {...props} />
<span>
)
const Parent = props => {
const [value, setValue] = useState(0);
const cleanValue = () => setValue(0);
return (
<div>
<MyButton onClick={() => setValue(1)}>
{value}
</MyButton>
<button onClick={cleanValue}>
Reset
</button>
</div>
)
}

Related

Why does a React component unmount but jsx doesn't

I came across this behaviour recently and was trying to understand its cause. Basically, what I noticed so far is that a React child component will be mounted and unmounted on state changes of its parent. However, a jsx containing the same child component does not.
I put together this simplified example to demonstrate the behaviour.
const Child = ({ title }) => {
const [count, setCount] = React.useState(0);
const increment = () => setCount((x) => x + 1);
return (
<button onClick={increment}>
{title} Current count = {count}
</button>
);
};
const App = () => {
const [, setState] = React.useState(false);
const rerender = () => setState((x) => !x);
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
return (
<div>
<button onClick={rerender}>Re render parent</button>
<br />
<ChildWrapper />
{childWrapperJsx}
</div>
);
}
const domContainer = document.querySelector('#root');
const root = ReactDOM.createRoot(domContainer);
const e = React.createElement;
root.render(e(App));
<script src="https://unpkg.com/react#18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#18/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Does anyone know the reason behind this? Is there a way to prevent React from unmounting a child component in this case?
I think your question is about the difference between the two buttons' behavior, one is coming back to zero after clicking to rerender the parent component and the other not.
First of all, we should understand the life-cycle of a function component, the render is executing each state change.
function App() {
/**
* it will be executed each render
*/
return (<div />);
}
Also we have to understand the difference between create a component and instantiate a component.
// this is creating a component
const ChildWrapper = () => <Child title="Component" />;
// this is instantiating a component
const childWrapperJsx = <Child title="jsx" />;
JSX is only a tool to transpile the syntaxe of this <Child title="jsx" /> to React.createElement('div', { title: "jsx" }) for example. To explain better, the code is transpiled to something like this:
// this is creating a component
const ChildWrapper = () => React.createElement('button', { title: 'Component' });
// this is instantiating a component
const childWrapperJsx = React.createElement('button', { title: 'jsx' }) ;
Without going deep in the hole. In your implementation we have both components implemented into the render of the parent, like this.
function App() {
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
return (<div />);
}
Right now we realized that the first implementation is creating a new component each render, so that react can't memoize the component in the tree, it is not possible to do it.
// each render ChildWrapper is a new component.
const ChildWrapper = () => <Child title="Component" />;
And the second one, the childWrapperJsx is already a react element instantiated and memoized. React will preserve the same instance on the parent component life cycle.
According to React's best practice, it is not recommended to create components inside the render of another component. If you try to put both implementations outside of the component, you will be able to see that both components won't be unmounted after the parent component's render.
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
function App() {
return (
<>
<ChildWrapper />
{childWrapperJsx}
</>
);
}

React: Pass state or data from parent to child component which is in object

I was trying to pass state or data as props from parent to a child which was in the state object. When a button is clicked on the parent component, the div of the child component will show the changes in true or false.
The child component successfully rendered the changes when <Child/> was placed directly in the parent, but it did not if the <Child/> was declared in object (state). I have tried the context i.e. createContext in the parent component and useContext in the child, it did not display successfully also.
How can the child component render the changes if it is in the state object in the parent component?
Please refer to the below example:
Parent.js (Successful code)
import React, { useState, useEffect } from 'react'
import Child from './Child'
function Parent() {
const [childs, setChilds] = useState()
const [toggle, setToggle] = useState(true)
const changeToggle = () => {
setToggle(!toggle)
}
useEffect(() => {
setChilds(<div><Child modeToggle={toggle} /></div>)
}, [])
return (
<div>
<div>
<button onClick={changeToggle}>Change toggle</button>
</div>
<div>
<div><Child modeToggle={toggle} /></div>
</div>
</div>
)
}
export default Parent
Parent.js (unsuccessful code)
import React, { useState, useEffect } from 'react'
import Child from './Child'
function Parent() {
const [childs, setChilds] = useState()
const [toggle, setToggle] = useState(true)
const changeToggle = () => {
setToggle(!toggle)
}
useEffect(() => {
setChilds(<div><Child modeToggle={toggle} /></div>)
}, [])
return (
<div>
<div>
<button onClick={changeToggle}>Change toggle</button>
</div>
<div>
{childs}
</div>
</div>
)
}
export default Parent
Child.js
import React, { useEffect } from 'react'
function Child(props) {
useEffect(()=>{
console.log(props.modeToggle)
},[props.modeToggle])
return (
<div>{props.modeToggle.toString()}</div>
)
}
export default Child
The snapshot of the web browser:
Web snapshot
The problem is that the toggle inside the effect hook is in a stale closure:
useEffect(() => {
setChilds(<div><Child modeToggle={toggle} /></div>)
}, [])
The above callback only runs once, so the childs that exists will always have a modeToggle prop of the initial value of toggle, which is true. Even if changeToggle is called, since the useEffect callback doesn't run again, the childs remains with its initial toggle prop.
I'd conditionally render the childs instead - put a boolean into state indicating whether or not to render it, so the prop can be passed down naturally in the parent's returned JSX:
function Parent() {
const [toggle, setToggle] = useState(true)
const changeToggle = () => {
setToggle(!toggle)
}
const [showChilds, setShowChilds] = useState(false);
useEffect(() => {
setShowChilds(true);
}, [])
return (
<div>
<div>
<button onClick={changeToggle}>Change toggle</button>
</div>
<div>
{
showChilds
? <div><Child modeToggle={toggle} /></div>
: null
}
</div>
</div>
)
}
#CertainPerformance, thanks for answering. I have based on your answer to getting a similar solution. Thank you very much.
It seems there is no way to directly get the updated value if it is in state object.
import React, { useState, useEffect } from 'react'
import Child from './Child'
function Parent() {
const [childs, setChilds] = useState()
const [toggle, setToggle] = useState(true)
const changeToggle = () => {
setToggle(!toggle)
}
useEffect(() => {
setChilds(<div><Child modeToggle={toggle} /></div>)
}, [toggle])
return (
<div>
<div>
<button onClick={changeToggle}>Change toggle</button>
</div>
<div>
{childs}
</div>
</div>
)
}
export default Parent

How to call a Parent method in Child Component using Hook (ForwardRef concept)

I tried the following code but it fails
So, this is my Parent Component:
import React from 'react'
import ChildComponent from './ChildComponent';
const ParentComponent = (props) => {
//step 1
// const inputRef = React.createRef();
const buttonRef = React.useRef();
const focusHandler = () => {
alert("hi");
}
return (
<div>
{/* In parent, we generally pass reference to child which we dint do here, lets see if props children help here */}
{props.children}
<ChildComponent ref="buttonRef" />
</div>
)
}
export default ParentComponent;
This is my child component:
import React from 'react'
const ChildComponent = React.forwardRef((props, ref) => {
return (
<div>
<button onClick={ref.focusHandler}>Focus Input</button>
</div>
)
})
export default ChildComponent;
On click of the button above in child component, I wish to call Parent method.
How can that be achieved?
EDITED
The reason you're getting the error is because refs in function components need to be passed using ref={buttonRef}, not ref="buttonRef". Class components have a thing they can do with string refs, but it's not recommended even there.
As for calling a function from a parent component, you don't need refs to do this. So if that was the only reason you were using a ref, you can remove the ref. Instead, pass the function as a prop:
const ParentComponent = (props) => {
const focusHandler = () => {
alert("hi");
}
return (
<div>
<ChildComponent focusHandler={focusHandler} />
</div>
)
}
const ChildComponent = (props) => {
return (
<div>
<button onClick={props.focusHandler}>Focus Input</button>
</div>
)
}
Just replace ref by focusHandler like below in parent component
<ChildComponent focusHandler={focusHandler} />
Then in ChildComponent, remove ref as well.
If you wonder how to use refs in this case (even though this is not the recommended way to pass callbacks), you need to assign focusHandler key and use the ref with ref.current, refer to Components and Props docs.
const ParentComponent = () => {
const buttonRef = React.useRef({ focusHandler: () => alert("hi") });
return (
<div>
<ChildComponent ref={buttonRef} />
</div>
);
};
const ChildComponent = React.forwardRef((props, ref) => {
return (
<div>
<button onClick={ref.current.focusHandler}>Focus Input</button>
</div>
);
});

how to use React.memo with a Component contains children

I have two Components, and I wrapped Parent with React.memo:
Child
const Child = ()=> <div>I'm Child</div>
export default Child
Parent
const Parent = (props)=> <div>{props.children}</div>
export default React.memo(Parent)
Use in App:
const App = () => {
const [count, setCount] = useState(0)
return(
<div>
<button onClick={()=>setCount(count+1)}></button>
<Parent>
<Child></Child>
</Parent>
</div>
)
}
Click the button, result:
The Parent Component will rerender, so the memo not working because it's children is a function component
So, how can I prevent rerender?
I know a way to solve by useMemo, but it's ugly and not friendly, do you have better ideas?
const App = () => {
const [count, setCount] = useState(0)
const children = useMemo(()=><Child></Child>,[])
return(
<div>
<button onClick={()=>setCount(count+1)}></button>
<Parent>
{children}
</Parent>
</div>
)
}
Wrap your <Child /> with React.memo:
const Child = ()=> {
console.log('render') // fires only once - on initial render
return <div>I'm Child</div>
}
const MChild = React.memo(Child);
const Parent = (props)=> <div>{props.children}</div>
const MParent = React.memo(Parent)
const App = () => {
const [count, setCount] = useState(0);
return(
<div>
<button onClick={()=>setCount(count+1)}>increment {count}</button>
<MParent>
<MChild></MChild>
</MParent>
</div>
)
}
render(<App />, document.getElementById('root'));
const children = useMemo(()=><Child></Child>,[])
Is the easiest way to go. Using memo(Child) wont work since jsx in fact returns a new object whenever you call <Child />. React.memo by default just use simple shallow comparison so there really is no other direct way to solve it. You can create your own function that would eventually support children and pass it to React.memo(MyComp, myChildrenAwareEqual).
Move <Parent><Child/></Parent> into a separate component, and memoize that component instead:
const Family = memo(() => <Parent><Child/></Parent>);
const App = () => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count => count + 1)}>{count}</button>
<Family />
</>
)
}
demo
const Memo = React.memo(({ children, value }) => {
return children
}, (prev, next) => prev.value === next.value)
And then to use it
function Title() {
const [count, setCount] = useState(0)
const onClick = () => {
setCount(c => c + 1)
}
const a = ''
return (
<>
<div onClick={onClick}>{count}</div>
<Memo value={a}>
<Child>
<Nothing2 a={a} />
</Child>
</Memo>
</>
)
}
When a changes, Child renders, otherwise it bails out with previous render.
I outline the rational behind it in my blog, https://javascript.plainenglish.io/can-usememo-skip-a-child-render-94e61f5ad981

How to focus something on next render with React Hooks

I'm playing with hooks, and I'm trying to do the following:
import React, { useState, useRef } from 'react';
const EditableField = () => {
const [isEditing, setEditing] = useState(false);
const inputRef = useRef();
const toggleEditing = () => {
setEditing(!isEditing);
if (isEditing) {
inputRef.current.focus();
}
};
return (
<>
{isExpanded && <input ref={inputRef} />}
<button onClick={toggleEditing}>Edit</button>
</>
);
};
This is going to fail, because current is null, since the component haven't re-rendered yet, and the input field is not yet rendered (and therefore can't be focused yet).
What is the right way to do this? I can use the usePrevious hook proposed in the React Hooks FAQ, but it seems like a painful workaround.
Is there a different way?
You can use the useEffect hook to run a function after every render when isEditing changed. In this function you can check if isEditing is true and focus the input.
Example
const { useState, useRef, useEffect } = React;
const EditableField = () => {
const [isEditing, setEditing] = useState(false);
const toggleEditing = () => {
setEditing(!isEditing);
};
const inputRef = useRef(null);
useEffect(() => {
if (isEditing) {
inputRef.current.focus();
}
}, [isEditing]);
return (
<div>
{isEditing && <input ref={inputRef} />}
<button onClick={toggleEditing}>Edit</button>
</div>
);
};
ReactDOM.render(<EditableField />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I know the accepted answer covers the requested element in the above question.
But as an additional note, if you are using functional components, make use of React.forwardRef to pass down the reference to child components. It might be
definitely useful for someone who refers to this question later on.
In a more cleaner way, you can write your child component which accept the ref as given below:
const InputField = React.forwardRef((props, ref) => {
return (
<div className={props.wrapperClassName}>
<input
type={props.type}
placeholder={props.placeholder}
className={props.className}
name={props.name}
id={props.id}
ref={ref}/>
</div>
)
})
Or Simply use this component
import { FC, useEffect, useRef } from 'react'
export const FocusedInput: FC<JSX.IntrinsicElements['input']> = (props) => {
const inputRef = useRef<null | HTMLElement>(null)
useEffect(() => {
inputRef.current!.focus()
}, [])
return <input {...props} type="text" ref={inputRef as any} />
}

Resources