Nesting components in React, props not passing to child component - javascript

I'm creating a list with React components, and am working on the list container and reusable list-item components. The parent most component passes information to middle component, but the child-most component does not have props values.
What am I doing wrong? No console errors.
middle component:
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return (
// want to render list-item component
<li key={video.etag}>{video.snippet.title}</li>
)
});
return (
<ul className="list-group">
{videoItems}
<ListItem
videos={ videoItems }
/>
</ul>
)
}
a console log in child-most component shows no props

I think it will be better if you pass props directly into the children component. Try this:
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return (
// want to render list-item component
<ListItem key={video.etag} video={video} />
)
});
return (
<ul className="list-group">
{videoItems}
</ul>
)
}
Inside your children component, you can display what you want

Try passing the property to the child-most component using the 'this' keyword.
<ListItem videos={this.videoItems}/>

Related

Creating tab component in React and handling click events for child components

New to React and trying to build a tabular component. I know I'm reinventing the wheel but I'm trying to take this as a learning experience.
Here is how I intend to use the component:
<Tabs>
<Tabs.MenuItems>
<Tabs.MenuItem>Tab item 1</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 2</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 3</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 4</Tabs.MenuItem>
</Tabs.MenuItems>
<Tabs.Panes>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
</Tabs.Panes>
</Tabs>
My current implementation works in displaying the items properly. But the one challenge I am facing is being able to handle the onClick event for the Tabs.MenuItem. I understand that I should not be handling the onClick in the Tabs.MenuItem child component, and rather should be handled in the upmost parent Tabs component.
I tried using forwardedRef but that posed some limitations in accessing the props.children. Even if I managed to get it working syntactically, I am not even sure how the Tabs component is suppose to access that ref.
The idea here is that depending on what Tabs.MenuItem is in an active state, it will correspond to the same child Tabs.Pane component index to render that pane.
import React, { forwardRef, useState } from "react";
const Tabs = (props, { activePane }) => {
return (
props.children
);
}
const MenuItems = (props) => {
React.Children.forEach(props.children, child => {
console.log(child);
})
return (
<div className="ui secondary menu" style={props.style}>
{props.children}
</div>
)
}
const MenuItem = (props) => {
const [isActive, setActive] = useState(false);
return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a className={isActive ? "item active" : "item"} onClick={() => setActive(!isActive)}>{props.children}</a>
)
}
// const MenuItem = forwardRef((props, ref) => (
// // eslint-disable-next-line jsx-a11y/anchor-is-valid
// <a ref={ref} className="item">{props.children}</a> // error accessing props.children
// ))
const Panes = (props) => {
return (
props.children
)
}
const Pane = (props) => {
return (
props.children
)
}
Tabs.MenuItems = MenuItems;
Tabs.MenuItem = MenuItem;
Tabs.Panes = Panes;
Tabs.Pane = Pane;
export default Tabs;
I am not looking for someone to complete the entire tabular functionality, just an example of how I can forward the children references to the topmost parent so that I can handle click events properly.

What is the React way of inserting an icon into another component?

