how to add components into reusuable component? react - reactjs

I'm not sure my title make sense :/ sorry for my poor description.
anyway what I was trying was.. making a reusuable component.
and the props of the reusuable component is another component.
here's what I did:
const Accordion = ({ title, content }) => {
return (
<Wrapper>
<Title>{title}</Title>
<Content>{content}</Content>
</Wrapper>
);
};
const ParentComponent = () => {
const title = () => <div>title</div>;
const content = () => {
return (<div><h2>...text...</h2><div>...text..</div></div>)
}
return <Accordion title={title} content={content} />;
};
it seems nice to me, but it does not work 🤯
the title and the content(...text...) was not showing at all.
it works in this way though, this is not what I want 🤯🤯
<Accordion title='title text' content='content context' />;
thanks for your help.

Just do this:
You need to render it like <element />
const Accordion = ({ title, content }) => {
return (
<Wrapper>
<Title><title /></Title>
<Content><content /></Content>
</Wrapper>
);
};

Related

How to create a portal to a child component?

I would like to create a portal to insert a content in a child component but I can't make it work. I tried to use a callback ref as shown on this answer but it doesn't work (the content of the portal is not displayed). Here is a simplified example of what I have:
const Parent = () => (
<Child
ref={(el) => {
if (!el) return;
const selector = el.querySelector(".title");
createPortal("My title", selector);
}}
/>
);
const Child = forwardRef((_, ref) => (
<div ref={ref}>
<h2 className="title"></h2>
<p>Test content</p>
</div>
));
I also tried with useRef(), but to no avail.
const Parent = () => {
const childRef = useRef();
const renderTitle = () => {
const selector = childRef.current.querySelector(".title");
return createPortal("My title", selector);
}
return (
<Child ref={childRef}>
{childRef.current && renderTitle()}
</Child>
);
}
Is there a way to do this?
I know the obvious solution is to pass the title as a prop but the title prop of my real <Child /> component displays a simple text and I need a different display. I don't want to change <Child /> as it can be used without <Parent/> and I use the latter to deal with other extra functionalities, which include building a custom title dynamically.
Here is a CodeSandbox, if you want to try.
Portals are not for these purposes. You can just pass Component to the child component. Or use the function to generate that name.
const Parent = () => (
<Child
titleComponent={<TitleComponent />}
/>
);
const Child = ((titleComponent) => {
return (
<div ref={ref}>
<h2 className="title">{titleComponent}</h2>
<p>Test content</p>
</div>
)
});
I managed to make it work by storing the selector in a state using useEffect:
const Parent = () => {
const childRef = useRef();
const [titleContainer, setTitleContainer] = useState(null);
useEffect(() => {
const titleContainerSelector = childRef.current.querySelector(".title");
setTitleContainer(titleContainerSelector);
}, []);
return (
<Child ref={childRef}>
{titleContainer && createPortal("My title", titleContainer)}
</Child>
);
}
It doesn't work when I try this on CodeSandbox but it works on my real components. I'm not sure why. For the record, here is the answer that gave me the solution.

add prop value and property to all children

