Is My Reactjs Components too Granular? - reactjs

I am playing around with Reactjs and I am wondering if I broken out my components too much.
<script type="text/babel">
class MainCompoent extends React.Component {
constructor() {
super();
this.state = {
items : [
{
name: 'Test 1',
type: { value: "1"},
types: [
{ value: "2"},
{ value: "1}
]
},
{
name: 'Test 2',
type: { value: "1"},
types: [
{ value: "2"},
{ value: "1}
]
},
]
};
}
componentDidMount() {
}
render() {
return (
<div className="item-container">
{
this.state.items.map((item, i) => {
return <Item item={item} index={i} />
})
}
</div>
)
}
}
class Item extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
render() {
return (
<div className="item">
<General item={this.props.item} index={this.props.index} />
<Specific1 />
<Specific2 />
</div>
)
}
}
class Specific1 extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
render() {
return (
<div className="Specific1-container">
<h1>Specific1 Container </h1>
</div>
)
}
}
class General extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
handleChange(event) {
this.props.handleChange(event.target.value, this.props.index);
}
handleChange2(event) {
this.props.handleChange2(event, this.props.index);
}
render() {
return (
<div className="general-container">
<h1>General Container </h1>
<div>
<label>Name</label>
<input type="text" value={this.props.item.name}/>
</div>
<div>
<label>Type </label>
<select value={this.props.item.type.value} onChange={(event) => this.handleChange(event)}>
{
this.props.item.types.map((type, i) => {
return <option key={'type-' + i} value={type.value} >{type.value}</option>;
})
}
</select>
</div>
</div>
)
}
}
class Specific2 extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
render() {
return (
<div className="specific2-container">
<h1>Specific2 Container </h1>
</div>
)
}
}
ReactDOM.render(<MainCompoent />, document.getElementById("Container"));
</script>
The above code I stripped out everything I don't think was necessary.
I am trying to do this without flux or redux.
In the Item container, there are 3 components that get rendered together. The general will always be shown but only 1 of the other 2 will ever be shown(despite my similar names they are different and have to be 2 separate components). All 3 components do share properties and hence why I put the state in the top and was passing it down.
The one thing though is when I have to update this state I potentially have to go up 2 parents(ie handelChange from General would have to go up to Item which would have to go to MainComponent to update the state).
Is this bad?

