Is passing components as props considered bad practice? - reactjs

As the headline states: is something like the pseudo code below considered bad?
<Outer
a = { ComponentA }
b = { ComponentB }
/>
var Outer = (props) => {
var ComponentA = props.a;
var ComponentB = props.b;
// do fancy stuff
// ...
return (
<div>
<ComponentA { ...fancypropsForA } />
<ComponentB { ...fancypropsForB } />
</div>
);
}
As an example: I'm using it to display tree data in different ways by passing a component that will render the data of a single node.
EDIT
As requested, I will try to make my question a little more clear.
There is a component that has some logic and some markup that is the same every time you use this component. But there are two (or more) places in that markup that should be replacable.
Picture a calendar that displays a whole month. There is a component that renders an individual date, and one that renders the weekday (in the bar at the top).
You want to reuse that calendar in multiple places, but you need different markup for the date/weekday components each time.
One way to achieve this is:
<Calendar
data={ data }
weekdayComponent={ MyWeekDayComponent }
dateComponent={ MyDateComponent }
/>
<Calendar
data={ data }
weekdayComponent={ SomeOtherWeekDayComponent }
dateComponent={ SomeOtherDateComponent }
/>
So, i found that this works. But I'm not sure if that is actually bad.

As long as data flows only in one direction, you're generally OK. Your example is a little contrived, so it's hard to see what general problem you're trying to solve, but I think what you're actually looking for are Higher Order Components.
https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e

Related

React: best practice for state control

Lately I've noticed that I'm writing a lot of React code were I have 2 components:
Data filter
Data display
In the first component I might have N possible filters to apply to the data. These filters often complex components on their own. When the user defines the filters I need to pass it from 1st component to the 2nd for display.
Example: https://codesandbox.io/s/wonderful-sutherland-16zhp8?file=/src/App.js
What I see in many cases to happen is that I manage the state of the filter in multiple places.
In the example above, I have it in the Toolbar component and in the parent one.
What is the best way of managing states in such case?
You should definitely never duplicate the state in multiple places as this can create bugs where they get out of sync. The solution is to hoist state and then pass it down via props, see https://beta.reactjs.org/learn/sharing-state-between-components.
Here is your codesandbox modified as an example: https://codesandbox.io/s/keen-wilbur-0xyi8f
You are duplicating the sate multiple times, plus there are some unnecessary helper functions along the way.
The state can be held in one place, with its setter function passed to the toolbar component, like so:
export default function App() {
const [searchStr, setSearchStr] = useState("");
return (
<div className="App">
<h1>Tony Starks Nicknames</h1>
<Toolbar searchStr={searchStr} onSearchStrChange={setSearchStr} />
<List searchStr={searchStr} />
</div>
);
}
Then, the toolbar can look like this:
function Toolbar({ searchStr, onSearchStrChange }) {
const handleChange = (e) => {
onSearchStrChange(e.target.value);
};
return (
<div>
<input
type="text"
onChange={handleChange}
value={searchStr}
placeholder="search..."
/>
</div>
);
}
This way, there's only one place where the state is stored
If your hierarchy's depth is or might become greater than 2, I would suggest the Context API.
https://reactjs.org/docs/context.html

What is the best way to add additional input field on a button click?

I am wondering what is the standard way to add additional input field from onClick() on a button. A scenario might be adding a Todo-list item field on click.
My approach was having a state array that stores the actual component and concat the array.
const[ComponentList, setComponentList] = useState([<Component id={1}/>]);
And then...
function addAnotherQuestion() {
setComponentList(ComponentList.concat(generateComponent(currentID)));
}
I was told this is a very bad idea and I would end up with messed up inputs because of stale states (which I did, and then I solved by writing to Redux store directly from the child component). This is not an ideal solution, so I want to know what is the standard way to do this?
I would store only inputs data in array like so:
const [inputs, setInputs] = useState(["some_id_1", "some_id_2", "some_id_3"]);
function addAnotherQuestion() {
setInputs(inputs.concat(currentID));
}
and then render them separately:
<>
{ inputs.map((id) => <Component key={id} id={id}/>) }
</>
How about keeping an array of Id's instead of components?
const[ComponentListIds, setComponentListIds] = useState([1]);
Why do you need to generate the components? What you should probably do, is generating new Id's instead. And then render the components within the render part of you component with:
render(
...
ComponentListIds.map(id=> <Component key={id} id={id}>)
...
)

React Stateless Components: Interacting with their output and appearance

