How to properly pass functions as props to child component in React? - javascript

I currently have a function that I want to pass to the a child that is two levels deep. My function is :
const addToTrip= (isChecked, coordinates, title, yelpID)=>{
console.log("added to trip ")
if(isChecked){
setWaypoints((waypoints)=>[...waypoints, {name:title, yelp_id:yelpID, coordinates:coordinates}])
} else if(!isChecked){
const newWaypoints = waypoints.filter((waypoint)=>waypoint.yelp_id !== yelpID)
setWaypoints(newWaypoints)
}
}
My function is used in my Businesses component and it is rendered under certain conditions
{hikes.length>0 && (
<Businesses
hikes = {hikes}
addToTrip = {addToTrip}
/>
)}
When I initially run my app and hikes is empty, I thought my function isnt supposed to be invoked until I call it, but it is being called because my waypoints state gets set twice and my console.log("added to trip ") triggers twice as well.
I tried to go about it with changing my render logic of Businesses to
{hikes.length>0 && (
<Businesses
hikes = {hikes}
// addToTrip = {addToTrip}
addToTrip = {()=>addToTrip}
/>
)}
With the above code, I dont actually get my console log statements and my waypoints state remain untouched. Now the issue with this is I cant seem to use this function in my child component properly like I was able to in my first approach. I know that I am calling the addToTrip function successfully as my console.log will print as expected, but it's not detecting my input parameters any more.
Here is my first child component:
export const Businesses = (props)=>{
const {hikes, addToTrip} = props
return(<>
<div className="businessesColumn">
{hikes.map(hike=>(
<BusinessCard ...
/>
)}
Here is my second child component:
export const BusinessCard = (props)=>{
const {img, title, location, description, star, reviewCount, addToTrip, coordinates, yelpID} = props;
const [isChecked, setIsChecked] = useState(false);
useEffect(()=>{
console.log(isChecked)
addToTrip(isChecked, coordinates, title, yelpID)
},[isChecked])
const handleOnChange = ()=>{
setIsChecked(!isChecked);
}
return (...)
Q1: How come my function is called even when I am not calling it when I am passing it as addToTrip = {addToTrip}? I know react creates the function on every render, unless I usecallback, but is it suppose to "call" it?
Q2: How am I able to pass parameters from child component if I were to pass function as props with addToTrip = {()=>addToTrip}
Q3: Does React create a new addToTrip object for each component it is passed to? Ive noticed that my addToTrip gets called for each component that requires it but doesnt even call it
I greatly appreciate any help.

Q1: How come my function is called even when I am not calling it when I am passing it as addToTrip = {addToTrip}? I know react creates the function on every render, unless I usecallback, but is it suppose to "call" it?
It actually does not trigger your function but passes down to child components only. In this context, you don't need to call useCallback, because your function is never modified.
Q2: How am I able to pass parameters from child component if I were to pass function as props with addToTrip = {()=>addToTrip}
addToTrip = {()=>addToTrip} is not a proper way to call your function. It should be
addToTrip = {()=>addToTrip()} (it will create a new function every time the component gets re-rendered). In this case, you can call useCalback to memoize your function
addToTrip = {addToTrip} (it won't create a new function but re-use the current function)
Q3: Does React create a new addToTrip object for each component it is passed to? Ive noticed that my addToTrip gets called for each component that requires it but doesnt even call it
The problem is from useEffect. When your component get rendered, useEffect will be triggered initially, and addToTrip(isChecked, coordinates, title, yelpID) will get called. For the fix, you should move addToTrip to handleOnChange which is only triggered when you tick on checkboxes
const handleOnChange = ()=>{
setIsChecked(!isChecked);
addToTrip(!isChecked, coordinates, title, yelpID);
}

I can tell in this case that it may not be extremely relevant. But, if you have a huge state tree logic where you would need the same state being passed down from a grandfather component (parent of a parent) to a grandchild (child of a child), then React has a context that helps React JS Context Docs.
For Q1, I believe addToTrip is accurate since you are passing down the function reference (as opposed to passing down something like addToTrip()).

Related

Alternative to extending a functional component

I have the following component where within the useEffect, I am calling some data reading related
functions meant to happen once on load.
The problem is, some of the prop data are not available at this stage (still undefined) like the prodData and index.
They are only available when I get into the Nested components like <NestedComponent1 />.
I wish to move this logic into the nested components which will resolve this issue.
But I do not want to repeat these code inside the useEffect for each component. Instead looking to write these 7 lines once maybe in a function
and just call it with the 3 NestedComponents.
Issue is that there is a higher order function wrapping here plus all the values like prodData and index is coming from Redux store.
I can't just move all these logic inside useEffect into a normal JS function and instead need a functional component for this.
And if I make a functional component to perform these operations, I can't call it in the useEffect for each of the NestedComponents.
Cos this is not valid syntax.
React.useEffect(() => {
<NewlyCreatedComponentWithReadingFunctionality />
}, []);
Thus my query is, is there a way I could write a functional component which has the data reading logic inside its useEffect.
And then extend this functional component for each of the functional components so that the useEffect would just fire
when each of these NestedComponents are called?
Doesn't seem to be possible to do this thus looking for alternatives.
This is the existing component where some of these prop values are undefined at this stage.
const MyComponent = ({
prodData,
index,
country,
highOrder: {
AHigherOrderComponent,
},
}) => {
// this is the logic which I am looking to write once and be
// repeatable for all the NestedComponent{1,2,3}s below.
React.useEffect(() => {
const [, code] = country.split('-');
const sampleData = prodData[index].sampleData = sampleData;
const period = prodData[index].period = period;
const indication = prodData[index].indication = indication;
AHigherOrderComponent(someReadDataFunction(code, sampleData));
AHigherOrderComponent(someReadDataFunction(code, period);
AHigherOrderComponent(someReadDataFunction(code, indication);
}, []);
return (
{/* other logics not relevant */}
<div>
<div>
<NestedComponent1 />
<NestedComponent2 />
<NestedComponent3 />
</div>
</div>
);
};
export default connect( // redux connect
({
country,
prodData,
index,
}) => ({
country,
prodData,
index,
})
)(withHighOrder(MyComponent));
React components implement a pattern called composition. There are a few ways to share state between parts of your React application but whenever you have to remember some global state and offer some shared functionality, I would try and manage that logic inside a context provider.
I would try the following:
Wrap all your mentioned components inside a context provider component
Offer the someReadDataFunction as a callback function as part of the context
Within your provider, manage react state, e.g. functionHasBeenCalled that remembers if someReadDataFunction has been called already
Set functionHasBeenCalled to true inside someReadDataFunction
Call someReadDataFunction inside your components within a useEffect based on the props data
This way, your application globally remembers if the function has been executed already but you can still use the latest data within your useEffect within your components to call someReadDataFunction.

need to pass an effect via props or force component reload from outside the root component in preact/react

I have a situation that an item outside the component might influence the item in the backend. ex: change the value of one of the properties that are persisted, let's say the item status moves from Pending to Completed.
I know when it happens but since it is outside of a component I need to tell to the component that it is out of sync and re-fetch the data. But from outside. I know you can pass props calling the render method again. But the problem is I have a reducer and the state will pick up the last state and if I use an prop to trigger an effect I get into a loop.
Here is what I did:
useEffect(() => {
if (props.effect && !state.effect) { //this runs when the prop changes
return dispatch({ type: props.effect, });
}
if (state.effect) { // but then I get here and then back up and so on
return ModelEffect[state.effect](state?.data?.item)}, [state.status, state.effect, props.effect,]);
In short since I can't get rid of the prop the I get the first one then the second and so on in an infinite loop.
I render the root component passing the id:
render(html`<${Panel} id=${id}/>`,
document.getElementById('Panel'));
Then the idea was that I could do this to sync it:
render(html`<${Panel} id=${id} effect="RELOAD"/>`,
document.getElementById('Panel'));
any better ways to solve this?
Thanks a lot
I resolved it by passing the initialized dispatch function to a global.
function Panel (props) {
//...
const [state, dispatch,] = useReducer(RequestReducer, initialData);
//...
useEffect(() => {
//...
window.GlobalDispatch = dispatch;
//...
}, [state.status, state.effect,]);
with that I can do:
window.GlobalDispatch({type:'RELOAD'});

react state function does it get created on every render

I understand why useCallback is good. It doesn't create new functions if deps didn't change.
Is the same thing true for state functions ?
for example:
// parent component
const [val, setVal] = useState<boolean>(false);
<childComponent
:setVal={setVal}
/>
Now if parent component re-renders for some other reason than val field, setVal function will be created again, which will cause childComponent to re-render again for no obvious reasons. Is this true ? and if so, I guess, there's no way to work around this ?
No it's fine to pass setVal. useState takes care of returning the same setVal instance on every render cycle.

Using Variable from Props vs Passing Prop as Argument in React

In terms of writing components, which would be the preferred way to write below component? Assume that removeCard is outside of shown scope, ie. redux action.
My assumption would be that ComponentCardB would be, as it avoids passing an unnecessary argument which would be in the scope anyway. I imagine in terms of performance in the grand scheme of things, the difference is negligible, just more of a query in regards to best practise.
TIA
const ComponentCardA = (id) => {
const handleRemove = (cardId) => {
removeCard(cardId);
};
<div onClick={() => handleRemove(id)} />;
};
const ComponentCardB = (id) => {
const handleRemove = () => {
removeCard(id);
};
<div onClick={handleRemove} />;
};
With functional components like that, yes, there's no reason for the extra layer of indirection in ComponentCardA vs ComponentCardB.
Slightly tangential, but related: Depending on what you're passing handleRemove to and whether your component has other props or state, you may want to memoize handleRemove via useCallback or useMemo. The reason is that if other props or state change, your component function will get called again and (with your existing code) will create a new handleRemove function and pass that to the child. That means that the child has to be updated or re-rendered. If the change was unrelated to id, that update/rerender is unnecessary.
But if the component just has id and no other props, there's no point, and if it's just passing it to an HTML element (as opposed to React component), there's also probably no point as updating that element's click handler is a very efficient operation.
The second option is better way because using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison.
Also if you don't want to use syntax with props.id you rather create function component with object as parameter:
const Component = ({id}) => { /* ... */ }
Of course using arrow function is also allowed but remember, when you don't have to use them then don't.

Cant change variable with callback function but console logging works

I am trying to change a variable in react with a callback function but cannot seem to do so. Here is my react component:
const MyComponent = () => {
let scenePinned;
const sceneCallback = event => {
if (event && event.state === 'DURING') {
console.log('Pinned');
scenePinned = true;
} else {
console.log('Not Pinned');
scenePinned = false;
}
};
console.log(scenePinned);
return (
<div>
<div style={scenePinned ? 'pinned' : 'not pinned'}/>
{(progress, event) => (
//Stuff Happens Here
), sceneCallback(event) )}
</div>
);
}
I am using react-scrollmagic and am trying to get the scenePinned variable to change from false to true and back to false again when scene is pinned to top. The console logging of Pinned and Not Pinned is happening correctly but I cannot seem to change the scenePinned variable. I am sure this is something very basic that I am not getting but I cannot understand why this is happening. Any help would be appreciated.
Note: I have tried using state to store the value but the callback is fired on scroll so the maximum depth is exceeded when trying to use state to store the scrolling status.
You need to use state for this. Otherwise the variable is reinitialized every time the component is rendered, and the value is lost.
console.log(scenePinned);
will run for the first time when the page loads
with react we use state the handle dynamic values.
or use rxjs
or create your own object and set listeners on it. with some custom event
so ex. with state
state={scenePinned:null}
then inside render method console.log(this.state.scenePinned)
A possible solution is to define a state variable in a parent component that will pass it to <MyComponent> as a prop.
Them move the sceneCallback function to the parent component and pass it as a prop to <MyComponent>
An explanation on how to define such a callback exists in many places. Here is one: (mine... ;) https://stackoverflow.com/a/55555578/5532513

Categories