I'd stacked with similar question and found to my self a pretty simple answer. It all depends on what level of abstraction do you need.
Eg. if Item component could not "live" without General then the better way is to include General to Item. If they are totally independent then you could simply keep General props-API stable and change itself.
Passing props to the parent is OK, but the better way is to keep one source of truth. If you don't want to use any of data-flow libraries (redux, flux, whteverx) then simply make the Root component as the smartest. Let it to control of app-state changing.
Here is a nice "guide" how to let React components communicate with each other (https://facebook.github.io/react/docs/lifting-state-up.html). Parent to Child via Props, Child to Parent via Callbacks.

Related

Call method of all chlidren items in a list of React PureComponent

I'm new to React. I have a list component that is composed of an array of editable items and I wanted to use Pure Components for the item component.
I want to serialize the whole list calling a serialize method in every item of the list whitch returns an object.
Something like:
class List extends React.Component {
render() {
const items = [
{ name: 'first' },
{ name: 'second' }
];
const itemComponents = items.map((elem, index) => {
return <Item
key={index}
name={elem.name}></Item>
});
return <div>
{itemComponents}
<button onClick={this.serialize}>Serialize</button>
</div>
}
serialize() {
console.log(); //todo: serialized list??
}
}
class Item extends React.PureComponent<{ name: string }, { checked: boolean }> {
constructor(props: { name: string }) {
super(props);
this.state = { checked: false }
}
render() {
return <div>
<h3>{this.props.name}</h3>
<input
type='checkbox'
checked={this.state.checked}
onChange={() => this.setState(prevState => ({ checked: !prevState.checked }))} />
</div>
}
// cannot be accessed from parent
serialize() {
return {
name: this.props.name,
checked: this.state.checked
}
}
}
Looking around I found that a solution may be to 'lift the state' and have the event handling of the items defined in the List component. That may be fine in this example but my actual items are more complex and I wanted to avoid having one massive component.
Another solution that may be possible uses the useRef() hook but I get a compilation error with the PureComponent and defies the purpose of using them. As far as I am concerned the Item component is Pure since does not have side effects.
Am I missing something? Should I be doing things completely differently?

Passing functions to child components in React - this.props.handleChange is not a function?

Trying to create a simple todo list and I figure out how to pass the function from the parent component down to the child component without it throwing an error
App Component
class App extends React.Component {
state = {
todo: todoData.map(todo => {
return <TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
}),
count: 0,
}
handleChange = (id) => {
console.log(id)
}
render(){
return(
<div className="flex">
<Header />
<div className="todoList" >
{this.state.todo}
</div>
</div>
)
}
}
TodoItem component
class TodoItem extends React.Component {
render(){
console.log(this.props)
return(
<p className="todoItem" onClick={this.props.clickeds}>
<input type="checkbox" checked={this.props.todo.completed} onChange={() => this.props.handleChange(this.props.todo.id)} />
{this.props.todo.text}
</p>
)
}
}
I'm trying to mess with the onChange handler in the TodoItem component, but I keep getting the same error that this.props.handleChange is not a function
Todo just for reference
todoData = {
id: 2,
text: "Grocery Shopping",
completed: false
},
What am I doing wrong?
When I change the handleChange function to NOT an arrow function in the app component, it works. (handleChange(id)). If I change this function to an arrow function (handleChange = (id) => { } ) I run into this error.
I recommend you use ES6's class syntax for now (which I have added below) as this is how most React tutorials —including the official ones— are written.
The data structure you should keep in the state should be an Array of Todo Objects.
You don't need to keep the components inside the state, simply iterate them on render (and don't worry about its performance, React won't recreate the HTML dom by doing this, so the render will be very efficient).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos : todoData,
count : 0,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
const todos = this.state.todos.map(todo => (
(todo.id === id)
? Object.assign(todo, { completed: !todo.completed }
: todo
));
this.setState({ todos });
}
render() {
// ...
this.state.todos.map(todo => (
<TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
);
}
}
// todoData should probably be an array in case you want to include more by default:
todoData = [
{
id: 2,
text: "Grocery Shopping",
completed: false
},
{
id: 2,
text: "Grocery Shopping",
completed: false
}
];
Be careful about mutating the state
The reason for the ugly and confusing map() and Object.assign() inside handleChange is because you cannot do a shallow copy, nor edit the array directly:
this.state.todos[i].completed = true // ! mutates the state, no good
Preferably Lodash's library or even better, an immutable library or memoized selectors would do the trick of setting a todo as completed in a much nicer fashion.
Without deep cloning or immutability, you would need to copy the object, clone it, then create a new array with the new object, and assign that array to the state.

How to scroll to bottom when props changed in react-virtualized?

I have component App with List from react-virtualized library.
And I need on initial render, that my List scroll to bottom.
And I did it, when added scrollToIndex option. But when I add new object in my list array, it does not scroll to my last added object. How can I fix it? And is it good solution to use "forceUpdate()" function?
import { List } from "react-virtualized";
import loremIpsum from 'lorem-ipsum';
const rowCount = 1000;
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
class App extends Component {
constructor() {
super();
this.renderRow = this.renderRow.bind(this);
this.list = Array(rowCount).fill().map((val, idx) => {
return {
id: idx,
name: 'John Doe',
image: 'http://via.placeholder.com/40',
text: loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound: 4,
sentenceUpperBound: 8
})
}
});
}
handle = () => {
this.list = [...this.list, { id: 1001, name: "haha", image: '', text: 'hahahahahaha' }];
this.forceUpdate();
this.refs.List.scrollToRow(this.list.length);
};
renderRow({ index, key, style }) {
console.log('____________', this.list.length);
return (
<div key={key} style={style} className="row" >
<div className="image">
<img src={this.list[index].image} alt="" />
</div>
<div onClick={this.handle}>{this.state.a}</div>
<div className="content">
<div>{this.list[index].name}</div>
<div>{this.list[index].text}</div>
</div>
</div>
);
}
render() {
return (
<div className="App">
<div className="list">
<List
ref='List'
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow}
rowCount={this.list.length}
overscanRowCount={3}
scrollToIndex={this.list.length}
/>
</div>
</div>
);
}
}
export default App;
You mentioning you need to scroll to the bottom when the list item is changed and to be honest i don't like to use forceUpdate. As mentioned on the React docs:
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
Luckily, one of React lifecycle method is suitable for this case, it is call componentDidUpdate. But you need to do some refactor of your code. Instead using private field, i suggest to put it on state/props.
This method will invoked immediately after updating props/state occurs. However, This method is not called for the initial render.
What you need to do is, compare the props, is it change or not? Then call this.refs.List.scrollToRow(this.list.length);
Sample code
class App extends Component {
constructor() {
this.state = {
list: [] // put your list data here
}
}
// Check the change of the list, and trigger the scroll
componentDidUpdate(prevProps, prevState) {
const { list } = this.state;
const { list: prevList } = prevState;
if (list.length !== prevList.length) {
this.refs.List.scrollToRow(list.length);
}
}
render() {
// usual business
}
}
more reference for React lifecyle methods:
https://reactjs.org/docs/react-component.html#componentdidupdate

Why does my child React component need to be wrapped inside a function to detect parent state change?

