Detect Function If Being Called In Another Component in React - javascript

I have this function called handleSelectProduct in ProductComponent and I wanted to detect it in ProductDetailsComponent. If handleSelectProduct is called in ProductComponent, then I want to run a certain function in ProductDetailsComponent using useEffect.
ProductComponent
const ProductComponent = () => {
const [selectedProduct, setProduct] = useState(null);
const handleSelectProduct = (event) => {
setProduct(event.target.value);
};
return (
<div>
<Select
value={selectedProduct}
onChange={(e) => handleSelectProduct(e)}
>
{(products || []).map(({ id, name }) => (
<MenuItem value={id}>{name}</MenuItem>
))}
</Select>
<ProductDetailsComponent handleSelectProduct={handleSelectProduct} />
</div>
);
};
export default ProductComponent;
ProductDetailsComponent
const ProductDetailsComponent = ({ handleSelectProduct }) => {
useEffect(() => {
handleSelectProduct ? formik.setFieldValue("productInfo", "") : null;
}, [handleSelectProduct]);
};
export default ProductDetailsComponent;

If handleSelectProduct is called in ProductComponent, then I want to run a certain function in ProductDetailsComponent
I don't think that's exactly what you really want. If the components were that tightly coupled, you should use just a single component. The ProductDetailsComponent behaviour should depend only on its props and its internal state.
What you really want is to call a certain function in ProductDetailsComponent whenever the selected product changes. You can do that, as you already noticed, using useEffect - but with the product as a dependency. You just have to pass the selectedProduct as a prop to the component instead of the handleSelectProduct function.
const ProductComponent = () => {
const [selectedProduct, setProduct] = useState(null);
return (
…
<ProductDetailsComponent selectedProduct={selectedProduct} />
);
};
const ProductDetailsComponent = ({ selectedProduct }) => {
useEffect(() => {
if (selectedProduct) formik.setFieldValue("productInfo", "");
}, [selectedProduct]);
};

