What is the better way to pass a component as props? - javascript

What is the better way to pass a component in props? And how it's better to call it as Component or as a function. Or there are no differences?
const HeaderComponet = props => {
return <div style={styles.header}>Header</div>;
};
const renderCardBody = props => {
return <div style={styles.body}>Body</div>;
};
function App() {
return (
<Card HeaderComponet={HeaderComponet} renderCardBody={renderCardBody} />
);
}
const Card = props => {
const { HeaderComponet, renderCardBody } = props;
return (
<div>
<p>Card</p>
{HeaderComponet && <HeaderComponet {...props} />}
{renderCardBody && renderCardBody(props)}
</div>
);
};
codesandbox https://codesandbox.io/s/hungry-violet-jlvsp

I would simply render it as Child Component
<Card>
<HeaderComponent {...props}/>
<RenderCardBody {...props}/>
</Card>

how it's better to call it as Component or as a function
In theory, both are same. JSX we write is converted to a function based JS and is used in that way.
Sample:
Class Component used as function
Function Component as a component
So which to use?
Function: If there is a processing involved. Say based on 1 state, it should show banner. On another, a form. Yes, the logic can be moved to another component as used, however, if a component needs to be smart and do processing based on state, such processing should be done in a function
Component: If its directly consumed. Like an form component wrapping multiple component to create a structure.
What is the better way to pass a component in props?
In my POV, you should not pass components as props. A component should know what its consuming. Still if you want to have dynamic component, I'd do in following manner.
Create a list of possible components that can be used and based on that create a map.
Create props getter function so you can dynamically use it.
Now based on props, get the component and props, and do rendering.
Sample:
What is the better way to pass a component in props?
Note: The Tile components are just for demo and hence they are very basic. In reality, they can be more complicated
const DefaultTile = (props) => Array.from({ length: props.count }, (_, i) => <div className='tile medium-tile' key={i}>Tile</div>)
const LargeTile = (props) => Array.from({ length: props.count }, (_, i) => <div className='tile large-tile' key={i}>Tile</div>)
const SmallTile = (props) => Array.from({ length: props.count }, (_, i) => <div className='tile small-tile' key={i}>Tile</div>)
const componentMap = {
large: LargeTile,
medium: DefaultTile,
small: SmallTile
}
const renderPropMap = {
large: (props) => ({ count: props.count, showExtraInfo: props.info }),
medium: (props) => ({ count: props.count }),
small: (props) => ({ count: props.count, onHover: props.onHover })
}
const App = (props) => {
const Comp = componentMap[props.size];
const childProps = renderPropMap[props.size](props)
return <Comp { ...childProps } />
}
ReactDOM.render(<App size='medium' />, document.querySelector("#app"))
.tile {
border: 1px solid gray;
display: inline-block;
margin: 4px;
}
.small-tile {
width: 50px;
height: 50px;
}
.medium-tile {
width: 100px;
height: 100px;
}
.large-tile {
width: 200px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

Best Practice to Implement React Parent-Child Components Using Hooks

I'm picking up React and not sure if I'm doing this correctly. To preface the question I've read all about the React hooks; I understand them in isolation but am having trouble piecing them together in a real-life scenario.
Imagine I have a Parent component housing a list of Child components generated via a map function on the parent:
<Parent>
{items.map(i => <Child item={i} />)}
</Parent>
And say the Child component is just a simple:
function Child({item}) {
return <div>{item}</div>
}
However the Child component needs to update its view, and be able to delete itself. My question is - should I call useState(item) on the child so it internally has a copy of the item? In that case if I updated the item the items list in the parent wouldn't get updated right? To fix that I ended up having something that looks like:
<Parent>
{items.map(i =>
<Child
item={i}
updateItem={(index) => setItems( /* slice and concat items list at index */ )}
deleteItem={(index) => setItems( /* slice items list at index */ )}
/>)
}
</Parent>
And the Child component simply invokes updateItem and deleteItem as appropriate, not using any React hooks.
My question here are as follows:
should I have used useState in the child component?
should I have used useCallback on the updateItem/deleteItem functions somehow? I tried using it but it didn't behave correctly (the correct item in the Parent got removed but the state in the remaining rendered Child were showing values from the deleted Child for example.
My understanding is that this would be very inefficient because an update on 1 child would force all other children to re-render despite them not having been updated.
If done most properly and efficiently, what should the code look like?
Thanks for the pointers.
should I have used useState in the child component?
Usually duplicating state is not a good idea; so probably no.
should I have used useCallback on the updateItem/deleteItem functions
somehow
You might need it if you want to pass those callbacks to components wrapped in React.memo.
My understanding is that this would be very inefficient because an
update on 1 child would force all other children to re-render despite
them not having been updated
Yes your understanding is correct, but whether you would notice the slow down, depends on number of things such as how many child components there are, what each of them renders, etc.
If done most properly and efficiently, what should the code look like?
See below. Notice I added React.memo which together with useCallback should prevent those items from re rendering, props of which didn't change.
const Child = React.memo(function MyComponent({ item, update }) {
console.log('Rendered', item);
return (
<div
onClick={() => {
update(item);
}}
>
{item.name}
</div>
);
});
let itemsData = [
{ id: 0, name: 'item1' },
{ id: 1, name: 'item2' },
];
export default function App() {
let [items, setItems] = React.useState(itemsData);
let update = React.useCallback(
(item) =>
setItems((ps) =>
ps.map((x) => (x.id === item.id ? { ...x, name: 'updated' } : x))
),
[]
);
return (
<div>
{items.map((item) => (
<Child key={item.id} item={item} update={update} />
))}
</div>
);
}
Now if you click item1, console.log for item2 won't be called - which means item2 didn't rerender
No you don't have to create internal state. That's an anti pattern to create a local state just to keep a copy of props of the component.
You can keep your state on parent component in your case. Your child component can execute callbacks like you used,
for example,
const [items, _] = useState(initialItemArray);
const updateItem = useCallback((updatedItem) => {
// do update
}, [items])
const deleteItem = useCallback((item) => {
// do delete
}, [items])
<Child
data={item}
onUpdate={updateItem}
onDelete={deleteItem}
/>
Also note you shouldn't over use useCallback & useMemo. For example, if your list is too large and you use useMemo for Child items & React re renders multiple 100 - 1000 of list items that can cause performance issue as React now have to do some extra work in memo hoc to decide if your <Child /> should re render or not. But if the Child component contain some complex UI ( images, videos & other complex UI trees ) then using memo might be a better option.
To fix the issue in your 3rd point, you can add some unique key ids for each of your child components.
<Child
key={item.id} // assuming item.id is unique for each item
data={item}
onUpdate={(updatedItem) => {}}
onDelete={(item) => {}}
/>
Now react is clever enough not to re render whole list just because you update one or delete one. This is one reason why you should not use array index as the key prop
#Giorgi Moniava's answer is really good. I think you could do without useCallback as well and still adhere to best practices.
const {useEffect, useState} = React;
const Child = ({ item, update }) => {
const [rerender, setRerender] = useState(0);
useEffect(() => setRerender(rerender + 1), [item]);
useEffect(() => setRerender(rerender + 1), []);
return (
<div className="row">
<div className="col">{item.id}</div>
<div className="col">{item.name}</div>
<div className="col">{item.value}</div>
<div className="col">{rerender}</div>
<div className="col">
<button onClick={update}>Update</button>
</div>
</div>
);
}
const Parent = () => {
const [items, setItems] = useState([
{
id: 1,
name: "Item 1",
value: "F17XKWgT"
},
{
id: 2,
name: "Item 2",
value: "EF82t5Gh"
}
]);
const add = () => {
let lastItem = items[items.length - 1];
setItems([
...items,
{
id: lastItem.id + 1,
name: "Item " + (lastItem.id + 1),
value: makeid(8)
}
]);
};
const update = (sentItem) => {
setItems(
items.map((item) => {
if (item.id === sentItem.id) {
return {
...item,
value: makeid(8)
};
}
return item;
})
);
};
const makeid = (length) => {
var result = "";
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
return (
<div className="parent">
<div className="header">
<h1>Parent Component</h1>
<h2>Items in Parent State</h2>
</div>
<div className="table">
<section>
<header>
<div className="col">ID</div>
<div className="col">NAME</div>
<div className="col">VALUE</div>
</header>
{items.map((item, i) => (
<div className="row" key={item + "-" + i}>
<div className="col">{item.id}</div>
<div className="col">{item.name}</div>
<div className="col">{item.value}</div>
</div>
))}
</section>
<div className="header">
<h1>Children</h1>
<h2>Based on Items state</h2>
</div>
<button onClick={add}>Add</button>
<section>
<header>
<div className="col">ID</div>
<div className="col">Name</div>
<div className="col">Value</div>
<div className="col">Re-render</div>
<div className="col">Update</div>
</header>
{items.map((item, i) => (
<Child
item={item}
key={"child-" + item + "-" + i}
update={() => update(item)}
/>
))}
</section>
</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
.parent {
font-family: sans-serif;
}
.header {
text-align: center;
}
section {
display: table;
width: 100%;
}
section > * {
display: table-row;
background-color: #eaeaea;
}
section .col {
display: table-cell;
border: 1px solid #cccccc;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

how to pass arguments up via props in functions with React?

In a class, I would write: this.prop.bind(this,arg)
like this:
<button onClick={this.props.delete.bind(this, id)} id='deletebtn'>
X
</button>
but how would I do the same thing in a function?
I need to use a class or there is a way to do it without it?
ps: did hooks killed classes? if not when are classes really necessary?
You want to create a function that returns a function, like this:
function deleteItem(id) {
return function() {
... <using `id`>
}
}
or with arrow functions:
const deleteItem = (id) => {
return () => {
... <using `id`>
}
}
or even shorter:
const deleteItem = (id) => () => {
... <using `id`>
}
and then you do
<button onClick={deleteItem(id)} id='deletebtn'>
where deleteItem(id) is now a function.
To answer your question about hooks, they did not "kill classes" but provide a arguably better alternative only for React components. Class components are still supported. It is hard to imagine a situation that cannot be handled by the new functional components with hooks, though.
You can pass the removeItem handler down to the child component -
const { useState } = React
const initialItems =
[ "muffins", "cake", "pies" ]
const MyApp = ({ init = initialItems }) => {
const [items, setItems] = useState(init)
const removeItem = (pos = 0) => event =>
setItems([...items.slice(0, pos), ...items.slice(pos + 1)])
const addItem = event =>
event.key === "Enter"
? setItems([...items, event.target.value ])
: null
return <div>
{items.map((name, i) =>
<Item name={name} onDelete={removeItem(i)} />
)}
<div className="item">
<input onKeyDown={addItem} placeholder="Type here and press [ENTER]..." />
</div>
</div>
}
const Item = ({ name = "", onDelete }) =>
<div className="item">
{name}
<button onClick={onDelete}>🗑️</button>
</div>
ReactDOM.render(<MyApp />, document.body)
body {
font-family: monospace;
}
.item {
background-color: ghostwhite;
padding: 0.5rem;
}
input {
display: block;
box-sizing: border-box;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
Okay, I found a way,
Like #JulienD Said it, I just need it to create a function that returns a function
this is how I did it.
the child sends the argument to the parent
<button onClick={() => {props.delPost(id)}}>delete</button>
the parent sends the argument up one more level
<Post delPost={(id)=>{props.delPost(id)}} key={post.id} postItem={post} />)
finally the root parent make use of the argument
const deletePost = (id) => {
console.log(id);
}
<Feed delPost={deletePost} posts={posts} />

Toggling visibility of array of stateless react components

I am trying to simply map over some data returned from an api and create a stateless component for each object returned. I want to be able to click on any of the components to toggle visibility of the rest of its data.
I have tried numerous ways to do it and keep hitting a brick wall, i've also scoured stack overflow and cannot seem to find an answer.
I have gotten it working by making them individual class components, however it seems like a lot of unnecessary code for just a toggle functionality.
Thank you in advance for any help or insight, here is a quick breakdown of what I have currently.
For clarification this is a simple app for me to learn about using react and an external api, it is not using redux.
fetched users in state of class component
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: []
};
}
async componentDidMount() {
let fetchedData = await API_Call("people");
this.setState({ resource: fetchedData.results });
while (fetchedData.next) {
let req = await fetch(fetchedData.next);
fetchedData = await req.json();
this.setState({
resource: [...this.state.resource, ...fetchedData.results]
});
}
}
}
Then map over the results and render a component for each result
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person key={i} {...person} />
));
return <div>{mappedPeople}</div>;
}
Is there i can make each person component a stateless component with the ability to click on it and display the rest of the data? Here is what I have currently.
class Person extends Component {
constructor(props) {
super(props);
this.state = {
visibility: false
};
}
toggleVisible = () => {
this.setState(prevState => ({
visibility: !prevState.visibility
}));
};
render() {
return (
<div>
<h1 onClick={this.toggleVisible}>{this.props.name}</h1>
{this.state.visibility && (
<div>
<p>{this.props.height}</p>
</div>
)}
</div>
);
}
}
Again thanks in advance for any insight or help!
You could keep an object visible in your parent component that will have keys representing a person index and a value saying if the person is visible or not. This way you can toggle the person's index in this single object instead of having stateful child components.
Example
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: [],
visible: {}
};
}
// ...
toggleVisibility = index => {
this.setState(previousState => {
const visible = { ...previousState.visibile };
visible[index] = !visible[index];
return { visible };
});
};
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person
key={i}
{...person}
visible={this.state.visible[i]}
onClick={() => this.toggleVisibility(i)}
/>
));
return <div>{mappedPeople}</div>;
}
}
const Person = (props) => (
<div>
<h1 onClick={props.onClick}>{props.name}</h1>
{props.visible && (
<div>
<p>{props.height}</p>
</div>
)}
</div>
);
Similar idea with #Tholle but a different approach. Assuming there is an id in the person object we are changing visibles state and toggling ids.
class PersonList extends React.Component {
constructor(props) {
super(props)
this.state = {
resource: this.props.persons,
visibles: {},
}
}
toggleVisible = id => this.setState( prevState => ({
visibles: { ...prevState.visibles, [id]: !prevState.visibles[id] },
}))
render() {
const mappedPeople =
this.state.resource.map((person, i) =>
<Person
key={person.id}
visibles={this.state.visibles}
toggleVisible={this.toggleVisible}
{...person}
/>
)
return (
<div>
{mappedPeople}
</div>
)
}
}
const Person = (props) => {
const handleVisible = () =>
props.toggleVisible( props.id );
return (
<div>
<h1 onClick={handleVisible}>
{props.name}</h1>
{props.visibles[props.id] &&
<div>
<p>{props.height}</p>
</div>
}
</div>
);
}
const persons = [
{ id: 1, name: "foo", height: 10 },
{ id: 2, name: "bar", height: 20 },
{ id: 3, name: "baz", height: 30 },
]
const rootElement = document.getElementById("root");
ReactDOM.render(<PersonList persons={persons} />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can make sure your "this.state.resource" array has a visibility flag on each object:
this.state.resource = [
{ ..., visibility: true },
{ ..., visibility: false}
...
];
Do this by modifying your fetch a little bit.
let fetchedData = await API_Call("people");
this.setState({
resource: fetchedData.results.map(p => ({...p, visiblity: true}))
});
Merge your Person component back into PersonList (like you are trying to do), and on your onclick, do this:
onClick={() => this.toggleVisible(i)}
Change toggleVisible() function to do the following.
toggleVisible = (idx) => {
const personList = this.state.resource;
personList[idx].visibility = !personList[idx].visibility;
this.setState({ resource: personList });
}
So now, when you are doing:
this.state.resource.map((person, i) => ...
... you have access to "person.visibility" and your onclick will toggle the particular index that is clicked.
I think that directly answers your question, however...
I would continue with breaking out Person into it's own component, it really is good practice!
Other than better organization, one of the main reason is to avoid lamdas in props (which i actually did above). Since you need to do an onClick per index, you either need to use data attributes, or actually use React.Component for each person item.
You can research this a bit here:
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
BTW you can still create "components" that aren't "React.Component"s like this:
import React from 'react';
const Person = ({ exProp1, exProp2, exProp3}) => {
return <div>{exProp1 + exProp2 + exProp3}</div>
}
Person.propTypes = {
...
}
export default Person;
As you can see, nothing is inheriting from React.Component, so you are getting the best of both worlds (create components without creating "Components"). I would lean towards this approach, vs putting everything inline. But if your application is not extremely large and you just want to get it done, going with the first approach isn't terribly bad.

React - Dynamic creation of List item inside component

Is there any way to add dynamical li element into my ul list ?
I'd like add my li by clicking the button. Here is example code
class Component1 extends React.Component {
constructor() {
super();
}
add() {
let ul = document.getElementById('mylist');
let li = document.createElement('li');
li.appendChild(document.createTextNode({some_variables});
ul.appendChild(li);
}
render() {
return (
<a href="#" onClick={() => this.add()}>Add</a>
<ul id="mylist">
/* dynamic list ITEM */
</ul>
);
}
}
ReactDOM.render(<Component1 />, document.getElementById('root'));
Of course current function add() doesn't work on React
When using react we are not "touching" the DOM as we usually do with other libraries (like jQuery).
One of the best and core features of react is the virtual DOM, the Reconciliation & diffing algorithm
React builds and maintains an internal representation of the rendered
UI. It includes the React elements you return from your components.
This representation lets React avoid creating DOM nodes and accessing
existing ones beyond necessity, as that can be slower than operations
on JavaScript objects. Sometimes it is referred to as a “virtual DOM”
In react you create components (functions) that renders / returns a jsx (markup).
A simple example to your scenario could be:
const ListItem = ({ value, onClick }) => (
<li onClick={onClick}>{value}</li>
);
const List = ({ items, onItemClick }) => (
<ul>
{
items.map((item, i) => <ListItem key={i} value={item} onClick={onItemClick} />)
}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
fruites: ['Apple', 'Banana', 'Orange']
};
}
onClick = () => {
const { inputValue, fruites } = this.state;
if (inputValue) {
const nextState = [...fruites, inputValue];
this.setState({ fruites: nextState, inputValue: '' });
}
}
onChange = (e) => this.setState({ inputValue: e.target.value });
handleItemClick = (e) => {console.log(e.target.innerHTML)}
render() {
const { fruites, inputValue } = this.state;
return (
<div>
<input type="text" value={inputValue} onChange={this.onChange} />
<button onClick={this.onClick}>Add</button>
<List items={fruites} onItemClick={this.handleItemClick} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Inject new inline styles to a React HOC product

Using a common HOC pattern like so works beautifully. However, there are times when you really don't want a component to be wrapped and just want the same component you passed in extended a bit. That's what I'm struggling with here.
Wrapper HOC
const flexboxContainerStyles = {
display: 'flex',
flexDirection: 'row',
backgroundColor: 'pink',
}
let WrapInFlexContainer = FlexChild => class extends React.Component {
render(){
return (
<div className="flexContainer" style={flexboxContainerStyles} >
<FlexChild {...this.props} />
</div>
)
}
}
const Button = (props) => <button>{props.txt}</button>
let FlexButton = WrapInFlexContainer(Button);
The following examples result in a button with no style attributes.
Example 1.1: pass-through via createClass
function hocPassThroughViaClass(Component) {
return React.createClass({
render: function() {
return <Component {...this.props} style={flexboxContainerStyles}/>;
}
});
}
Example 1.2 pass-through via direct render
let hocPassThroughViaRender = Element => class extends React.Component {
render(){
return <Element {...this.props} className="flexContainer" style={flexboxContainerStyles} />
}
}
Example 2: create
function hocCreate(Component) {
return React.createClass({
render: function() {
const modifiedProps = Object.assign({}, {...this.props}, {...flexboxContainerStyles});
return React.createElement(Component, { ...modifiedProps });
}
});
}
Example 3: clone
function hocClone(Component) {
return React.createClass({
render: function() {
const modifiedProps = Object.assign({}, {...this.props}, {...flexboxContainerStyles});
return React.cloneElement(<Component {...modifiedProps } />);
}
});
}
// render examples
let HOCPassThroughViaClassButton = hocPassThroughViaClass(Button); // 1.1
let HOCPassThroughRenderButton = hocPassThroughViaRender(Button); // 1.2
let HOCCreatedButton = hocCreate(Button); // 2
let HOCClonedButton = hocClone(Button); // 3
From a couple of points I'm seeing here and there across the web, it doesn't seem like it's possible to return the same Component if it is an only child.
See: https://github.com/threepointone/glamor/blob/master/docs/createElement.md
https://discuss.reactjs.org/t/trying-to-do-a-reactdom-render-a-el-replacing-the-el-not-appending-to-it/2681/2
The following examples result in a button with no style attributes.
Isn't this happening because you're not passing the style prop along? Wouldn't this be fixed by doing this:
const Button = (props) => <button style={props.style}>{props.txt}</button>
Update:
HOCs don't magically apply props to children of the wrapped component, meaning that low level elements like <button /> or <div /> need props passed to them one way or another. You're passing props to <Button />, not <button />. You can however make an HOC that takes a basic element and adds whatever to it.
let hoc = element => (
class extends React.Component {
render() {
let { children, ...props } = this.props
return React.createElement(
element,
{ ...props, style: flexboxContainerStyles },
children,
)
}
}
)
Usage:
let FlexButton = hoc('button')
let App = props => <FlexButton>{props.txt}</FlexButton>
fiddle
That being said, you're not changing the API of the base component by passing along known props like style and className along. In fact it's a great way to make components more reusable without specifying implementation details.
// good!
let Button = ({ children, ...props }) => <button {...props}>{children}</button>

Categories