I want to pass a prop value to all of a components current children, however I feel it's a little tedious to do it to each and every single child component (especially if you have a considerable amount). I initially thought a context would suffice, but I came across the same issue of adding a considerable amount of boilerplate code that could otherwise be extremely simple.
Is there anything within React that could achieve the same affect as:
<Parent>
<ChildOne propForAllChildren={this.state.example} />
<ChildTwo propForAllChildren={this.state.example} />
<ChildThree propForAllChildren={this.state.example} />
</Parent>
With something like this:
<Parent passPropsToChildren={{"propForAllChildren": this.state.example}}>
<ChildOne />
<ChildTwo />
<ChildThree />
</Parent>
There's nothing in the documentation which I can find, and I could very easily create a component which would do exactly this. But, why reinvent the wheel if it already exists?
In this case I usually look the problem on children side. Let me explain better: you know that all Child will have a prop called propForAllChildren and most probably this prop is always at the same value. Well, on Child put a default value for that prop so you could avoid to pass it:
<Parent>
<ChildOne />
<ChildTwo />
<ChildThree />
</Parent>
export default function ChildOne(props) {
const { propForAllChildren = true } = props;
...
}
And the same thing for the other Child.
Yes, this line const { propForAllChildren = true } = props; will be the bolierplate but I think is a minimum price to pay.
hi this is the solution
import React,{useState , useEffect} from 'react';
const ChildOne = (props) => {
console.log(props);
return(
<p> ChildOne </p>
)
}
const ChildTwo = (props) => {
console.log(props);
return(
<p> ChildTwo </p>
)
}
const ChildThre = (props) => {
console.log(props);
return(
<p> ChildThre </p>
)
}
const Parent = ({ children,passPropsToChildren }) => {
const [ propedChilds , setPropedChilds ] = useState([]);
useEffect(() => {
if(passPropsToChildren){
const childrenWithProps = React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, {
...passPropsToChildren
});
}
return child;
});
setPropedChilds(childrenWithProps);
}
}, [children,passPropsToChildren]);
return(
<>
{propedChilds}
</>
)
}
const App = () => {
return(
<Parent passPropsToChildren={{ "propForAllChildren": true }}>
<ChildOne />
<ChildTwo />
<ChildThre />
</Parent >
)
}
As it stands, there seems to be no official iterable prop component such as the one defined. So I went to my own devices to create a very basic implementation of one - just in case anyone deems it useful - feel free to use or alter.
Hopefully one day the React team will add similar functionality to a future version of React.
const PropsToChildren = React.memo(({ props, children }) => (
<Fragment>
{ children.map((child) => React.cloneElement(child, {...props})) }
</Fragment>
));
// it can be used as such
<PropsToChildren props={{propA: propAValue}}>
{ /* children go here... */ }
</PropsToChildren>
// and it will output the following
<ChildOne propA={propAValue} />
<ChildTwo propA={propAValue} />
// etc...
You can create a HOC DispatchPropsToChildren to do this functionality. The purpose of this HOC component is to loop between children and injecting to them the props.
function DispatchPropsToChildren({ propsToDispatch, children }) {
return (
<>{children.map(child => cloneElement(child, { ...propsToDispatch }))}</>
);
}
How to use it:
Component App:
export default function App() {
return (
<Parent>
<DispatchPropsToChildren propsToDispatch={{ propOne: 1, propTwo: 2 }}>
<ChildOne />
<ChildTwo />
</DispatchPropsToChildren>
</Parent>
);
}
Component Parent:
function Parent({ children }) {
return children;
}
Component ChildOne:
function ChildOne(props) {
console.log(props);
return <div>ChildOne</div>;
}
Component ChildTwo:
function ChildTwo(props) {
console.log(props);
return <div>ChildTwo</div>;
}
You can check this demo:
https://stackblitz.com/edit/react-xqmevc

My event onClick in my map does not work. Very strange behavior

My Onclick on bestmovies map does not work. If I place it on a H1, for example, works. onClick={handleClickMovie}
// imports....
const Movies = () => {
const [popularMovies, setPopularMovies] = useState([])
const [bestMovies, setBestMovies] = useState([])
const [showPopUp, setShowPopUp] = useState(false)
const handleClickMovie = () => {
setShowPopUp(console.log('Clicked'))
}
useEffect(() => {
async function getMovies() {
const responsePopularMovies = await getPopularMovies()
setPopularMovies(responsePopularMovies.results)
const responseBestMovies = await getBestMovies()
setBestMovies(responseBestMovies.results)
}
getMovies()
}, [])
return (
<div>
<Wrapper>
{showPopUp ? <MoviePopUp /> : null}
<h1>Filmes Populares</h1>
<Content>
{popularMovies.map(item => (
<MovieItem movie={item} />
))}
</Content>
<h1>Filmes Bem Avaliados</h1>
<Content>
{bestMovies.map(item => (
<MovieItem movie={item} onClick={handleClickMovie} />
))}
</Content>
</Wrapper>
</div>
)
}
export default Movies
MovieItem.js
import React from 'react'
import { Cover, Score, Title } from './MovieItem.styles'
const MovieItems = ({ movie }) => {
return (
<Cover key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt="capas"
/>
<Score>{movie.vote_average}</Score>
<Title>{movie.title}</Title>
</Cover>
)
}
export default MovieItems
try wrapping in a div
<Content>
{bestMovies.map(item => (
<div onClick={handleClickMovie}>
<MovieItem movie={item} onClick={handleClickMovie} />
</div>
))}
</Content>
As #anthony_718 answered, you are calling onClick on a JSX component. JSX components aren't in the DOM and don't have click events (although they can render HTML elements if they contain them).
If you want, you can also pass the props all the way up to an actual html element the <Cover> renders.
#anthony_718's answer is correct.
The reason it didn't work it's because <MovieItem> doesn't have onClick in his props.
However, to facilitate reusability, you can modify your component like so:
const MovieItems = ({ movie, onClick }) => {
return (
<div onClick={onClick}>
<Cover key={movie.id}>
// ... rest of your stuff
</Cover>
</div>
)
}
export default MovieItems
It's essentially the same solution, but by placing <div onClick> within the component definition, you make it more reusable than the other option.
check this
bestMovies.map((item, i) => { return( <MovieItem movie={item} onClick={handleClickMovie} /> )})

