I have the following code `
import { useState, useEffect } from 'react'
import LayoutContent from './layout_content';
type Props = {
children: JSX.Element | JSX.Element[]
}
const Layout = ({ children }: Props) => {
const [selected, setSelected] = useState(countries[0]);
const country= selected.id
return (
<>
<Sidebar onClick={toggle} sidebar={open} />
<LayoutContent sidebar={open} countriesWithsrc ={countriesWithsrc} selected={selected} lected={setSelected} >
{children}
</LayoutContent>
</>
)
}
export default Layout;
`
How do I pass the variable country from the Layout component to the children without state management ?.I.e I want to drill it.
If you don't want any state management you can use React.Children. It provides utilities to work with the children prop. React.Children.map will run a method for every immediate child of the component. You can use cloneElement along with that to create a clone of your element by passing in extra properties. Infact you can even modify the children of an element you are cloning, but that is not the ask here.
Do note that context would be the better way to do it.
const Layout = ({ children }: Props) => {
....
....
const modifiedChildren = React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { testProp : 'test' });
}
return child;
});
....
....
return (
<>
<Sidebar onClick={toggle} sidebar={open} />
<LayoutContent sidebar={open} countriesWithsrc ={countriesWithsrc} selected={selected} lected={setSelected} >
{modifiedChildren}
</LayoutContent>
</>
)
}
Related
Hey I use bootstrap with React and I try figure out, how I can extend my component by passing className props deeper. In my atom component I have two files. First one with component declaration.
Breadcrumb.js
export const Breadcrumb = (props) => {
const { className } = props;
const classes = getClasses(className);
return (
<Link to={props.path} className={classes} {...props}>
{props.children}
</Link>
);
};
and another one with getClasses() which returns all default BS classes.
Breadcrumb.style.js
export const getClasses = (extra = "") => {
const defaultClasses = getDefaultClasses();
const addingClasses = extra;
const classes = `${defaultClasses} ${addingClasses}`;
return classes;
};
const getDefaultClasses = () => `ps-3 fs-3 fw-bold text-decoration-none`;
What I want to achieve is, when I'll invoke my Breadcrumb component, and I'll decied to extend it on extra classes I can do that by pass className props...like
TopBar.js
export const TopBar = () => {
const breadcrumbs = useBreadcrumbs(routes, { disableDefaults: true });
const classes = getClasses();
return (
<div className={classes}>
{breadcrumbs.map(({ match, breadcrumb }) => (
<Breadcrumb
path={match.pathname}
children={breadcrumb}
className="cs_breadcrumb"
key={uuidv4()}
/>
))}
</div>
);
};
But when I do that, my declare Breadcrumb className is override by invoke Breadcrumb className... Although in Breadcrumb.js console.log(classes) returns concated classes.
Anyone knows how to achieve that or has any tips ?? I'll be glad
Change
export const Breadcrumb = (props) => {
const { className } = props;
const classes = getClasses(className);
return (
<Link to={props.path} className={classes} {...props}>
{props.children}
</Link>
);
};
to
export const Breadcrumb = ({ className, ...rest }) => {
const classes = getClasses(className);
return (
<Link to={props.path} className={classes} {...rest}>
{props.children}
</Link>
);
};
So, you need to extract the className prop in the place where props was, and also add ...rest for the rest props.
I guess you want to extend component classes with other classes passed via props.
If I understand correctly, you can try like this:
export const Breadcrumb = (props) => {
const { className } = props;
const classes = getClasses(className);
return (
<Link to={props.path} className={[classes, className].join(" ")]}
{...props}>
{props.children}
</Link>
);
};
I'm having a trouble with passing props to children in functional react. My {children} is a Details component like below:
<SidebarPage>
{children}
</SidebarPage>
const Details = () => {}
how can I pass props in way that, I put props to {children} and I receive this props in Details component? Is this even workable?
Thanks for any help!
You can pass component as prop, something like -
const SidebarPage = ({ChildComponent}) => {
const props = { name: "X" };
return <ChildComponent {...props} />
}
const Details = (props) => {}
<SidebarPage ChildComponent={Details} />
You could use cloneElement to add new props to a component children
const newProps = {aProp: "aProp"} ;
return (
<SidebarPage>
{React.Children.map(props.children, child => {
return React.cloneElement(child, {
newProps,
}, null)
}
</SidebarPage>
)
If I had my component without HOC it did fire but now i wrapped my component inside withSpinner Hoc but it does not fire the fetching start.
const CollectionPage = (props) => {
const { isCollectionLoaded, isCollectionFetching } = props;
useEffect(() => {
props.fetchCollectionsStart();
}, []);
const { title, items } = props.collection;
return (
<div className="collection-page">
<SearchBar />
<h2 className="title">{title} </h2>
<div className="items">
{items.map((item) => (
<CollectionItem key={item.id} {...props} item={item} />
))}
</div>
</div>
);
};
const mapStateToProps = (state, ownProps) => ({
collection: selectCollection(ownProps.match.params.collectionId)(state),
isCollectionFetching: selectIsCollectionFetching(state),
isCollectionLoaded: selectIsCollectionsLoaded(state),
});
export default WithSpinner(
connect(mapStateToProps, { fetchCollectionsStart })(CollectionPage)
);
here is the console of the state.
and this is the withSpinner Hoc:
const WithSpinner = (WrappedComponent) => ({
isCollectionLoaded,
...otherProps
}) => {
return !isCollectionLoaded ? (
<SpinnerOverlay>
<SpinnerContainer />
</SpinnerOverlay>
) : (
<WrappedComponent {...otherProps} />
);
};
export default WithSpinner;
As you can see from the image, I just see the spinner is spinning becuase fetchCollectionStart is not firing so redux store is not updated.
isCollectionLoaded will be true (as I suspect) once dispatch fetchCollectionsStart finishes and redux state is updated.
But you have an issue, fetchCollectionsStart is only dispatched at CollectionPage mount phase which never occurs since isCollectionLoaded is false by default and WithSpinner blocks CollectionPage.
I suggest to move the dispatch useEffect to Spinner Hoc, which makes sense given your code structure. your hoc may look something like:
const WithSpinner = (WrappedComponent) => ({
isCollectionLoaded,
fetchCollectionsStart,
...otherProps
}) => {
useEffect(() => {
fetchCollectionsStart();
}, []);
return !isCollectionLoaded ? (
<SpinnerOverlay>
<SpinnerContainer />
</SpinnerOverlay>
) : (
<WrappedComponent {...otherProps} />
);
};
export default WithSpinner
It's because your property isCollectionLoaded isn't being updated, and your view to update the spinner to the WrappedComponent depends on the property isCollectionLoaded being changed.
You're already using a higher-order component with redux's connect, but what you're attempting to do is create a composite component, with the Spinner and collection searcher. Your instance of withSpinner in the second example will need to expose or call the connect function, so that redux can do its magic.
By exposing the named component in the first example, you're exposing a React component that has bound logic:
export default WithSpinner(
connect(mapStateToProps, { fetchCollectionsStart })(CollectionPage)
);
This can be used as:
<WithSpinner/>
The easier solution, rather than creating a composite component, is to add the spinner to the CollectionPage component:
if (!isContentLoaded) {
return (<Spinner/>);
}
return (
<div className="collection-page">
<SearchBar />
<h2 className="title">{title} </h2>
<div className="items">
{items.map((item) => (
<CollectionItem key={item.id} {...props} item={item} />
))}
</div>
</div>
);
I faced with some problem. I have heavy JSX Element with multipe states. In another part of app I need to pass this Element to Modal window with keeping all states. What is the best solution for solving this problem? Of course I can make Parent with all states and pass it to Child. But maybe it's possible to freeze all states and pass JSX Element as independent component?
Structure will look like:
ParentElement
|_
AnotherElement
|_
SomeHeavyElement
ParentElement:
const ParentElement= () => {
return (
<React.Fragment>
<AnotherElement />
<SomeHeavyElement />
</React.Fragment>
);
};
export default ParentElement;
AnotherElement:
const AnotherElement= () => {
return (
<React.Fragment>
<dialog>
<SomeHeavyElement/>
</dialog>
</React.Fragment>
);
};
export default AnotherElement;
SomeHeavyElement
const SomeHeavyElement= () => {
const [state1, setState1] = useState(true);
...
const [state99, setState99] = useState(false);
return (
<React.Fragment>
{/*some logic*/}
</React.Fragment>
);
};
export default SomeHeavyElement;
You have to lift state up, meaning you should define your state on top of both component (in <ParentElement>). You can't really freeze your component internal state.
Here is a minimal example:
const ParentElement= () => {
const [state1, setState1] = useState(true);
// ...
const [state99, setState99] = useState(false);
return (
<React.Fragment>
<AnotherElement state={{state1, state99}} />
<SomeHeavyElement state={{state1, state99}} />
</React.Fragment>
);
};
export default ParentElement;
const SomeHeavyElement= ({state}) => {
return (
<React.Fragment>
{/*some logic*/}
</React.Fragment>
);
};
export default SomeHeavyElement;
const AnotherElement= ({state}) => {
return (
<React.Fragment>
<dialog>
<SomeHeavyElement state={state} />
</dialog>
</React.Fragment>
);
};
export default AnotherElement;
Also, when you have a lot of useState defined, you could useReducer to centralize your component state. Also, if you want to avoid props drilling, you could define handle your state using React API context.
I wanted to make a HOC that would make use of the underlying dom element. I was looking into the ref-forwarding API in order to do this.
Effectively, I am using a context component to locate the position of DOM nodes and register their positions. I was trying something like this:
export default function createIntersectable<TProps = {}>(
Component: React.ComponentType<TProps & { ref: React.RefObject<HTMLElement> }>,
) {
class Intersectable extends React.PureComponent<ComponentProps<TProps>> {
componentDidMount() {
this.props.register(this.props.forwardedRef.current);
}
componentWillUnmount() {
this.props.unregister(this.props.forwardedRef.current);
}
render() {
const { forwardedRef, ...rest } = this.props;
return <Component ref={forwardedRef} {...rest} />;
}
}
return React.forwardRef((props: TProps, ref: React.RefObject<any>) => {
return (
<IntersectionContext.Consumer>
{({ register, unregister }) => (
<Intersectable
{...props}
forwardedRef={ref}
register={register}
unregister={unregister}
/>
)}
</IntersectionContext.Consumer>
);
});
}
I am then consuming it like this:
renderNodes() {
return this.props.nodes.map((node) => {
const ref = React.createRef();
<Node {...node} ref={ref} key={node.id} />
});
}
Where Node is a component wrapped by createIntersectable.
However, I get null for my ref at all times. I'm not sure of the appropriate way to do this.
What's the correct way to forward a ref with react 16.3 in a HOC?