how to call 2 different methods of Child from Parent using useImperativeHandler? - javascript

I have a parent component where I need to call 2 methods of its Child. I am able to call one using useImperativeHandler like
const Parent = () => {
const childRef = useRef();
return (
<div>
<Child ref={childRef} />
<Button onClick={() => childRef.current.methodOne()}>
Submit
</Button>
</div>
);
};
and then in Child Compoennt
const Child = forwardRef((props, ref) => {
useImperativeHandle(
ref,
() => ({
methodOne() {
// some code
},
}),
[]
);
return;
});
So far it works very ok.
But I want another button in the Parent component to call a second method(let's call it methodTwo) in the same Child. How can I do it?

Edit: Found the answer. userImperativeHandle takes multiple methods like this and you can call them normally in Parent Component.
useImperativeHandle(ref, () => ({
methodOne: () => {
},
methodTwo: () => {
}
}));

check this example , i tried to make it vary simple to understand , but if you get into trouble i am happy to even help you on a zoom call
this is the basic concept in react , to pass a method from parent to child
https://stackblitz.com/edit/react-yh2wau?file=src%2FChild.jsx
i am passing a console.log function from parent to child in this example
and the 2 buttons call different methods like what you want to do .
hopes that makes sense!
for more information and learning i suggest going through the documentation of React
try this tutorial
it will give you the basics

Related

Reducing renders in child components of a context

I'm trying to delve deeper than the basics into React and am rebuilding a Tree library I had written in plain JS years back. I need to expose an API to users so they can programmatically add/remove nodes, select nodes, etc.
From what I've learned, a ref and context is a good approach. I've built a basic demo following the examples (without ref, for now) but I'm seeing every single tree node re-render when a selection is made, even though no props have changed for all but one.
I've tried a few things like memoizing my tree node component, etc but I feel like I'm failing to understand what's causing the re-render.
I'm using the react dev tools to highlight renders.
Here's a codesandbox demo.
My basic tree node component. I essentially map this for every node I need to show. On click, this calls select() from my context API. The rerender doesn't happen if that select() call is disabled.
const TreeNodeComponent = ({ id, text, children }) => {
console.log(`rendering ${id}`);
const { select } = useTreeContext();
const onClick = useCallback(
(event) => {
event.preventDefault();
event.stopPropagation();
select([id]);
},
[select, id]
);
return (
<div className="tree-node" onClick={onClick}>
{text}
{children ? <TreeNodesComponent nodes={children} /> : ""}
</div>
);
};
The important part of my context is the provider:
const TreeContextProvider = ({ children, nodes = [], selectedNodes }) => {
const [allNodes] = useState(nodes);
const [allSelectedNodes, setSelectedNodes] = useState(selectedNodes || []);
const api = useMemo(
() => ({
selected: () => allSelectedNodes,
select: (nodes) => {
setSelectedNodes(Array.from(new Set(allSelectedNodes.concat(nodes))));
}
}),
[allSelectedNodes]
);
const value = useMemo(
() => ({
nodes: allNodes,
selectedNodes: allSelectedNodes,
...api
}),
[allNodes, allSelectedNodes, api]
);
return <TreeContext.Provider value={value}>{children}</TreeContext.Provider>;
};
Again, when the call to setSelectedNodes is disabled, the rerenders don't happen. So the entire state update is triggering the render, yet individual props to all but one tree component do not change.
Is there something I can to improve this? Imagine I have 1000 nodes, I can't rerender all of them just to mark one as selected etc.

Calling function defined within a react function component on a click event form another function component - React.js

I am constructing some node objects in a function(prepareNodes) to pass to React Flow within a functional component A (lets say), and I have defined a custom node component(CardNode) stateless, which has a button. On button click it should trigger the function(prepareNodes) defined within Component A.
function ComponentA = ({ selectedNodes }) => {
const reactFlowWrapper = useRef(null);
const [elements, setElements] = useState([]);
const [edges, setEdges] = useState([]);
const prepareNode = async (nodeid) => {
//some service calls to fetch data and constuct nodes
setElements([ ...nodes]);
setEdges([...edges]);
}
return (
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={elements}
edges={edges}
//some properties
>
</ReactFlow>
</div>
</ReactFlowProvider>
)
};
export default ComponentA;
function CardNode({ data }) {
const renderSubFlowNodes = (id) => {
console.log(id);
//prepareNode(id)
}
return (
<>
<Handle type="target" position={Position.Top} />
<div className="flex node-wrapper">
<button className="btn-transparent btn-toggle-node" href="#" onClick={() => renderSubFlowNodes(data['id']) }>
<div>
<img src={Icon}/>
</div>
</button>
</div>
<Handle type="source" position={Position.Bottom}/>
</>
);
}
export default CardNode;
I looked for some references online, and most of them suggest to move this resuable function out of the component, but since this function carries a state that it directly sets to the ReactFlow using useState hook, I dont think it would be much of a help.
Other references talks about using useCallback or useRefs and forwardRef, useImperativeHandle especially for functional component, Which I did not quite understand well.
Can someone suggest me a solution or a work around for this specific use-case of mine.
You can add an onClick handler to the each node, and within the node view you call this handler on click.
In the parent Component within the onClick handler you can call prepareNode as needed.
useEffect(() => {
setElements(
elements.map(item => {
...item,
onClick: (i) => {
console.log(i);
prepareNode();
},
})
)},
[]);
The classical approach is to have a parent object that defines prepareNode (along with the state items it uses) and pass the required pieces as props into the components that use them.
That "parent object" could be a common-ancestor component, or a Context (if the chain from the parent to the children makes it cumbersome to pass the props all the way down it).

How to pass data from vanilla JavaScript to React functional component

I'm trying to update (setState) a React functional component from within "regular" (vanilla) JavaScript.
I searched through StackOverflow but all the answers deal with passing data from React to (vanilla) JavaScript, and not the other way around.
Let's take the example from the docs:
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
To render it in JavaScript, I do:
let example = ReactDOM.render(
<Example />,
document.getElementById('example-wrapper')
);
Now suppose I want to manually update the count from the vanilla JavaScript code, outside of react. Like:
function updateExampleCount(newCount) {
example.setCount(newCount); // ???
}
I can't access the component state, as setCount is a private variable inside the function, and example returned from render is null.
If I use a class component, then render returns a reference to the component and then I can call example.setState. But I prefer not to convert my component into a class if I can avoid it.
The docs for render say:
Render a React element into the DOM in the supplied container and return a reference to the component (or returns null for stateless components).
But my component does have a state (count), it just doesn't recognize it.
If it's not possible to use the return value from render, is there another way to "get" the component and then use setCount (or some other way to set the state)?
Or do I just have to use a class component for this?
Thanks.
There is no way to access the state from outside the component. It's like trying to access a locally scoped variable from outside a function.
Using a class component wouldn't help either since you wouldn't be able to get hold of the instance of the class created inside the React app.
If you want to trigger a state change from outside the application, then the application needs to provide an event handler.
For (a really quick and dirty) example:
const outside = {
value: 2,
callbacks: [],
addCallback: function (callback) { this.callbacks.push(callback); },
setValue: function (value) {
this.value = value;
this.callbacks.forEach(
callback => callback(this.value)
);
}
};
function Component = () => {
const [val, setVal] = useState(outside.value);
useEffect(() => {
outside.addCallback((value) => setVal(value));
}, []);
return <p>{val}</p>
}
It is possible. You could pass your setCount function as a parameter to use it in your JS outside of React - but I would not really recommend this.
I would recommend that you keep your business logic and React logic separate.
The only things that need to be aware of state and will be using it are React components themselves. I would structure your code in a way that it is not coupled to React, and does not have to use or depend on React state in any way.
This is easier said than done at the beginning. If you need help with it, maybe provide a use case that you are trying to solve in this way, and a better answer might be provided.
It can be done by extending Example so it will pass a reference to the setCount function back to the parent code, see below. (This might be the same as what Oli mentioned, if so then I had the same idea and made a working implementation before answering)
const { useState } = React;
// functionFromComponent will store the function from Example.
let functionFromComponent = undefined;
const setter = (someFn) => functionFromComponent = someFn;
const Example = ({ setFunction }) => { // take `setFunction` from props
const [count, setCount] = useState(0);
setFunction(setCount); // pass setCount to the parent code
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
ReactDOM.render(
<Example setFunction={setter} />,
document.getElementById('example-wrapper')
);
function buttonClicked() {
if (functionFromComponent) {
functionFromComponent(777);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="example-wrapper"></div>
<button id="regularButton" onclick="buttonClicked()">Regular button</button>

Use ref inside React.memo

React.memo can be used to control whether a React function component should update or not. It compares by props by default. Now I want to compare a prop with one of the ref inside the function component. To be more specific, here is the code(Written in Typescript):
interface RichEditorProps {
value: string;
onChange: (value: string) => void;
}
export const RichEditor = React.memo((props: RichEditorProps) => {
const contentEditableDiv = useRef<HTMLDivElement>(null);
useEffect(() => {
contentEditableDiv.current!.addEventListener("input", () => {
props.onChange(contentEditableDiv.current!.innerHTML);
});
});
return (
<div contentEditable suppressContentEditableWarning ref={contentEditableDiv}>
{props.value}
</div>
)
}, ((prevProps, nextProps) => {
// I want to do this, but it doesn't work because contentEditableDiv is not available here.
return nextProps.value !== contentEditableDiv.current!.innerHTML;
}));
As you can see, I want to update the component when contentEditableDiv.current!.innerHTML doesn't equal to nextProps.value, but I cannot do it because contentEditableDiv is not available there. Is there any other way to achieve this without turning it into a class Component?
P.S. One way to achieve this may be to give the div an id and use document.getElementById to grab the div component inside the compare function. But I want to avoid this approach since in this way, I have to think of a method to assign a unique id to it, I haven't found an easy way to do it. And I'm curious if there's any other way to do it.

Is this a valid way of updating state that depends on prevState?

I am following along with a video tutorial on using React. The presenter is currently detailing how to add a toggle button to a UI. They said to give it a go first before seeing how they do it, so I implemented it myself. My implementation was a little different to theirs, just the handler was different; but it does seem to work.
Can anyone with more experience using React tell me, is my toggleSideDrawerHandler wrong in some way? Or is it a valid shorter way of setting the state that depends on a previous state?
My implementation:
//Layout.js
class Layout extends Component {
state = {
showSideDrawer: false
};
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
closeSideDrawerHandler = () => {
this.setState({ showSideDrawer: false });
};
render() {
return (
<Fragment>
<Toolbar drawerToggleClicked={this.toggleSideDrawerHandler} />
<SideDrawer
open={this.state.showSideDrawer}
close={this.closeSideDrawerHandler}
/>
<main className={styles.Content}>{this.props.children}</main>
</Fragment>
);
}
}
//Toolbar.js
const toolbar = props => (
<header className={styles.Toolbar}>
<DrawerToggle clicked={props.drawerToggleClicked} />
<div className={styles.Logo}>
<Logo />
</div>
<nav className={styles.DesktopOnly}>
<NavItems />
</nav>
</header>
);
Tutorial implementation:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
Your solution works, but I guess in the part, where you call the toggleSideDrawerHandler you probably call it like
() => this.toggleSideDrawerHandler(this.state)
right?
If not, can you please paste the rest of your code (especially the calling part) to see where you get the prevState from?
This works, because you pass the old state to the method.
I would personally prefer the tutorials implementation, because it takes care of dependencies and the "user" (the dev using it) doesn't need to know anything about the expected data.
With the second implementation all you need to do is call the function and not think about getting and passing the old state to it.
Update after adding the rest of the code:
I think the reason, why it works is because the default value for your parameter is the one passed by the event by default, which is an event object.
If you use prevState.showSideDrawer you are calling an unknown element on this event object, that will be null.
Now if you use !prevState.showSideDrawer, you are actually defining it as !null (inverted null/false), which will be true.
This is why it probably works.
Maybe try to toggle your code twice, by showing and hiding it again.
Showing it will probably work, but hiding it again will not.
This is why the other code is correct.
You should stick to the tutorial implementation. There is no point in passing component state to the children and then from them back to the parents. Your state should be only in one place (in this case in Layout).
Child components should be only given access to the information they need which in this case is just showSideDrawer.
You are using this:
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
This is a conventional way to update state in react, where we are defining the function and updating state inside. Though you are using term prevState but it doesn't holds any value of components states. When you call toggleSideDrawerHandler method you have to pass value and prevState will hold that value. The other case as tutorial is using:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
This is called functional setStae way of updating state. In this function is used in setState methods first argument. So prevState will have a value equal to all the states in the component.Check the example below to understand the difference between two:
// Example stateless functional component
const SFC = props => (
<div>{props.label}</div>
);
// Example class component
class Thingy extends React.Component {
constructor() {
super();
this.state = {
temp: [],
};
}
componentDidMount(){
this.setState({temp: this.state.temp.concat('a')})
this.setState({temp: this.state.temp.concat('b')})
this.setState({temp: this.state.temp.concat('c')})
this.setState({temp: this.state.temp.concat('d')})
this.setState(prevState => ({temp: prevState.temp.concat('e')}))
this.setState(prevState => ({temp: prevState.temp.concat('f')}))
this.setState(prevState => ({temp: prevState.temp.concat('g')}))
}
render() {
const {title} = this.props;
const {temp} = this.state;
return (
<div>
<div>{title}</div>
<SFC label="I'm the SFC inside the Thingy" />
{ temp.map(value => ( <div>Concating {value}</div> )) }
</div>
);
}
}
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
<div id="react"></div>
<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>
So depending on requirement you will use one of the two ways to update the state.

Categories