This is my working code, and below it is what I'd like my code to look like but doesn't work:
I'd like my MainComponent's selectDay method to change TableComponent's urls for fetching data based on which day is selected
class MainComponent extends React.Component{
constructor(props) {
super(props);
this.state = {
urls: [{
appointmentsUrl: "/api/appointments",
callsUrl: "/api/calls"
}]
}
}
selectDay(day) {
if(day.key == 1) {
this.setState({
urls: [{
appointmentsUrl: "/api/appointments",
callsUrl: "/api/calls"
}]
})
} else {
this.setState({
urls: [{
appointmentsUrl: "/api/appointments2",
callsUrl: "/api/calls2"
}]
})
}
}
render() {
var calendarData = [//init some array of dates];
return (
<div>
<TopMenuComponent/>
<div className="contentContainer">
{
((urls)=>{
return(
<TableComponent key={urls[0].appointmentsUrl} appointmentsUrl={urls[0].appointmentsUrl} callsUrl={urls[0].callsUrl}/>
)
})(this.state.urls)
}
</div>
</div>
)
}
This works, but what I would like is something like is something like:
<TableComponent appointmentsUrl={this.state.appointmentsUrl} callsUrl={this.state.callsUrl}/>
And initialize the state as: this.state= {appointmentsUrl: "/api/appointments",
callsUrl: "/api/calls"}
If you have your state in the structure you represented in the question, and you want to use the first object in that array (assuming you have the destructuring syntax enabled):
function render() {
const [firstUrlObj] = this.state.urls
return (
<div>
<TopMenuComponent/>
<div className="contentContainer">
<TableComponent appointmentsUrl={firstUrlObj.appointmentsUrl} callsUrl={firstUrlObj.callsUrl}/>
</div>
</div>
)
}
If you want to render the whole array:
function render() {
const urls = this.state.urls
return (
<div>
<TopMenuComponent/>
<div className="contentContainer">
{ urls.map(urlObj => <TableComponent appointmentsUrl={urlObj.appointmentsUrl} callsUrl={urlObj.callsUrl}/>) }
</div>
</div>
)
}
If you have a single object in the array and you don't plan to have more, I suggest looking at the other answer.

How to call parents method in React without passing it throught props

So i'm building a custom select field. The problem is that the select wrapper and options are different components, so the code for creating such select will be:
<SelectComponent onChange={usersFunction}>
<OptionComponent value="value 1" />
<OptionComponent value="value 2" />
<OptionComponent value="value 3" />
</SelectComponent>
More specifically the problem is that i don't know how to let the SelectComponent know when the option was clicked and witch option was clicked (i don't want to pass any other functions into the code above. I want it only to have onChange function).
What i'm doing right now is in SelectComponent render function i'm wrapping each child in props.children into another div witch has onClick property.
Something like this:
render() {
return {
this.props.children.map(function(item, index){
return (
<div key={index} onClick={this.handleClickMethod.bind(this, item, index)}>{item}</div>
)
})
}
}
Although this is kind of working i'm not really satisfied with the solution. May be there are any other more "react" solutions?
You can use React.cloneElement() to add additional props to children, like so:
render() {
let self=this;
return (
{self.props.children.map(child, index) => {
return React.cloneElement(child, {
onClick: self.handleClickMethod.bind(self, child, index)
})
})}
)
}
Then you can add the handler to the props, without the additional wrapper <div>
UPDATE: Alternatively, you can simply pass the options as an array (instead of as children), like so:
<SelectComponent onChange={usersFunction} options={['value 1','value 2', 'value 3']}>
And build your options component dynamically.
From the official page (on context in react):
We're fond of simply passing the items as an
array
You can do something like,
import React from 'react'
import ReactDOM from 'react-dom'
class OptionComponent extends React.Component{
constructor(props) {
super(props);
}
handleClick(){
this.props.handler(this.props.index);
}
render() {
return (
<div className={this.props.isChecked ? "optionbtn selected" : "optionbtn"} onClick={this.handleClick.bind(this)} data-value={this.props.value}>
<label>{this.props.text}</label>
</div>
);
}
}
class SelectComponent extends React.Component{
constructor() {
super();
this.state = {
selectedIndex: null,
selectedValue: null,
options: ["Option 0","Option 1","Option 2","Option 3"]
};
}
toggleRadioBtn(index){
if(index == this.state.selectedIndex)
return;
this.setState({
selectedIndex: index,
selectedValue: this.state.options[index],
options: this.state.options
});
this.props.onChange.call(this.state.options[index]);
}
render() {
const { options } = this.state;
const allOptions = options.map((option, i) => {
return <OptionComponent key={i} isChecked={(this.state.selectedIndex == i)} text={option} value={option} index={i} handler={this.toggleRadioBtn.bind(this)} />
});
return (
<div data-value={this.state.selectedValue}>{allOptions}</div>
);
}
}
var app = document.getElementById('app');
ReactDOM.render(<SelectComponent onChange={usersFunction}/>, app);
Make method available via context. Require that method in child component and call it.
https://facebook.github.io/react/docs/context.html
Redux is based on this principle.

Resources