how do i make an object pass as a child - reactjs

learning react/nextjs...i have a main layout that simply includes two components - footer, and header.
function MainLayout(children) {
return(
<div>
<Header />
{children}
<Footer />
</div>
);
}
export default MainLayout;
i want this layout to sit within the main page like so:
import {default as Layout} from './MLayout.js';
import {default as Navbar} from './MNavbar.js';
function MainPage() {
return(
<div>
<Layout children={Navbar} />
</div>
);
}
export default MainPage;
and within that mainpage, i'd like to insert a navbar in between the footer and header of the mainlayout. the navbar will change later, so that's why i want to keep it as a child.
the navbar im using does not use links, it uses buttons instead:
const Navbar = (props, {children}) => {
const [login, makeLogin] = useState(false);
function Login(){
makeLogin(true);
}
if (login) {
return (
<div>
<LoginPage />
</div>
)
}
else {
return ( blah blah blah the regular nav bar buttons.
);
}};
the error i get:
Unhandled Runtime Error
Error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.

Pass <Navbar/> as children in MainPage to
<MainLayout children={<Navbar />} />
Get { children } in
function MainLayout({ children }) {...}

Destructure the props for your MainLayout and Navbar components by wrapping them in curly brackets:
function MainLayout({ children }) {
const Navbar = ({ children }) => {
currently your code is passing the entire props object, which looks like { children: ... } and is causing your error.

Related

Map through two arrays of components and strings and render in one component

I have two arrays that I want to map through:
const social = ["Snapchat", "TikTok", "Dribbble", "Discord", "Facebook"];
const socialIcons = [<SnapchatIcon />, <DribbbleIcon />];
The socialIcons array are all components
How can I send both values as props into my DummyRectangle component? Here is my current code:
{social.map((s, index) => (
<div className="dummy_buttonsWrapper">
<DummRectangle social={s} socialIcons={i} />
</div>
))}
And here is DummyRectangle component:
function DummRectangle({ social, socialIcons }) {
// console.log("---->", socialIcons);
return (
<div>
<p>{social}</p>
{<socialIcon/>} // render social icon component
</div>
);
}
To do so, you don't need to wrap tags around your socialIcon in your DummRectangle. Also, it doesn't seem that you are passing the socialIcon component at all. If I were you, I would do something like this:
The following two are the components as an example that you would like to render (in your case - socialIcons)
// Comp1.js
import React from "react";
const Comp1 = () => <div>actual Comp1</div>;
export default Comp1;
// Comp2.js
import React from "react";
const Comp2 = () => <div>actual Comp2</div>;
export default Comp2;
Now, in your main Parent component, you would simply get the current component of the componentName (in your case - social) by accessing your component's array with an index. Then, you would pass this currentComponent as props to your Child component where you want to render it.
// App.js
import React from "react";
import Comp1 from "./Comp1";
import Comp2 from "./Comp2";
import DummyComponent from "./DummyComponent";
export default function App() {
const componentNames = ["Comp1", "Comp2"];
const components = [<Comp1 />, <Comp2 />];
return (
<div className="App">
{componentNames.map((name, index) => {
const currentComponent = components[index];
return (
<div>
<DummyComponent componentName={name} component={currentComponent} />
</div>
);
})}
</div>
);
}
In your Child component, you can simply render it by enclosing it into the brackets - no need to add tags. React will do all the rendering for you. In your case it would be { socialIcon }
// DummyComponent.js
import React from "react";
const DummyComponent = ({ componentName, component }) => {
return (
<div>
<p>{componentName}</p>
{component}
</div>
);
};
export default DummyComponent;
Link to Codesandbox with the above code for reference: click here

Next JS, React have child component tell parent what to render

I am new to NextJS and React and want to create some dynamic routing. I have a parent page that is calling a child component which show some navigation.
In my parent component I have a layout that includes a header and right sidebar which will show on each page, but depending on the link that is clicked in my Nav component the body content will change.
My parent component:
import React from 'react';
import { Header } from './Header;
import { SubNav } from './SubNav;
import { Sidebar } from './Sidebar;
export const AboutPage = () => {
return (
<>
<Header />
<SubNav />
<div className="content">
>>>I want to do something like this <<<
{section === history && <History />}
{section === contact && <Contact />}
.....
</div>
<Sidebar />
</>
);
};
In my child Nav component I am mapping through some data that includes a 'value' for each different section that I have defined in an array.
<NavButtons
onClick={() => {
const path = AppRoute.ABOUT.replace(':section', 'value');
router.push(path, path, { shallow: true });
}}
/>
The page routes correctly with the value of the section changing each time I click on a different Navigation button but I can't get the content to change in the parent component, I have tried creating a function in the parent that will get the value from the child so that I can use it to define the sections in the parent, but it ends up creating an infinite loop. Confused on how to get the two to work together.
You can detect the pathname with the useRouter Hook.
Then you can check in a switch case statement which site the user is on and render the components for the route.
import { useRouter } from next/router
Inside the component
const router = useRouter();
The function to return the component
function renderSidebar(pathname){
switch(pathname){
...
}
Inside the render function
{ renderSidebar(router.pathname) }
See further usage in the NextJS docs
https://nextjs.org/docs/api-reference/next/router
import React from 'react';
import {useState} from 'react';
import { Header } from './Header';
import { SubNav } from './SubNav';
import { Sidebar } from './Sidebar';
export const AboutPage = () => {
const [section,setSection] = useState()
sectionHandler = (section) => {
setSection(section)
}
return (
<>
<Header />
<SubNav />
<div className="content">
>>>I want to do something like this <<<
{section === history && <History />}
{section === contact && <Contact />}
.....
</div>
<Sidebar />
</>
);
};
and in your child comp
<NavButtons
onClick={() => {
const path = AppRoute.ABOUT.replace(':section', 'value');
props.sectionHandler(section)
router.push(path, path, { shallow: true });
}}
/>
don't forget to pass sectionhandler from parent to child comp

Passing props from parent to sibling in React

I am recreating a simple React app that I have already created in Angular. The React app has two components: one (menus.js) for a side menu and a second (content.js) that will display the content from each item in the menu when each link is clicked (just like an iframe of sorts). In the App.js I am making a REST API call to populate the state for the menus.js component. Note that both components are in the App.js as follows:
App.js
import React,{Component} from 'react';
import Menus from './components/menus';
import Content from './components/content';
class App extends Component {
state = {
menus: []
}
componentDidMount(){
fetch('api address')
.then(res => res.json())
.then((data)=> {
this.setState({menus: data})
})
.catch(console.log)
}
render(){
return (
<div>
<div><Menus menus={this.state.menus} /></div>
<div><Content /></div>
</div>
);
}
}
export default App;
here is the menu.js component; it takes a prop (menus) from App.js and builds the menu links with items from it:
import React from 'react';
import { BrowserRouter as Router, Link,} from "react-router-dom";
const Menus = ({ menus }) => {
return (
<Router>
<div>
<center><h1>Lessons</h1></center>
{menus.map(menu => (
<li key={menu.lesson}>
<Link to={`/lesson/${menu.lesson}`}>{menu.lessonName}</Link>
</li>
))}
</div>
</Router>
);
};
export default Menus;
Here is what I need - how do I pass items from the same prop (from App.js) to the content component? FYI - I need this to happen each time a link in the menu in menu.js is clicked (which is why a key is used in the list The simple idea is content will update in the content component each time a menu link in the menu component is clicked.
**content.js**
import React from 'react'
const Content = () => {
return (
<div>{menu.content}</div>
)
};
export default Content
Based on your description of the problem and what I can see of what you've written, it seems to me like you are trying to build an application where the menu persists, but the content changes based on menu clicks. For a simple application, this is how I would structure it differently.
<ParentmostComponent>
<MenuComponent someProp={this.state.some_data} />
<Switch>
<Route path={"/path"} render={(props) => <Dashboard {...props} someProp={this.state.some_other_data_from_parents} />
</Switch>
</ParentMostComponent>
This would allow the menu to always stay there no matter what the content is doing, and you also won't have to pass the menu prop to two components.
In your menu.js, attach the menu object to the Link
...
{menus.map(menu => (
<li key={menu.lesson}>
<Link to={{
pathname: `/lesson/${menu.lesson}`,
state: menu
}}> {menu.lessonName} </Link>
</li>
))}
...
In your content.js receive the menu like this:
import React from 'react'
const Content = () => {
console.log(props.location.state.menu.content);
return (
<div>{props.location.state && props.location.state.menu.content }</div>
)
};
export default Content
Read more here
Your example uses React Router, so this answer uses it as well.
First of all, move the Router up the hierarchy from Menus to App to make the router props available to all components. Then wrap your Content inside a Route to render it conditionally (i.e. if the path matches "/lesson/:lesson"):
class App extends Component {
state = {
menus: [
{
lesson: '61355373',
lessonName: 'Passing props from parent to sibling in React',
content: 'I am recreating a simple React app…'
},
{
lesson: '27991366',
lessonName: 'What is the difference between state and props in React?',
content: 'I was watching a Pluralsight course on React…'
}
]
}
render() {
const { menus } = this.state
return (
<Router>
<div>
<div><Menus menus={menus}/></div>
<Route path="/lesson/:lesson" render={({ match }) => (
<div><Content menu={menus.find(menu => menu.lesson === match.params.lesson)}/></div>
)}>
</Route>
</div>
</Router>
)
}
}
With the help of the render prop, you can access the router props (in this case match.params.lesson) before rendering your child component. We use them to pass the selected menu to Content. Done!
Note: The basic technique (without React Router, Redux etc.) to pass props between siblings is to lift the state up.

Creating a parent 'workspace' component in ReactJS

Using ReactJS, I am trying to create a common workspace component that will have toolbar buttons and a navigation menu. The idea I have is to re-use this component to wrap all other dynamic components that I render in the app.
Currently, I've created a Toolbar and MenuBar components that I then add to each component in the app as such:
<Toolbar/>
<MenuBar/>
<Vendors/>
This does not feel right, since my aim is to have just one component which would be something like:
<Workspace>
<Vendor/>
</Workspace>
However, I am not sure of how to achieve this and whether this is the right approach.
As to whether or not it is the right approach is subjective, but I can provide insight into one way to make a "wrapper" type component:
// Your workspace wrapper component
class Workspace {
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
Toolbar goes here
</div>
<div className="workspace__nav">
Navgoes here
</div>
<div className="workspace__content">
{this.props.children}
</div>
</div>
)
}
}
// Using the component to define another one
class MyComponent {
render() {
return (
<Workspace>
This is my workspace content.
</Workspace>
)
}
}
You can also look at HOC's or Higher Order Components to wrap things.
React offer two traditional ways to make your component re useable
1- High-order Components
you can separate the logic in withWorkspace and then give it a component to apply that logic into it.
function withWorkSpace(WrappedComponent, selectData) {
// ...and returns another component...
return class extends React.Component {
render() {
// ... and renders the wrapped component with the fresh data!
// Notice that we pass through any additional props
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
const Component = () => {
const Content = withWorkSpace(<SomeOtherComponent />)
return <Content />
}
2- Render Props
or you can use function props then give the parent state as arguments, just in case you need the parent state in child component.
const Workspace = () => {
state = {}
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
{this.props.renderTollbar(this.state)}
</div>
<div className="workspace__nav">
{this.props.renderNavigation(this.state)}
</div>
<div className="workspace__content">
{this.props.children(this.state)}
</div>
</div>
)
}
}
const Toolbar = (props) => {
return <div>Toolbar</div>
}
const Navigation = (props) => {
return <div>Toolbar</div>
}
class Component = () => {
return (
<Workspace
renderNavigation={(WorkspaceState) => <Navigation WorkspaceState={WorkspaceState} />}
renderTollbar={(WorkspaceState) => <Toolbar {...WorkspaceState} />}
>
{(WorkspaceState) => <SomeComponentForContent />}
</Workspace>
)
}

React: Is it bad practice to import a child component directly rather than pass in as a dependency?

I may be over thinking this, but I am curious if importing a child component directly is bad practice with regards to coupling and testing.
Below is a simple example:
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
<Header></Header>
<div>{this.props.importantContent}</div>
</div>
)
}
}
To me it looks like there is now coupling between Widget and Header. With regards to testing, I don't see an easy way to mock the Header component when testing the Widget component.
How do other larger React apps handle cases like this? Should I pass Header in as a prop? If using react-redux, I can inject header with the Connect method like below to reduce boilerplate. Is that sound?
import { connect } from 'react-redux';
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
{this.props.header}
<div>{this.props.importantContent}</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
header: Header
}
}
export default connect(mapStateToProps)(Widget)
I am interested is simple doing what the community is generally doing. I see that one solution is doing shallow rendering to test on the main part of the component and not the child components using something like Enzyme.
Thoughts or other ideas?
Passing elements / components as props is a good idea. Having default props is a good idea too:
const Widget = ({
header = <div>Default Header.. </div>,
content = <div>Default Content.. </div>
}) =>
<div>
{header}
{content}
</div>
Then elsewhere in your app:
<Widget header={<Header title="Foo" />} content="content from props" />
No need to inject using connect
You can also pass a component, not just an element if you want to interact with props / send data back to parent:
const Widget = ({
Header = props => <div>Default Header.. </div>,
Content = props => <div>Default Content.. </div>
}) =>
<div>
<Header />
<Content />
</div>
Elsewhere:
<Widget Header={Header} Content={props => <Content />} />
As long as the component always renders the same thing it can be directly rendered as a child rather than the parent.
If all other portions of the Component remain constant and only the Header can be different across pages then you could actually implement it as an HOC instead of passing it as a props
const MyCompFactory = ({CustomHeader = DefaultHeader}) => {
return class Widget extends React.Component {
render() {
return (
<div>
<CustomHeader/>
<div>{this.props.importantContent}</div>
</div>
)
}
}
}
and use it like
const CustomComponent = MyCompFactory({CustomComponent: Header})
as long as testing is concerned in your case, you could just shallow render your component and then Search if the Header component is rendered something like
import Header from 'path/to/header'
const component = shallow(
<Widget {...customProps}/>
)
test('test' , () => {
expect(component.find(Header).exists()).toBe(true)
})

Resources