0

I have simple react-application:

export default class App extends Component {
    render() {
        return (
            <Provider store={store}>
                <Router>
                    <Header/>
                    <Switch>
                        <Route exact path="/" component={MainPage}/>
                        <AdminLayout>
                            <Route exact path="/admin" component={AdminPage}/>
                            <Route exact path="/admin/add-user" component={AddUserPage}/>
                        </AdminLayout>
                        <Route path="/sign-up" component={SignUpPage}/>
                        <Route path="*" component={NotFound}/>
                    </Switch>
                </Router>
            </Provider>
        );
    }
}

Here I needed Layout component for admin components. I wrapped them in it:

export default class AdminLayout extends React.Component<{ }, AdminLayoutState> {
    state: AdminLayoutState = {
        contentType: "personal"
    };


    setActiveMenuItem = (contentType: string) => {
        this.setState({
            contentType: contentType
        });
    };

    render() {
        const {
            contentType
        } = this.state;

        return (
            <Fragment>
                <Segment size={"huge"}>
                    <Grid>
                        <Grid.Row>
                            <Grid.Column width={3}>
                                <MenuLeft
                                    contentType={contentType}
                                    onClick={(contentType) => this.setActiveMenuItem(contentType)}
                                />
                            </Grid.Column>
                            <Grid.Column width={12}>
                                {
                                    React.Children.map(this.props.children, function (child) {
                                        // @ts-ignore
                                        return React.cloneElement(child, {})
                                    })
                                }
                            </Grid.Column>
                        </Grid.Row>
                    </Grid>
                </Segment>
            </Fragment>
        );
    }
}

I need to access layout params like contentType:

export default class AdminPage extends React.Component<AdminPageProps, AdminPageState> {
    constructor(props: AdminPageProps) {
        super(props);
        // const dispatch = useDispatch();

    }

    state: AdminPageState = {
        // optional second annotation for better type inference
        columnNames: [
            "first name",
            "last name",
            "mail",
            "actions"
        ],
    };


    render() {
        const {
            columnNames,
        } = this.state;
        const {
            contentType
        } = this.props;

        return (
            <>
                <Header>
                    HEADER
                    {contentType}
                </Header>
                <Divider/>
                <Button primary floated={"right"}>
                    Add
                </Button>
                <UsersTable
                    color={"blue"}
                    tableType={"doctor"}
                    recordsLimit={50}
                    columnNames={columnNames}
                />
            </>
        );
    }
}

But I get error, that propert does not exist. Then I changed child pass in Layout to:

return React.cloneElement(child, this.props)

And get Uncaught TypeError: Cannot read property 'props' of undefined

I supposed that my wrapper will get params from parent components and I will be able to access its state from child components like AdminPage, AddUserPage. Unfortunately no. How can I perform such wrapper and pass props to childs properly?

1 Answer 1

1

Solution #1

React.cloneElement takes 3 arguments ,

  1. The element to clone.
  2. The props to spread to the cloned element props.
  3. The new children to append to the element. If omitted the original children will remain.

So in your case you need to add the contentType prop as the second argument .

{
  React.Children.map(this.props.children, function (child) {
    // @ts-ignore
    return React.cloneElement(child, { contentType });
  });
}

what this does is { contentType } it spreads this additional props along with the props applied for that component . But in your case the above alone might not work because your children is Route component from react-router-dom . So you might need a wrapper on top of your Route Component as well .

const CustomRoute = ({component: Component, path, ...rest}) => {
  return (
    <Route path={path} render={(props) => <Component {...props} {...rest} />} />
  );
};

Now you can replace you route inside the admin layout as

<AdminLayout>
<CustomRoute exact path="/admin" component={AdminPage}/>
<CustomRoute exact path="/admin/add-user" component={AddUserPage}/>
</AdminLayout>

solution 2 (RenderProps)

Another approach will be to use the render props pattern where you can render the children of the AdminLayout as a function .

<Grid.Column width={12}>
 {this.props.children(contentType)}
</Grid.Column>

With this we can now render the routes as

<AdminLayout>
    {(contentType) => {
        return (
            </>
            <Route exact path="/admin" render={(props) => <AdminPage {...props} contentType={contentType} />} />
            <Route exact path="/admin/add-user" render={(props) => <AddUserPage {...props} contentType={contentType} />} />   
            </>
        )
    }}
</AdminLayout>

IMHO the render props pattern is much more simpler as this avoids the need to create a custom Route component.

Reference

Render Props

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.