I'm building a webpage and realized a common style shared by each component (same background, border, and title style). So I thought I should make an HOC which accepts the inner content of each component as well as a title, and returns an outer component which wraps this inner component and heading.
At first I ran into a lot of issues trying to get this to work, being new to React, but now it's finally working but I still don't understand how.
Here is my HOC
const BaseBlock = (WrappedComponent) => {
return class BaseBlock extends Component {
render () {
return (
<div className={styles['base-block']}>
<div className={styles['container']}>
<div className={styles['base-block-head']}>
{ this.props.title }
</div>
<div className={styles['base-block-body']}>
<WrappedComponent {...this.props} />
</div>
</div>
</div>
)
}
}
}
export default BaseBlock
This is the WrappedComponent:
const HighlightsBlock = (props) => {
return <ListsComponent items={props.items} />
}
export default BaseBlock(HighlightsBlock)
And this is the ListsComponent
const ListsComponent = (props) => {
if (props.items) {
return (
<ul className={styles['styled-list']}>
{props.items.map((item, idx) => {
return (
<li key={idx} className={styles['styled-list-item']}>{item}</li>
)
})}
</ul>
)
} else return (
<h3>No highlights</h3>
)
}
export default ListsComponent
And this is how I'm using the component in my app:
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Now, I can see the HighlightsBlock component receiving props twice (Once when I'm using it in my App with props, and once inside the HOC Baseblock as WrappedComponent ). If I remove props from either of these places it stops working. I don't understand how this is working.
When you render <HighlightsBlock items={this.getHighlights()} title='Highlights' /> you are actually rendering the component returned by HOC which in turn renders your actually HighlightsBlock component as <WrappedComponent {...this.props} />
You can think of HighlightsBlock component to be nested two level deep and hence you need to pass on the props to it, firstly as {...this.props} from within HOC and then receive it as props in functional component
This is because of this.getHighlights() in this line,
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Every time you pass props to child component this function is getting executed.
To solve this issue, maintain a state value in your parent component and set that value in getHighlights function like,
getHighlights(){
//you logic to get data
this.setState({items:data.items}); //considering `data` is object which has `items`
}
Now you can pass items like,
<HighlightsBlock items={this.state.items} title='Highlights' />
Related
I am new to react and was wondering if you are allowed to directly pass one or more components to another instead of passing it as a prop or child.
Consider the following:
const ChildComponent = () => {
return (
<h1> I am a child </h1>
);
}
const ChildComponent2 = () => {
return (
<h1> I am also a child </h1>
);
}
//Passing directly to Parent
const ParentComponent = () => {
return (
<div>
<ChildComponent/>
<ChildComponent2/>
</div>
);
}
//Passing as children
const ParentComponent2 = ({ChildComponent, ChildComponent2}) => {
return (
<div>
{ChildComponent}
{ChildComponent2}
</div>
);
}
<ParentComponent>
<ChildComponent/>
<ChildComponent2/>
</ParentComponent>
I have read about passing components as children vs props. I have also seen that it is an antipattern to create functional components inside of functional components. I can't seem to find anything about directly passing one or more components inside of another without using props or children. When testing it out, the component rendered properly, but I am not sure if it's a valid way of passing components to a parent component.
With React's composition model, you can take advantage of the special children prop to pass children elements directly.
// With props object destructuring
const ParentComponent = ({children}) => {
return (
<div>
{children}
</div>
);
}
// Without props object destructuring
const ParentComponent = (props) => {
return (
<div>
{props.children}
</div>
);
}
This lets you pass other components as children to the parent component by nesting the JSX:
// A pair of children components
const ChildComponent1 = () => {
return <div>I am child 1</div>;
};
const ChildComponent2 = () => {
return <div>I am child 2</div>;
};
// Compose your parent component with its children
const App = (props) => {
return (
<ParentComponent>
<ChildComponent1 />
<ChildComponent2 />
</ParentComponent>
);
}
As per the React docs, this is the recommended way of composing your components.
As a final thought, sometimes you might need specialized components or partials and perhaps not make use of children but always render a specific component.
I'm new to React and having some difficulty trying to add a new child component to a component that has already been rendered.
I have an App component which initially contains a Main component (main menu).
I also have Popover components which I want to appear on top of Main when they are children of <App> (and hence siblings of <Main>).
These Popover components vary in number. Each <Popover> can contain buttons which launch another <Popover> over the top again. So the structure would be like
<App>
<Main></Main>
<Popover></Popover>
<Popover></Popover>
...
</App>
However, when the page first loads there are no Popover components open, and the<App> is rendered without any. Here is a stripped-down version of my code:
class App extends React.Component{
constructor(props){ super(props) }
render(){
return (
<div>{this.props.children}</div>
)
}
}
class Main extends React.Component{
constructor(props){ super(props) }
render(){
return (
//main menu stuff here
)
}
}
ReactDOM.render(<App><Main /></App>, root);
How can I add new <Popover>s to my <App> when the user clicks something? Before React I would simply do App.appendChild(Popover) kind of thing, but I'm quite lost here.
I should add that the elements the user will click to trigger an initial <Popover> are not contained within <Main>; they are outside of the <App>, as I am trying to slowly transition my existing page to using React. I think this could be part of my problem.
So basically in React, you have multiple ways of doing this, but to be more reliable you need to have data that represents the dynamic components you will render in your DOM. And to do this you need to create a state and a function that can add new information to your state. Then simply by sharing this function with your other components, you can trigger it from wherever you want, and this will update your state which will increase the amount of dynamic components you will render.
Take a look at this example
import { useState } from "react";
export default function App() {
const [popups, setPopups] = useState([]);
const addNewPopup = () => {
setPopups([...popups, { title: "I am a popup" }]);
};
return (
<div className="App">
<ChildComponent onClick={addNewPopup} />
{popups.map((p) => {
return <Popup title={p.title} />;
})}
</div>
);
}
function ChildComponent({ onClick }) {
return (
<div>
<p>I am a child component</p>
<button onClick={onClick}>Add new element</button>
</div>
);
}
function Popup({ title }) {
return <div>I am a popup with title = {title}</div>;
}
Consider this example
export function InsideHoc(props){
const [A, setA] = useState(false);
return({if(A) && (<h1>Print something</h1>)});
}
In another file
import {A, setA} from './inside-file';
function ToggleFromOutside(){
return(<p onClick={setA(!A)}>Verify</p>);
}
Can setA be exposed outside so that the state of this component be changed from outside? I know this can be done through redux. But without using this, is there a way to change the state of one component?
Structure is like this
import {withCreateHOC} from './external';
import childComponent from './child';
class A extends React.Component {
render(){
<Another menus={(item) => <MenuItems object={item} />}
/>
}
}
export default withCreateHOC(A, {custom: childComponent, title: 'Add'});
//withCreateHOC renders modal here as well as it has a button to toggle the state. Same state should be used from below function
function MenuItems(){
return(<button onClick={displayModal}>)
}
Yes.
You can lift the state in that case. This works good if you don't need to pass down the setState to far down the tree.
If you don't want to pass the setState function all the way down the React element tree, you will need to use context, redux or some other state handling.
Example state lift
export function Parent(){
const [message, setMessage] = useState("Hello World");
return (
<>
<Child1 message={message} />
<Child2 changeMessage={setMessage} />
</>
);
}
// Can be in other file
function Child1(props){
return(<p>{props.message}</p>);
}
// Can be in other file
function Child2(props){
return(
<a onClick={() => props.changeMessage("Changed")}>
I can change things in other components.
</a>
);
}
Example of React tree with shared context/redux
<WithRedux>
<App>
<Navigation />
<Modal />
<PageRenderer />
<SomeList>
<ListItem />
<ListItem />
</Somelist>
</App>
<WithRedux>
All the children of the WithRedux component can access the state and modify it.
(PS: You can wrap the App with the HOC withRedux etc, this example is just for visualization)
I created the following render props component, through the children prop as function:
export class GenericAbstractLoader extends React.Component<IGenericAbstractLoaderRenderProps, any> {
constructor(props: IGenericAbstractLoaderRenderProps) {
super(props);
}
public render() {
return this.props.isLoading ? (
<div className="generic-abstract-loader-wrapper">
<div className="generic-abstract-loader">
<img src={loader} />
</div>
{this.props.children(this.props)}
</div>
) : (
this.props.children(this.props)
);
}
}
It actually just renders the wrapped component if isLoading is false, or adds a layer of loading if the prop is true.
It works "quite perfectly".
I do call it in this way from another component:
public render() {
return (
<div>
<button onClick={this.justDisable}>JUST SET FALSE TO STATE</button>
<br />
<button onClick={this.enableDisable}>Disable/Enable Elements</button>
<br />
<GenericAbstractLoader customProp="custom prop" isLoading={this.state.shouldDisable}>
{props => <HomeTestForm {...props} />}
</GenericAbstractLoader>
</div>
);
}
As you can see the wrapped component is a simple HomeTestForm which contains just one input text:
export class HomeTestForm extends React.Component<IGenericAbstractLoaderHOCProps, any> {
constructor(props: IGenericAbstractLoaderHOCProps) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = { value: 'Initial Value' };
}
public render() {
return (
<div>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</div>
);
}
public handleChange(event: any) {
this.setState({ value: event.target.value });
}
}
My issue is that when I toggle the isLoading prop, the state isn't kept by the wrapped component, so if I change the value of the inner text field, when adding the loading layer, and when removing it, the value is not taken, so the input field is still rendered with the init value.
What's the right way to create persistent wrapped components with render props?
Your component's state is getting reset because React is creating an entirely new component when your loading state changes. You can see these if you add a console.log to the constructor of HomeTestForm. React thinks this is necessary because of the ternary operator in GenericAbstractLoader. If you can restructure the render function in GenericAbstractLoader to something like the example below, you can give React the context it needs to persist the component instance across renders.
render()
return (
<div className={this.props.isLoading ? "generic-abstract-loader-wrapper" : ""}>
{this.props.isLoading && <img src={loader} />}
{this.props.children(this.props)}
</div>
);
}
React's documentation has a brief section about this under Reconciliation. This situation falls under the Elements of Different Types heading.
Also, unrelated to your question, based on your current example you don't need to use a render prop. The render function in GenericAbstractLoader can just render {this.props.children} without the function call and you can place your props directly on the child component as in the example below. You may have just simplified your example for SO and have a situation where you need render props, but I wanted to point this out just in case.
<GenericAbstractLoader isLoading={this.state.shouldDisable}>
<HomeTestForm customProp="custom prop" />
</GenericAbstractLoader
I'm new to react and redux (and posting on stack overflow!).
I'd like to hide/display a redux-form based on a button choice.
I have two buttons: Option-A and Option-B.
I followed the redux tutorial exactly to have their onClick methods dispatch setVisibilityFilter(buttonprops.filter) through a container. See: FilterLink.js This works fine and updates the state's visibilityFilter with the corresponding option.
However, I'm stuck about how I should access the state's filter to hide/display different forms. I would like something similar to what formValueSelector does, but it isn't applicable for buttons (because they don't return values?)
This is my main component's code:
class MainForm extends Component {
render() {
const { error } = this.props
return (
<Grid.Column width={9}>
<Button.Group floated='right'>
<FilterLink filter={VisibilityFilters.SHOW_A}>A</FilterLink>
<Button.Or />
<FilterForm filter={VisibilityFilters.SHOW_B}>B</FilterLink>
</Button.Group>
/* If SHOW_A, display FORM_A, else if SHOW_B, display FORM_B */
</Grid.Column>
)
}}
I feel like just toying with the state directly now would waste the effort of implementing redux. I think I should be passing the value as a prop down to the child forms, but I'm confused how to do so, especially because I don't know how I would get that value without changing my onClick anyway, and onClick is already defined in FilterLink.js
There must be some way to access my state visibility filter to hide/display a form, just unsure how to get there. Thank you!
With connect, you can pass anything from the Redux Store to your component through its props.
So based on the link you posted, this should work:
import { connect } from 'react-redux'
class MainForm extends Component {
render() {
const { error, visibilityFilter } = this.props
return (
<Grid.Column width={9}>
<Button.Group floated='right'>
<FilterLink filter={VisibilityFilters.SHOW_A}>A</FilterLink>
<Button.Or />
<FilterForm filter={VisibilityFilters.SHOW_B}>B</FilterLink>
</Button.Group>
{visibilityFilter === VisibilityFilters.SHOW_A
? <FormA />
: <FormB />
}
</Grid.Column>
)
}}
const mapStateToProps = state => ({
visibilityFilter: state.visibilityFilter
})
export default connect(mapStateToProps)(MainForm)
Make sure you have connected the component you want to conditionally render things to the redux store.
import { connect } from 'react-redux'
...
const mapStateToProps = state => ({visibleFilter: state.visibilityFilter})
export default connect(mapStateToProps)(MainForm)
Then you can access this information in your connected component's props, e.g.
render() {
return {
{this.props.visibleFilter === VisibilityFilters.SHOW_A && (<FormA /> )}
{this.props.visibleFilter === VisibilityFilters.SHOW_B && (<FormB /> )}
}
}