How can I force update React.memo child component?

My main functional component performs a huge amount of useQueries and useMutations on the child component hence I have set it as React.memo so as to not cause re-rendering on each update. Basically, when new products are selected I still see the old products because of memo.
mainFunction.js
const [active, setActive] = useState(false);
const handleToggle = () => setActive(false);
const handleSelection = (resources) => {
const idsFromResources = resources.selection.map((product) => product.variants.map(variant => variant.id));
store.set('bulk-ids', idsFromResources); //loal storage js-store library
handleToggle
};
const emptyState = !store.get('bulk-ids'); // Checks local storage using js-store library
return (
<Page>
<TitleBar
title="Products"
primaryAction={{
content: 'Select products',
onAction: () => {
setActive(!active)
}
}}
/>
<ResourcePicker
resourceType="Product"
showVariants={true}
open={active}
onSelection={(resources) => handleSelection(resources)}
onCancel={handleToggle}
/>
<Button >Add Discount to Products</Button> //Apollo useMutation
{emptyState ? (
<Layout>
Select products to continue
</Layout>
) : (
<ChildComponent />
)}
</Page>
);
ChildComponent.js
class ChildComponent extends React {
return(
store.get(bulk-ids).map((product)=>{
<Query query={GET_VARIANTS} variables={{ id: variant }}>
{({ data, extensions, loading, error }) => {
<Layout>
// Query result UI
<Layout>
}}
</Query>
})
)
}
export deafult React.memo(ChildComponent);
React.memo() is useful when your component always renders the same way with no changes. In your case you need to re-render <ChildComponent> every time bulk-id changes. So you should use useMemo() hook.
function parentComponent() {
... rest of code
const bulkIds = store.get('bulk-ids');
const childComponentMemo = useMemo(() => <ChildComponent ids={bulkIds}/>, [bulkIds]);
return <Page>
... rest of render
{bulkIds ?
childComponentMemo
:(
<Layout>
Select products to continue
</Layout>
)}
</Page>
}
useMemo() returns the same value until buldIds has not changed. More details about useMemo() you can find here.

Is having a lot of render methods within a components bad practice?

I had written a component and there are some other instances of small components within that components. Is it a bad practice to use a renderxxx for each instance? thank you
It depends. If you are using some kind of logic for rendering your components, then you should go with dedicated renderX methods. But if all you are doing is to return the components, you should simply describe it with JSX.
Without logic
const MyComponent = ({ options }) => (
<div>
<SelectList options={options} />
</div>
);
With logic
const MyComponent = ({ options }) => {
const renderOptions = () => {
if (options.length < 5) {
return <RadioButtonGroup options={options} />;
}
return <SelectList options={options} />;
};
return (
<div>
{renderOptions()}
</div>
);
};
Another approch would be wrapping another component around every renderX method.
const OptionsRenderer = ({ options }) => {
if (options.length < 5) {
return <RadioButtonGroup options={options} />;
}
return <SelectList options={options} />;
};
const MyComponent = ({ options }) => (
<div>
<OptionsRenderer options={options} />
</div>
);

Resources