I have a custom react component as follows
const MyWidget = (props) => (
<div>
{props.options.map((option) => (
<div onClick={(evt) => { onSelect(evt); }}>option</div>
))}
</div>
);
and I want to pass the index of the div to the parent onSelect. The way I have been doing this in the past is:
const MyWidget = (props) => (
<div>
{props.options.map((option) => (
<div onClick={(evt) => { onSelect(option, evt); }}>option</div>
))}
</div>
);
but my widgets would behave more like native widgets if I did
const MyWidget = (props) => (
<div>
{props.options.map((option) => (
<div onClick={(evt) => {
evt.selectedOption = option;
onSelect(evt);
}}>option</div>
))}
</div>
);
so parents can register callbacks in the same way they would if they were using a native element. Are there any reasons why doing this is a bad idea? I guess name clashes would be one, but maybe this is mitigated by the tree structure, where it is the parent's responsibility to know what names are used in child events. Also, are Synthetic Events something that can be relied upon or are they an implementation feature that will change from time to time?
Related
Is there any way in React to preserve the identity of an element when it moves between different parents?
For example:
const MyComponent = () => {
return (
<div>
{condition() ? (
<Parent1><Child /></Parent1>
) : (
<Parent2><Child /></Parent2>
)}
</div>
);
});
In my case, Child contains a video element, and when the condition() changes mid-way through the video, it gets recreated, which causes hiccups in the playback. I would like it to continue playing smoothly.
I tried:
Memoizing all of the components - didn't help
Using a "key" prop - didn't help either, as the key only matters within the same parent
Passing the Child to the parents as a "children" prop ... didn't help either
There is nothing strange going on in the parents, it can even be something like:
const Parent1OrParent2 = ({children}) => {
return (
<div>
<span>Hello I am parent 1 or parent 2</span>
{children}
</div>
);
});
Is it even possible to preserve the element at all?
the only way to do that is to pre-initialize children so that it is a:
const MyComponent = () => {
const child = <Child />
return (
<div>
{condition() ? (
<Parent1>{child}</Parent1>
) : (
<Parent2>{child}</Parent2>
)}
</div>
);
});
Demo: https://playcode.io/1190983
I have a component which has child components, i want to render these child components with different Ids. They are getting their data from store.The problem is they are rendered but with the same item. how can this be solved?
MultiImages Component
const MultiImages: () => JSX.Element = () => {
const values = ['500', '406', '614'];
return (
<div>
{values.map((val, index) => {
return <OneImage key={index} projectID={val} />;
})}
</div>
);
};
export default MultiImages;
OneImage Component
const OneImage: () => JSX.Element = ({ projectID }) => {
const projectData = useProjectDataStore();
const { getProject } = useAction();
useEffect(() => {
getProject(projectID ?? '');
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
Your issue here - you are calling in a loop, one by one fetch your projects, and each call, as far as we can understand from your example and comments override each other.
Your are doing it implicitly, cause your fetching functionality is inside your Item Component OneImage
In general, the way you are using global state and trying to isolate one from another nodes is nice, you need to think about your selector hook.
I suggest you, to prevent rewriting too many parts of the code, to change a bit your selector "useProjectDataStore" and make it depended on "projectID".
Each load of next project with getProject might store into your global state result, but instead of overriding ALL the state object, you might want to use Map(Dictionary) as a data structure, and write a result there and use projectID as a key.
So, in your code the only place what might be change is OneImage component
const OneImage: () => JSX.Element = ({ projectID }) => {
// making your hook depended on **projectID**
const projectData = useProjectDataStore(projectID);
const { getProject } = useAction();
useEffect(() => {
// No need of usage **projectID** cause it will inherit if from useProjectDataStore
getProject();
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
And inside of your useProjectDataStore store result into a specific key using projectID.
Your component OneImage will return what's in the return statement, in your case:
<>
<div>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
This tag <></> around your element is a React.fragment and has no key. This is the reason you get this error.
Since you already have a div tag wrapping your element you can do this:
<div key={parseInt(projectID)}>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
You can also change the key to Math.floor(Math.random() * 9999).
Note that passing the prop key={index} is unnecessary, and is not advised to use index as keys in a react list.
I want to get a ref, more specifically a getBoundingClientRect() on the <Header/> and <Testimonials/> component. I then want to watch for a scroll event and check if the two components ever overlap. Currently, my overlap variable never flips to true even if what appears on the page is that the two components are overlaping.
const [isIntersecting, setIsIntersecting] = useState(false)
const header = useRef(null)
const testimonials = useRef(null)
const scrollHandler = _ => {
let headerRect = header.current.getBoundingClientRect();
let testiRect = testimonials.current.getBoundingClientRect();
let overlap = !(headerRect.right < testiRect.left ||
headerRect.left > testiRect.right ||
headerRect.bottom < testiRect.top ||
headerRect.top > testiRect.bottom)
console.log(overlap) // never flips to true
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
const App = () => {
return (
<div className="App">
<Header />
<LandingPage />
<div style={{ height: '100vh', backgroundColor: 'black', color: 'white' }}>
</div>
<AboutPage />
<TestimonialsPage />
<Footer />
</div>
);
}
First: Components can't receive directly a ref prop, unless you are wrapping the Component itself in a React.forwardRef wrapper:
const Component = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// Inside your Parent Component:
const ref = useRef();
<Component ref={ref}>Click me!</Component>;
Second: you can also pass a ref down to a child as a standard prop, but you can't call that prop ref since that's a special reserved word just like the key prop:
const Component= (props) => (
<button ref={props.myRef}>
{props.children}
</button>
);
// Inside your Parent Component
const ref = useRef();
<Component myRef={ref}>Click me!</Component>;
This works perfectly fine, and if it's a your personal project you
might work like this with no issues, the only downside is that you
have to use custom prop name for those refs, so the code gets harder to
read and to mantain, especially if it's a shared repo.
Third: Now that you learnt how to gain access to the DOM node of a child Component from its parent, you must know that even if usually it's safe to perform manipulations on those nodes inside a useEffect ( or a componentDidMount ) since they are executed once the DOM has rendered, to be 100% sure you will have access to the right DOM node it's always better using a callback as a ref like this:
const handleRef = (node) => {
if (node) //do something with node
};
<Component ref={handleRef}/>
Basically your function hanldeRef will be called by React during
DOM node render by passing the node itself as its first parameter,
this way you can perform a safe check on the node, and be sure it's
100% valorized when you are going to perform your DOM manipulation.
Concerning your specific question about how to access the getBoundingClientRect of a child Component DOM node, I made a working example with both the approaches:
https://stackblitz.com/edit/react-pqujuz
You'll need to define each of your components as Forwarding Refs, eg
const Header = forwardRef<HTMLElement>((_, ref) => (
<header ref={ref}>
<h1>I am the header</h1>
</header>
));
You can then pass a HTMLElement ref to your components to refer to later
const headerRef = useRef<HTMLElement>(null);
const scrollHandler = () => {
console.log("header position", headerRef.current?.getBoundingClientRect());
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
return (
<Header ref={headerRef} />
);
I'm using TypeScript examples since it's easier to translate back down to JS than it is to go up to TS
I have a functional component that has one function within it, renderMessages.
const MessageContainer = (props) => {
const renderMessages = () => {
return props.messages.map((message, index) => {
return(
<Message
key={index}
username={message.username}
message={message.message}
fromCurrentUser={message.fromCurrentUser}
/>);
})
}
return(
<div className='messages'>
{renderMessages()}
</div>
)
}
However, I realized that instead of wrapping renderMessages function on the map, I can just have:
const renderMessages = props.messages.map((message, index) => {
return(
<Message
key={index}
username={message.username}
message={message.message}
fromCurrentUser={message.fromCurrentUser}
/>);
})
}
And as a result, my final return would just contain
return(
<div className='messages'>
{renderMessages}
</div>
)
In a class-based component and within a render function, I'd use the last of the two. Which of the two is considered the best practice when using functional components, and why?
EDIT:
Which of the two is considered the best practice when using functional components, and why?
Best practices change with context - e.g. the team you're working on - so this is an opinion-based question out of the gate.
That being said, in my opinion, I wouldn't do either. I'd do (and I do) this:
const MessageContainer = (props) => {
return (
<div className='messages'>
{props.messages.map((message, index) => (
<Message
key={index}
username={message.username}
message={message.message}
fromCurrentUser={message.fromCurrentUser}
/>
))}
</div>
)
}
What's the purpose of the extra variable anyway?
While you're at it, don't use indexes for keys
The dirty secret about all those extra methods you stuck on your class components that encapsulated rendering logic is that they were an anti-pattern - those methods were, in fact, components.
EDIT #2
As pointed out in the other answer, the most performant solution for this specific use case is specifying the map function outside the functional component:
const renderMessage = (message,index) => (
<Message
key={index}
{...message}
/>
)
const MessageContainer = (props) => {
return (
<div classname='messages'>
{props.messages.map(renderMessage)}
</div>
);
}
But, you shouldn't prematurely optimize and I would advocate for the original solution I posted purely for simplicity/readability (but, to each their own).
Good job separating the mapping outside the component's return, because this way you'll be only calling the same function over and over again untill the .map is done iterating, but if you wrote it in the component's return, every time the .map iterate over the next item you'll be creating a new function.
Regarding the question, I'd recommend the second way, clean/readable code is always preferable.
P.S. try to use the unique message id instead of the index.
I want to bundle some data together with a component. Here is an example of a SFC that has a property called name. I do not want to use the property name with the component named MyFormTab. Instead I would like to access this property from the parent component and assign it to be displayed within the parent.
const MyFormTab = (props) => {
const name = props.name
return (
<>
<div className='flex-center-col'>
<input type='email'></input>
<input type='text'></input>
</div>
</>
)
}
I would then like to render this component inside a parent and use the name property for another purpose
const ParentOfMyFormTab = () => {
const [currentTab, setCurrentTab] = useState(1)
const Tab1 = <MyFormTab name='Tab1' />
const Tab2 = <MyFormTab name='Tab2' />
return (
<form>
<div id="tabTitles">
<h2 onClick={setCurrentTab(1)}>Tab1.name</h2>
<h2 onClick={setCurrentTab(2)}>Tab2.name</h2>
</div>
{currentTab === 1 ? <Tab1 /> : <Tab2 />}
</form>
)
}
Instead of an SFC, I could also use a class I'm thinking.
class MyFormTab {
constructor(name){
this.name = name
}
render(){
return (
<>
<div className='flex-center-col'>
<input type='email'></input>
<input type='email'></input>
</div>
</>
)
}
}
My project is predominantly using hooks however. My team lead(who doesn't know React much) will probably be hesitant towards mixing class components with hooks. I've read on other posts that hooks can basically replace class components in most situations. I don't know how hooks could be better, or even be used in this situation.
What do you think would be a good way to do what I am trying to do? Is putting SFC's with hooks and class components into the same project a good idea? Am I looking at this whole thing wrong?
Thank you
In react props are passed only from parent to child. So you can just have a parent with that name value and passed it down if you want to.
Edited my answer to respond to you edit.
const MyFormTab = (props) => {
const name = props.name
return (
<>
<div className='flex-center-col'>
<input type='email'></input>
<input type='text'></input>
</div>
</>
)
}
const ParentOfMyFormTab = () => {
const [currentTab, setCurrentTab] = useState(1)
const Tab1 = <MyFormTab name=`Tab1` />
const Tab2 = <MyFormTab name=`Tab2` />
return (
<form>
<div id="tabTitles">
<h2 onClick={setCurrentTab(1)}>Tab1</h2>
<h2 onClick={setCurrentTab(2)}>Tab2</h2>
</div>
{currentTab === 1 ? <Tab1 /> : <Tab2 />}
</form>
)
}
To you question about mixing class based and function components. You can't use hooks with class based components so don't, and there is no need to. I think you should learn more about the basics of react. If you need to share data with other components, the data should be in the parent component, passed to children or in a React context.