I'm trying to create an WithIcon wrapper component which would insert a child (icon) into a wrapped component.
Let's say I have a button:
<Button>Add item</Button>
I want to create a component WithIcon which will be used like this:
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
Ultimately what I want to achieve is this:
<Button className="with-icon"><i className="me-2 bi bi-{icon}"></i>Add item</Button>
Notice the added className and the tag within the Button's body.
I'm trying to figure out how the WithIcon component's code should look like. What is the React way of achieving this result?
The hardest part was the rules of using the WithIcon Will we only have one ?
Will we have only it at the leftmost ? Something like that.
But if we follow your example. We can relatively write something like this for the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
return (
<>
<i className={`me-2 bi bi-${i}`}></i>
{React.cloneElement(child, { className: "with-icon" })}
</>
);
});
};
Then we can just use it the way you want it
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
What we do is just looping through the children which in react is any nested jsx you throw in it (Button in our case)
You can find my fiddle here : https://codesandbox.io/s/react-font-awesome-forked-321tz?file=/src/index.js
UPDATE
So my previous answer does not fully meet the end result we want. The will need to be the main parent
The idea is still quite the same as before but here we are infering the type of the component we passed inside the WithIcon This also adds a safeguard when we passed a nested component inside the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
const MyType = child.type; // So we can get the Button
return (
<MyType className="with-icon">
<i className={`me-2 bi bi-${i}`}></i>
{(React.cloneElement(child, {}), [child.props.children])}
</MyType>
);
});
};
I think I'll go to sleep I'll update the rest of the explanation at later date.
See the fiddle here :
https://codesandbox.io/s/react-font-awesome-forked-y43fx?file=/src/components/WithIcon.js
Note that this code does not preserved the other props of the passed component, but you can relatively add that by adding {...child.props} at the MyComponent which is just (reflection like?) of infering the component.
Of course also have another option like HOC Enhancers to do this but that adds a bit of complexity to your how to declare your component api. So Pick whats best for ya buddy
Maybe try using a higher order component?
const withIcon = (icon, Component) => ({children, ...props}) => {
return (
<Component className="with-icon" {...props}>
<i className=`me-2 bi bi-${icon}` />
{children}
</Component>
);
}
Then the usage is
const ButtonWithIcon = withIcon("your-icon", Button);
<ButtonWithIcon>Add Item</ButtonWithIcon>
From my experience with react it usually comes down to either using a property inside the component like here (https://material-ui.com/api/button/) or higher order component like what I described.
There are two common patterns used in React for achieving this kind of composition:
Higher-Order Components
Start by defining a component for your button:
const Button = ({ className, children }) => (
<button className={className}>{children}</button>
);
Then the higher-order component can be implemented like this:
const withIcon = (Component) => ({ i, className = '', children, ...props }) => (
<Component {...props} className={`${className} with-icon`}>
<i className={`me-2 bi bi-${i}`} />
{children}
</Component>
);
Usage:
const ButtonWithIcon = withIcon(Button);
<ButtonWithIcon i="plus">Add Item</ButtonWithIcon>
Context
Start by defining the context provider for the icon:
import { createContext } from 'react';
const Icon = createContext('');
const IconProvider = ({ i, children }) => (
<Icon.Provider value={i}>{children}</Icon.Provider>
);
and then your component:
import { useContext } from 'react';
const Button = ({ className = '', children }) => {
const i = useContext(Icon);
if (i) {
className += ' with-icon';
children = (
<>
<i className={`me-2 bi bi-${i}`} />
{children}
</>
);
}
return (
<button className={className}>{children}</button>
);
};
Usage:
<IconProvider i="plus"><Button>Add Item</Button></IconProvider>

Update data in parent when added in child in react

I'm new to React. I see a lot of posts for updating children when the parent is updated but I haven't found the opposite.
I have a parent component (MainComponent) fetching and storing data. The form to add data (FormNewSubItem ) is in a child component.
When I submit the form I add a subitem in an item and I want the array of items in main component to store the new data.
In MainComponent.jsx
storeItems(items) {
// store items in db
}
retrieveItems() {
// return items from db
}
render {
return (
<MainComponent>
<ListItems items={this.retrieveItems()} />
</MainComponent>
)
}
In ListItems.jsx
render {
return (
<div>
{this.props.items.map((item, idx) => <Item item={item} key={idx} />)}
</div>
)
}
In Item.jsx
handleNewSubItem = (subItem) => {
this.props.item.addSubItem(subItem);
// how to update the 'fetchedItems' in MainComponent ?
}
render {
return (
<React.Fragment>
<div className="title">{this.props.item.title}</div>
<div className="content">
<ListSubItems subitems={this.props.item.subitems}>
<FormNewSubItem handleNewSubItem={thid.handleNewSubItem} />
</ListSubItems>
</div>
</React.Fragment>
)
}
So there are a few ways you can do this. A common way would to connect both components to the same set of data using a library like Redux.
But if you just want to do this in React, then you will need to define the function in the top level component and then pass it down as props to the child component.
So the MainComponent.js will have a function like
addSubItem = (item) => {
// update the state here
}
Then you will pass it down as props to the component you want to use it in, so pass it to the ListItems.js component by
<ListItems items={this.retieveItems()} addSubItem={addSubItem} />
and then again to the Item.js component
<Item item={item} key={idx} addSubItem={this.props.addSubItem}/>
then in the Item component just call it with this.props.addSubItem
Because it is scoped to the top level component, when you call the function in the child components and pass it an item, it will update the state in the parent component
Look into using Context and the useContext hook if you don’t want to use redux for a simple app.

React on click event order array of data passing in the component

I'm new to React and I'd like some help please. I'm having a button and a component inside my app.js which is the main file
import React from 'react'
const App = () => {
const {data, loading, error} = useQuery(GET_DATA, {
variables: {...}
})
console.log(data)
state = {
clickSort: false
}
let clickSort = () => {
this.setState({
clickSort: true
})
}
return (
<div className="myApp">
<button onClick="{this.clickSort}">Click Me</button>
<div className="myClass">
<FooComponent fooData={data} clickSort={this.state.clickSort} />
</div>
</div>
)
}
What I want to do is when I click the button to sort the array of data I'm rendering in my component in a desc order. I was thinking of passing another parameter like a flag in the component, but I'm not sure how can I do this
If both of your components (<Button /> and <List />) are wrapped within common parent (<Parent />) you may employ the concept, known as lifting state up
Essentially, it is binding event handler within one of the child component's props (onSort() of <Button />) to the callback within parent (handleSort() of <Parent />), as well as binding dependent child prop (isSorted of <List />) to the state variable of common parent (sorted of <Parent />).
With that, you simply keep track of sorted flag within parent state (using useState() hook) and once handleSort() is triggered, it modifies that flag and consequent re-render of dependent components (<List />) takes place:
const { render } = ReactDOM,
{ useState } = React
const sampleData = ['itemC', 'itemA', 'itemD', 'itemB']
const Button = ({onSort}) => <button onClick={onSort}>Sort it</button>
const List = ({listData, isSorted}) => {
const listToRender = isSorted ? listData.sort((a,b) => b > a ? 1 : -1) : listData
return (
<ul>
{listToRender.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
}
const Parent = () => {
const [sorted, setSorted] = useState(false),
handleSort = () => setSorted(true)
return (
<div>
<Button onSort={handleSort} />
<List listData={sampleData} isSorted={sorted} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
It looks from your question that you want to let a child component (FooComponent) know that the button has been clicked so that it can process (sort) the data it has received.
There are a lot of approaches to this. For instance, you could pass a boolean property to the child component that is a flag for it to do the sorting. So the parent component tracks when the button has been clicked, and the child component just observes this (perhaps in componentDidUpdate).
This would change slightly if you are using functional components, rather than class based components, but it gives you an idea.
state = {
requestSort: false
}
requestSort = () => {
this.setState({
requestSort: true
}
}
render() {
return (
<>
<button id="myBtn" onClick={this.requestSort}>Click Me</button>
<div className="myClass">
<FooComponent requestSort={this.state.requestSort} fooData={data} />
</div>
</>
)
}
Alternatively, since the data is being passed to the child component as well, you could have the parent sort it when it is clicked. It depends on if you are doing anything else with the data (i.e. is only FooComponent the one that should have the sorted copy of the data or not).
Pass the data from the state into FooComponent and write a function that sorts the data in that state. The data will instantly be updated in the child component once the state has updated in the parent component because the child component will rerender once it's noticed that the data in the parent component doesn't match the data that it previously received. Below is an example.
import React from 'react'
const FooComponent = ({ fooData }) => (
<div>
{fooData}
</div>
)
export default class Home extends React.Component {
constructor(props){
super(props);
this.state = {
data: [1, 4, 2, 3]
}
}
sortData() {
const { data } = this.state;
this.setState({
data: data.sort((a, b) => b - a),
})
}
render(){
const { data } = this.state;
return (
<div>
<button id="myBtn" onClick={() => this.sortData()}>Click Me</button>
<div className="myClass">
<FooComponent fooData={data} />
</div>
</div>
)
}
}

Using a functional component within a class

I'm wondering how I can create a stateless component within a class. Like if I use these functions outside the class, my page renders, but when I put them in the class. My page doesn't render. I want them to be inside the class so I can apply some class props to them.
class helloClass extends React.Component {
state = {
};
Hello =({ items}) => {
return (
<ul>
{items.map((item, ind) => (
<RenderHello
value={item.name}
/>
))}
</ul>
);
}
RenderHello = ({ value }) => {
return (
<div>
{open && value && (
<Hello
value={value}
/>
)}
</div>
);
}
render() {
}
}
export default (helloClass);
I have a setup like this. But not actually like this. And I keep getting the error that Hello and RenderHello do not exist. However, if I turn these into functions outside of the class, they work and everything renders on my page. I just want to know how I can achieve the same but within a class. If that's even possible.
Several ways of doing it, but the cleanist is to separate the stateless functions into it's their own files and have a single container that handles state and props and passes them down to the children:
Hello.js (displays the li items)
import React from 'react';
export default ({ items }) => (
<ul>
{items.map((item, ind) => (
<li key={ind}>
{item.name}
</li>
))}
</ul>
);
RenderHello.js (only returns Hello if open and value are true)
import React from 'react';
import Hello from './Hello';
export default ({ open, value, items }) => (
open && value
? <Hello items={items} />
: null
);
HelloContainer.js (contains state and methods to update the children nodes)
import React, { Component } from 'react';
import RenderHello from './RenderHello';
class HelloContainer extends Component {
state = {
items: [...],
open: false,
value: ''
};
...methods that update the state defined above (ideally, these would be passed down and triggered by the child component defined below)
render = () => <RenderHello {...this.state} />
}
Its strange because you have a recursive call that will end up in a infinite loop, but syntactically, it would be something like that:
class helloClass extends React.Component {
state = {
};
Hello(items) {
return (
<ul>
{items.map((item, ind) => (
{this.RenderHello(item.name)}
))}
</ul>
);
}
RenderHello(value) {
return (
<div>
{open && value && (
{this.Hello(value)}
)}
</div>
);
}
render()
{
}
}
export default (helloClass);

Categories

Resources