I have looked around for an answer to this - the closest I found being this question - but there is I think a significant difference in my case (the fact that it starts to get into the parent holding the state of its children's... children) which has finally lead to me asking for some clarification.
A very simple example of what I mean is below (and will hopefully better illustrate what I'm asking):
Suppose we have a bunch of book documents like
bookList = [
{
title: "book 1",
author: "bob",
isbn: 1,
chapters: [
{ chapterNum: 1, chapterTitle: "intro", chapterDesc: "very first chapter", startPg: 2, endPg: 23 },
{ chapterNum: 2, chapterTitle: "getting started", chapterDesc: "the basics", startPg: 24, endPg: 45 }
]},
{
title: "book 2" ... }
]
So main point being these embedded objects within documents that could be very long and as such may be collapsed / expanded.
And then here is a rough sample of code showing the components
class App extends Component {
constructor(props) {
super(props);
this.state = {
books: bookList,
focusBook: null
}
this.updateDetailDiv = this.updateDetailDiv.bind(this);
}
updateDetailDiv(book) {
this.setState(
{ focusBook: book}
);
}
render() {
return(
<BookList
bookList = {this.state.books}
updateDetailDiv = { this.updateDetailDiv }
/>
<BookDetail
focusBook = { this.state.focusBook }
/>
);
}
}
const BookList = props => {
return (
props.bookList.map(item=>
<li onClick={()=> props.updateDetailDiv(item)}> {item.title} </li>
)
);
}
const BookDetail = props => {
return (
<div className="bookDetails">
{ props.focusBook != null
? <div>
{props.focusBook.title},
{props.focusBook.author},
{props.focusBook.isbn}
Chapters:
<div className="chapterList">
{ props.focusBook.chapters.map(item=>
<span onClick={()=>someFunction(item)}>{item.chapterNum} - {item.chapterName}</span>
)}
</div>
<div id="chapterDetails">
This text will be replaced with the last clicked chapter's expanded details
</div>
</div>
: <div>
Select A Book
</div>
})
}
someFunction(item) {
document.getElementById('chapterDetails').innerHTML = `<p>${item.chapterDesc}</p><p>${item.startPg}</p><p>${item.endPg}</p>`;
}
So my problem is that i'm not sure what the best approach is for handling simple cosmetic / visual changes to data in functional stateless components without passing it up to the parent component - which is fine and makes sense for the first child - but what happens when many children will have their own children (who may have their own children) --> all requiring their own rendering options?
For example - here the App component will re-render the DetailDiv component (since the state has changed) - but I don't want the App also handling the DetailDiv's detailed div. In my example here its all very simple but the actual application I'm working on has 2 or 3 layers of embedded items that - once rendered by App - could realisticially just be modified visually by normal JS.
SO in my example you'll see I have a someFunction() in each Chapter listing - I can make this work by writing a separate simple 'traditional JS DOM function' (ie: target.getElementById or closest() -- but i don't think i'm supposed to be using normal JS to manipulate the DOM while using React.
So again to summarize - what is the best way to handle simple DOM manipulation to the rendered output of stateless components? Making these into their own class seems like overkill - and having the 'parent' App handle its 'grandchildren' and 'great-grandchildren's state is going to be unwieldy as the Application grows. I must be missing an obvious example out there because I haven't seen much in the way of handling this without layers of stateful components.
EDIT for clarity:
BookDetail is a stateless component.
It is handed an object as a prop by a parent stateful component (App)
When App's state is changed, it will render again, reflecting the changes.
Assume BookDetail is responsible for displaying a lot of data.
I want it so each of the span in BookDetail, when clicked, will display its relevant item in the chapterDetail div.
If another span is clicked, then the chapterDetail div would fill with that item's details. (this is just a simple example - it can be any other pure appearance change to some stateless component - where it seems like overkill for a parent to have to keep track of it)
I don't know how to change the UI/appearance of the stateless component after it is rendered without giving it state OR making the parent keep track of what is essentially a 'substate' (since the only way to update the appearance of a component is to change its state, triggering a render).
Is there a way to do this without making BookDetail a stateful component?
You can add a little bit of simple state to functional components to track the selected index. In this case I would store a "selected chapter index" in state and then render in the div the "chapters[index].details", all without manipulating the DOM which is a React anti-pattern.
The use-case here is that the selected chapter is an internal detail that only BookDetail cares about, so don't lift this "state" to a parent component and since it is also only relevant during the lifetime of BookDetail it is rather unnecessary to store this selected index in an app-wide state management system, like redux.
const BookDetail = ({ focusBook }) => {
// use a state hook to store a selected chapter index
const [selectedChapter, setSelectedChapter] = useState();
useEffect(() => setSelectedChapter(-1), [focusBook]);
if (!focusBook) {
return <div>Select A Book</div>;
}
const { author, chapters, isbn, title } = focusBook;
return (
<div className="bookDetails">
<div>
<div>Title: {title},</div>
<div>Author: {author},</div>
<div>ISBN: {isbn}</div>
Chapters:
<div className="chapterList">
{chapters.map(({chapterName, chapterNum}, index) => (
<button
key={chapterName}
onClick={() => setSelectedChapter(selectedChapter >= 0 ? -1 : index)} // set the selected index
>
{chapterNum} - {chapterName}
</button>
))}
</div>
// if a valid index is selected then render details div with
// chapter details by index
{chapters[selectedChapter] && (
<div id="chapterDetails">
{chapters[selectedChapter].details}
</div>
)}
</div>
</div>
);
};
DEMO
There is some approaches you can do to solve this problem.
First, you don't need to create some class components for your functional components, instead, you can use react hooks, like useState so the component can control it's own content.
Now, if you don't want to use React Hooks, you can use React Redux store to manage all your states: you can only change the state values using the Redux actions.
Happy coding! :D

Is it possible to pass components as properties and add interactions to them?

I am having the following class, that is supposed to handle a modal
class Modal extends React.Component {
constructor(props){
this.confirmBtn = props.confirmBtn || <button type="button">Confirm</button>
this.cancelBtn = props.confirmBtn || <button type="button">Cancel</button>
}
render(){
return (<div>
{this.props.children}
<div>{this.confirmBtn} {this.cancelBtn}</div>
</div>);
}
}
Since these buttons are components, they can do whatever they were supposed to do, but what I want to do is add the extra functionality of closing the modal.
Ideally I would like something like this:
Pseudocode
render(){
const ConfirmBtn = this.props.confirm;
const CancelBtn = this.props.cancel;
return (<div>
{this.props.children}
<div>
<ConfirmBtn onClick={this.close.bind(this)}/>
<CancelBtn onClick={this.close.bind(this)}/>
</div>);
}
I know it is possible to just keep the buttons internally and add callbacks to them, but I'm just wondering if this thing is even possible to do in React.
You code will work fine, assuming that this.props.confirm and this.props.cancel are components and not elements. As an example, Modal would be a component but <Modal/> is an element.
That said, I'm not personally a fan of passing components in this way. While it will work, you're not imposing any forced constraints on which kinds of Component is allowed to be passed in for confirm and cancel. Imagine passing in a textbox component. Or a button styled incorrectly that takes up too much space and ruins the style of the rest of the modal.
It would render, but.... eugh.
And sure, human testing should catch those issues. But if we need to rely on an actual human being, then we're susceptible to human error. We all hate human error, right?
Instead, I would suggest making a prop named buttonType. Use this prop as an enum to choose between whatever various button components you might wish to use. Have a default. e.g.
getButtonComponent() {
switch( this.props.buttonType ) {
case 'someType': return SomeType;
case 'anotherType': return AnotherType;
default: return DefaultType;
}
}
render() {
const ConfirmButton = this.getButtonComponent();
const CancelButton = this.getButtonComponent();
...

Split a very big react class

I have created a very big(500loc, It its very big and difficult to reason about) react class. Its an autocomplete. Whats the recommended way to split this up with react/reflux. Add the logic to som services? What is best practise. I have Stores but as I understand they shouldn't contain view logic.
It's difficult to be specific to your case given that you haven't provided the code behind your component, but if I were to develop an autocomplete component I would do it as follows.
Facebook's Thinking in React guidelines suggest to break down your UI into components that represent a single aspect of your data model:
In your case, you could implement the following hierarchy:
AutoComplete
|
--AutoCompleteInput
|
AutoCompleteResults (list of results)
|
--AutoCompleteResult (individual result)
So at a very high level...
AutoComplete.jsx:
[...]
render() {
return (
<div>
<AutoCompleteInput />
<AutoCompleteResults />
</div>
);
}
AutoCompleteInput.jsx:
[...]
updateQuery(e) {
// FYI - you should ideally throttle this logic
autoCompleteActions.updateQuery(e.target.value);
}
render() {
return <input onChange={this.updateQuery} />
}
AutoCompleteResults.jsx:
[...]
onStoreUpdated() { // hypothetically invoked when store receives new data
this.setState({
results: productListStore.getResults()
});
}
render() {
const { results } = this.state;
const children = results.map(result => {
return <AutoCompleteResult result={result} />
});
return (
<ul>
{children}
</ul>
);
}
You're correct to state that stores should not contain view logic, but they are allowed to store both general app state and data that's resulted from an XHR, for example. In the above example, the autoCompleteActions.updateQuery would consume a data service method to get the autocomplete options for the specific query. The success callback would then be responsible for passing the returned data to the store. The store should ideally notify subscribed components that it has received new data.
Although this is a simple overview, the result is that the components driving the AutoComplete functionality are broken down to respect single responsibilities, which should simplify your testing.

Resources