If the child component wants to know if/when a function in the parent component was called then the parent should pass down a prop to the child informing it of the condition. You may need to also pass a function to the child for the child to "acknowledge" it "saw" the function was called so the parent can "reset".
const ProductComponent = () => {
const [triggered, setTriggered] = React.useState(false);
const handleSelectProduct = (event) => {
setTriggered(true);
};
const reset = () => {
console.log("Parent trigger was reset");
setTriggered(false);
};
return (
<div>
Parent
<button disabled={triggered} type="button" onClick={handleSelectProduct}>
Trigger?
</button>
<ProductDetailsComponent triggered={triggered} reset={reset} />
</div>
);
};
const ProductDetailsComponent = ({ triggered, reset }) => {
React.useEffect(() => {
if (triggered) {
console.log("Child saw fn triggered in parent");
setTimeout(reset, 1000);
}
}, [reset, triggered]);
return <div>Child</div>;
};
ReactDOM.render(
<ProductComponent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />
An alternative could also be a simple function invocation count that is incremented, then the child need only see the count was incremented.
const ProductComponent = () => {
const [triggered, setTriggered] = React.useState(0);
const handleSelectProduct = (event) => {
setTriggered(c => c + 1);
};
return (
<div>
Parent
<button type="button" onClick={handleSelectProduct}>
Trigger?
</button>
<ProductDetailsComponent triggered={triggered} />
</div>
);
};
const ProductDetailsComponent = ({ triggered }) => {
React.useEffect(() => {
if (triggered) {
console.log("Child saw fn triggered in parent");
}
}, [triggered]);
return <div>Child</div>;
};
ReactDOM.render(
<ProductComponent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />

Related

React memo creates rerender

I'm having an issue with react memo when using nextjs. In the _app e.g. I have a button imported:
import { ChildComponent } from './ChildComponent';
export const Button = ({ classN }: { classN?: string }) => {
const [counter, setCounter] = useState(1);
const Parent = () => {
<button onClick={() => setCounter(counter + 1)}>Click me</button>
}
return (
<div>
{counter}
<Parent />
<ChildComponent />
</div>
);
};
Child component:
import React from 'react';
export const ChildComponent = React.memo(
() => {
React.useEffect(() => {
console.log('rerender child component');
}, []);
return <p>Prevent rerender</p>;
},
() => false
);
I made one working in React couldn't figure it out in my own app:
https://codesandbox.io/s/objective-goldwasser-83vb4?file=/src/ChildComponent.js
The second argument of React.memo() must be a function that returns true if the component don't need to be rerendered and false otherwise - or in the original definition, if the old props and the new props are equal or not.
So, in your code, the solution should be just change the second argument to:
export const ChildComponent = React.memo(
() => { ... },
// this
() => true
);
Which is gonna tell React that "the props didn't change and thus don't need to rerender this component".
So my issue was that I made a function called Button and returned inside a button or Link. So I had a mouseEnter inside the button which would update the state and handle the function outside the function. Kinda embarrassing. This fixed it. So the only change was I moved usestate and handlemousehover inside the button function.
const Button = () => {
const [hover, setHover] = useState(false);
const handleMouseHover = (e: React.MouseEvent<HTMLElement>) => {
if (e.type === 'mouseenter') {
setHover(true);
} else if (e.type === 'mouseleave') setHover(false);
};
return (
<StyledPrimaryButton
onMouseEnter={(e) => handleMouseHover(e)}
onMouseLeave={(e) => handleMouseHover(e)}
>
<StyledTypography
tag="span"
size="typo-20"
>
{title}
</StyledTypography>
<ChildComponent />
</StyledPrimaryButton>
);
};

why is React button not calling function on click

when I changed the disabled of button from true to false, it messed my onClick, why is that, and how to solve it?
const Component = () => {
const refrence = React.useRef(null)
setTimeout(() => {
refrence.current.disabled = false
}, 1000);
const handleClick = () => console.log('clicked')
return (
<div>
<button onClick={handleClick} ref={refrence} disabled>click me</button>
</div>
)
}
ReactDOM.render(
<Component />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script><div id="root"></div>
Issue
The issue here is that the React ref is mutating the DOM, but React isn't made aware of this and so Component isn't rerendered with a working button allowing the onClick event to go through. This is the main reason why direct DOM manipulations are considered anti-pattern in React.
Additionally, you are setting a timeout to occur every render, though this would have minimal effect it's still unnecessary and would be considered an unintentional side-effect.
Solution
Use component state and update state in a mounting useEffect hook to trigger a rerender.
const Component = () => {
const [isDisabled, setIsDisabled] = useState(true);
useEffect(() => {
setTimeout(() => {
setIsDisabled(false);
}, 1000);
}, []);
const handleClick = () => console.log('clicked');
return (
<div>
<button onClick={handleClick} disabled={isDisabled}>click me</button>
</div>
)
}
const Component = () => {
const [isDisabled, setIsDisabled] = React.useState(true);
React.useEffect(() => {
setTimeout(() => {
setIsDisabled(false);
}, 1000);
}, []);
const handleClick = () => console.log('clicked');
return (
<div>
<button onClick={handleClick} disabled={isDisabled}>click me</button>
</div>
)
};
ReactDOM.render(
<Component />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

What approach can I use to wait for all child callbacks to complete?

I'm trying to figure out an architecture to allow a parent component, to wait for all the child components to finish rendering and then do some work. Unfortunately in this case I need to do the rendering outside of React and it's done asynchronously instead.
This makes things a little complex. So in my example I want the doSomethingAfterRender() function in the ParentComponent to be called once, after all the ChildComponent customRender calls have completed.
I do have one potential solution, though it doesn't feel very clean which is to use a debounce on the doSomethingAfterRender() function. I'd much rather use a more deterministic approach to only calling this function once if possible.
I'm wondering if anyone has a better suggestion for handling this?
ParentComponent.js
const Parent = (props) => {
// This is the function I need to call
const doSomethingAfterRender = useCallback(
async (params) => {
await doSomething();
},
);
// Extend the child components to provide the doSomethingAfterRender callback down
const childrenWithProps = React.Children.map(props.children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { doSomethingAfterRender });
}
return child;
});
return (
<React.Fragment>
<...someDOM....>
{childrenWithProps}
</React.Fragment>
);
}
ChildComponent.js (this is actually a HoC)
const withXYZ = (WrappedComponent) =>
({ doSomethingAfterRender, ...props }) => {
// I need to wait for this function to complete, on all child components
const doRender = useCallback(
async () => {
await customRender();
// Call the doSomething...
if (doSomethingAfterRender) {
doSomethingAfterRender();
}
},
[doSomethingAfterRender]
);
return (
<React.Fragment>
<... some DOM ...>
<WrappedComponent {...props} renderLoop={renderLoop} layer={layer} />
</React.Fragment>
);
};
App.js
const Child = withXYZ(CustomWrappedComponent);
const App = () => {
return {
<ParentComponent>
<Child />
<Child />
<Child />
</ParentComponent>
};
}
If I understood correctly. I would do something like: useState with useRef.
This way I would trigger only once the Parent and that is when all Child components have finished with their respective async tasks.
Child.js
const child = ({ childRef, updateParent }) => {
const [text, setText] = useState("Not Set");
useEffect(() => {
if (typeof childRef.current !== "boolean") {
const display = (childRef.current += 1);
setTimeout(() => {
setText(`Child Now ${display}`);
if (childRef.current === 2) {
updateParent(true);
childRef.current = false;
}
}, 3000);
}
}, []);
return (
<>
<div>Test {text}</div>
</>
);
}
const Parent = ()=>{
const childRef = useRef(0);
const [shouldUpdate, setShouldUpdate] = useState(false);
useEffect(() => {
if (shouldUpdate) {
console.log("updating");
}
}, [shouldUpdate]);
return (
<div className="App">
{childRef.current}
<Child childRef={childRef} updateParent={setShouldUpdate} />
<Child childRef={childRef} updateParent={setShouldUpdate} />
<Child childRef={childRef} updateParent={setShouldUpdate} />
</div>
);
}

Passing data to sibling components with react hooks?

I want to pass a variable username from sibling1 component to sibling2 component and display it there.
Sibling1 component:
const sibling1 = ({ usernameData }) => {
// I want to pass the username value I get from input to sibling2 component
const [username, setUsername] = useState("");
const handleChange = event => {
setUsername(event.target.value);
};
return (
<Form.Input
icon='user'
iconPosition='left'
label='Username'
onChange={handleChange}
/>
<Button content='Login' onClick={handleClick} />
)
}
export default sibling1;
Sibling2 component:
export default function sibling2() {
return (
<h1> Here is where i want to display it </h1>
)
}
You will need to handle your userName in the parent of your siblings. then you can just pass setUsername to your sibling1, and userName to your sibling2. When sibling1 use setUsername, it will update your parent state and re-render your sibling2 (Because the prop is edited).
Here what it looks like :
const App = () => {
const [username, setUsername] = useState('Default username');
return (
<>
<Sibling1 setUsername={setUsername} />
<Sibling2 username={username} />
</>
)
}
const Sibling2 = ({username}) => {
return <h1> Helo {username}</h1>;
}
const Sibling1 = ({setUsername}) => {
return <button onClick={setUsername}>Set username</button>;
}
In parent of these two components create a context where you will store a value and value setter (the best would be from useState). So, it will look like this:
export const Context = React.createContext({ value: null, setValue: () => {} });
export const ParentComponent = () => {
const [value, setValue] = useState(null);
return (
<Context.Provider value={{value, setValue}}>
<Sibling1 />
<Sibling2 />
</Context.Provider>
);
Then in siblings you are using it like this:
const Sibling1 = () => {
const {setValue} = useContext(Context);
const handleChange = event => {
setValue(event.target.value);
};
// rest of code here
}
const Sibling2 = () => {
const {value} = useContext(Context);
return <h1>{value}</h1>;
}
best way: React Context + hooks
you can use React Context. take a look at this example:
https://codesandbox.io/s/react-context-api-example-0ghhy

Not able to call the the method of the child component in react

I have following components .
this component accept props which is function that can be called.
deleteIcon = () => {
console.log("deleting the document");
}
export const Parent = (props) => {
return (
<button onclick={() => {deleteIcon()}}
)
}
Now, I have some another component which uses this component. which has its own implementation of the deleteIcon method.
deletechildIcon = () => {
}
export const child = () => {
return (
<Parent deleteIcon={() => {deletechildIcon()}} />
)
}
So, from child still it is calling the parent method and not the child one. can any one help me with this ?
Some notice points:
onClick rather than onclick
no need to use arrow function inside props, which may cause performance loss and it's not the best practice
write your functions inside the component
Child component is the one which been called inside the Parent, you made it the opposite
Try the demo in-text:
const Parent = () => {
const deleteIcon = () => {
console.log("deleting the document");
};
return <Child deleteIcon={deleteIcon} />;
};
const Child = props => {
return <button onClick={props.deleteIcon}>XXX</button>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
You are calling the deleteIcon method in your Parent component, not the props.deleteIcon
It sounds like you want Parent to have a default deleteIcon prop that can be optionally overridden in specific implementations. You could do that by editing Parent like so:
deleteIcon = () => {
console.log("deleting the document");
}
export const Parent = (props) => {
const buttonOnClickHandler = props.deleteIcon || deleteIcon;
return (
<button onClick={() => {buttonOnClickHandler()}}
)
}
Or you could use default arguments:
deleteIconDefault = () => {
console.log("deleting the document");
}
export const Parent = ({
deleteIcon = deleteIconDefault
}) => {;
return (
<button onClick={() => {this.props.deleteIcon()}}
)
}
Hope this points you in the right direction!

Categories

Resources