I am still fairly new to Reactjs and I am struggling with one thing.
I have built a small React app that consists of:
App.js at the very top –the only class component that controls all the states
3 stateless components ( no1 , no2, no3 rendered in App.js (all with smaller components inside them, but that’s irrelevant)
App.js state includes 3 arrays all of which are used by those 3 stateless components rendered.
And then, in App.js I have around 400 lines of methods where I am modifying state by calling setState. That’s quite long....
My question is: is there anyway to split this file? To move methods to their respective components: no 1, 2 and 3.
It seems impossible to me as having all the states in one class requires calling setState (having setState methods) in the same class only.
This might be a stupid question but:
Is it possible to modify state outside parent that holds this state (App.js), for instance, modify it in a stateless component no 1, and still keep parent updated about the change so that it can inform stateless components no 2 and no 3 about the change.
What’s the best practice in my case?
thanks
What i do for code/logic splitting is:
Keep the App.js as simple as possible
If there is a lot of logic in a file/Component, i split the file to index.js and Component.js and keep the logic in index.js and the JSX/HTML in the Component.js
In your case you can:
Remove all logic from App.js
Create 3 folders no1, no2 and no3
Inside each folder create two files - index.js and ComponentNo1.js etc
In the index.js create a Component with state and in ComponentNo1.js create a stateless component that returns the JSX/HTML.
This is what I would recommend. If you find it hard to do it, post your App.js logic so that I could help you do it.
Sure something like this is completely valid and is a common React practice.
I've written a sandbox to give you an idea of how this works:
https://codesandbox.io/s/zxl2owp2np
But to explain in detail, let's assume you have the following components:
class App extends React.Component {
state = {
value: 5
};
increaseValue = () => {
this.setState({
value: this.state.value + 1
});
};
render() {
return (
<div className="App">
<Example1 value={this.state.value} increaseValue={this.increaseValue} />
<Example2 value={this.state.value} />
<Example3 value={this.state.value} />
</div>
);
}
}
const Example1 = (props) => {
const value = props.value;
return (
<div>
I am Example1. The value is {value}
<button onClick={() => props.increaseValue()}>Click</button>
</div>
);
}
const Example2 = (props) => {
const value = props.value;
return (
<div>
I am Example2. The value is {value}
</div>
);
}
const Example3 = (props) => {
const value = props.value;
return (
<div>
I am Example3. The value is {value}
</div>
);
}
Our main App component has a state value of 5 that we pass into our 3 child components. The child components are all using that value to display data. In the App component, we defined a state-updating function called increaseValue, which we pass in to Example1 as property. Now Example1 can use increaseValue by calling props.increaseValue()
props.increaseValue() is bound to our App component's execution context. So it will update the value in App's component state. Now with the updated value, it gets shared across all our Example components for use.
Regarding your question about moving methods to the components that are using them. This is not a common practice. Typically you define the function inside the component where the state is going to be updated. Meaning, if you have a function that is meant to update the App component, it should be defined in the app component.
Related
I'm currently developing an app that uses React in some parts of its UI to implement some complex feature that are very interactive. Some of these parts of the app, that are in fact React components, have become very complex in size.
This has led me to organize the code by dividing them in multiple subcomponents (and those in their own subcomponents, and so forth). The main problem is that some of the grandchildren of the main component might have the need to modify the state of another grandchildren that they're not related to. To do so, I end up having to have the state in the common parent and passing a lot of event handlers (methods of the parent component) to their children, and those children would need to pass it to their grandchildren.
As you can imagine, this is becoming some kind of a mess.
// MyComponent.js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1, 2, 3, 4],
selected: '',
}
this.add = this.add.bind(this)
this.handleChange = this.handleChange.bind(this)
}
add() {
const newNumber = this.state.list[this.state.list.length - 1] + 1,
list = [...this.state.list, newNumber]
this.setState({list})
}
handleChange({target}) {
this.setState({
selected: target.value,
})
}
render() {
return (
<>
<List items={this.state.list} selected={this.state.selected} />
<Button onClick={this.add} />
<input type="text" value={this.state.selected} onChange={this.handleChange} />
</>
)
}
}
// Button.js
class Button extends React.Component {
render() {
return (
<button onClick={this.props.onClick}>Click me!</button>
);
}
}
// List.js
class List extends React.Component {
constructor(props) {
super(props)
this.refs = props.items.map(_ => React.createRef())
}
render() {
return (
<ul>
{this.props.items.map((item, key) =>
(<li ref={this.ref[key]} key={key}>{item}</li>)
)}
</ul>
);
}
}
In the previous dummy code you can see how I need to define the add() method in the MyCompoent component so that an action that happens in the Button component can modify what is being shown in List. Even tho this might seem like the obvious way to do it, my component has a big component tree, and a lot of methods, and most of then are lost in the tree, passing from parent to child until it reaches the component that should be expected.
I have done some research on the internet and it turns out this is a very common problem. In most sites, using Redux or other state management library is recommended. However, all the tutorials and guides I've seen that implement Redux with React seem to assume you're only using React to build your app, in Single Page Application sort of way. This is not my case.
Is there any way to share the state of a component to avoid this kind of problem? Is there, maybe, a way to use Redux multiple times for multiple components in the same app, where one store saves only the state for MyComponent and can be accessed by either List or any of its possible children?
Redux doesn't require your entire site to be in React. It implements a higher-level component that you can use with any React components even if they are embedded in another site.
You can look at React hooks to solve similar problems. Specifically, check out useContext() and useState().
You've used a lifting state up pattern in react in your example.
It's quite common you good approach but when you app is growing you need to pass all bunch of props throu the tree of components. It's difficult to maintain.
In this case you need to check out redux with separated store or useContext() hook.
I have a question about the constructor in React
Look at a example at https://codesandbox.io/s/lpr147kmyl
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class SimpleComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: props.number,
}
console.log('constructor', this.state.number)
}
render() {
return (
<div>
{this.props.number}
</div>
)
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {
showFirstComponent: true,
}
setInterval(() => (
this.setState(previousState => (
{ showFirstComponent: !previousState.showFirstComponent }
))
), 1000);
}
render(){
let c1 = <SimpleComponent number="1" />
let c2 = <SimpleComponent number="2" />
return (
<div className="App">
<h1>
{this.state.showFirstComponent ? c1 : c2}
</h1>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I checked the console log and saw only one log message.
I thought there should be two log messages there as I create two instances of SimpleComponent :
let c1 = <SimpleComponent number="1" />
let c2 = <SimpleComponent number="2" />
My question is:
When does React create new a component instance and calls the component's constructor?
Thanks,
According to the doc, constructor is only called before a component is mounted. If you add two more React Lifecycle functions to your code example: componentDidMount and componentWillUnmount, you'll see that the component only mounted once in the entire process, regardless of the times the state changes. If you then add shouldComponentUpdate to the component, you'll see that there is only one instance of SimpleComponent and the upper-level state change is shown as props change on SimpleComponent.
The reason why you don't see two instances mounting and unmounting here is that variables c1 and c2 are not instances of SimpleComponent as commonly and conveniently assumed. Instead, they are ReactElement, which is a description of the component instance.
The primary type in React is the ReactElement. It has four properties: type, props, key and ref. It has no methods and nothing on the prototype.
React (Virtual) DOM Terminology
Only when the element gets rendered, an instance of the component gets created and constructor and the Lifecycle functions get called at their respective time points.
So then why, as in your example, the element c2 never gets to be used to create a component? This goes back to how React decides when and what to update at a certain time (you can read more about it here:
When a component updates, the instance stays the same, so that state is maintained across renders.
In your example, when the App component renders c2 instead of c1, React sees that the new component element is the same as the old one so it reuses the component(i.e. only a single instance exists) and updates the props/states accordingly.
Here are some more readings that I find useful to understand this problem:
https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/birth_mounting_indepth.html
Mark Amery's answer to this question is also very helpful.
If someone has landed here for react-native.
Assigning different keys for different instances will create new instances instead of reusing the same instance.
<MyComponent
key={this.state.uniqueInstanceKey}
myData={this.state.myData}/>
Your ternary is checking the state for showFirstComponent and rendering only one.
{this.state.showFirstComponent ? c1 : c2}
If you remove that all together, and just do
<SimpleComponent number="1" />
<SimpleComponent number="2" />
You'll get two logs. To further answer the question, constructor is called when the component is mounted, so usually once, unless you're dynamically mounting stuff. To get back at your ternary:
{this.state.showFirstComponent ? c1 : c2}
If you were to toggle this.state.showFirstComponent with a button lets say:
<button onClick={() => this.setState({
showFirstComponent: !this.state.showFirstComponent
})>
Toggle c1/c2
</button>
Every time you hit this button, component will be unmounted, and another one mounted, so you'll get a console log. Here's the order of react lifecycle methods between mount and unmount. However, if you do anything else, eg change the props passed, change the internal component state of <SimpleComponent />, it will NOT unmount, but rather, update, which calls the render method, and not the constructor. I suggest adding a console.log for some lifecycle methods in your <SimpleComponent /> and trying to trigger them, to better understand the component lifecycle. Or read an article about it.
Is this bad practices or not ?
export state change function from component
import it from other file.
call the function to change state?
In this way we can change some component state from anywhere.
For example...
We want to change the Model.js state from anywhere.
Modal.js
import React from 'react';
export let toggleModal;
export default class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
toggleModal = this.toggleModal;
}
toggleModal = () => {
this.setState({ open: !this.state.open });
};
render() {
const { open } = this.state;
return <div style={{ color: 'red' }}>{open && 'Hello Modal'}</div>;
}
}
App.js(Some Top Level component)
import React from 'react';
import Modal from './Modal';
export default () => (
<>
...
<Modal />
...
</>
);
Somewhere.js
import React from 'react';
import {toggleModal} from './Modal';
export default () => (
<>
<h1>Hello!</h1>
<button onClick={() => toggleModal()}>open Modal!</button>
</>
);
But there is no reference in React Official docs, so is this bad practices ?
What React Docs recommends...
Just passing function props to change parent state from parent to children
Use context
Redux or Mobx
But, these are too complex for me.
Example code here
https://next.plnkr.co/edit/37nutSDTWp8GGv2r?preview
Everything seems pretty much overwhelming and difficult at the beginning. But as we get out hands on them, it's give us more confidence to dig into.
I would recommend to use redux that's how we tackled props drilling problem. You can dispatch a action and connect reducer to corresponding component which upon updating state will re render. This is what I recommend to most of the people to learn the tale of redux with a real life example:
Understanding Redux: The World’s Easiest Guide to Beginning Redux
Apart from this you can take Dan Abramov, author of the library, free redux course on egghead.io:
Getting Started with Redux
The problem you run into, almost immediately like your code example does is this:
It will not work: your toggleModal() method expects a this to refer to an actual component instance. When your onClick() handler fires you invoke toggleModal() as a plain function. The this context will be wrong, and so at best (in your example) you will get an error because you try to invoke something undefined, at worst (in general) you end up invoking the wrong method.
When you think about it, for any non-trivial React component you will have a hard time obtaining a reference to the actual instance that is currently being used: you have to make sure that you are not forgetting to invoke the method on the right component instance and also you have to consider that instances may be created/destroyed 'at will' for whatever reason. For example: what if your component is rendered indirectly as part of some other component's render() method? Multiple layers of indirection like that make it even harder.
Now, you could fix all that by abusing ref with abandon but you will find that now you have to keep track of which ref refers to what particular instance, if you happen to have multiple of the components to consider in one render tree...
Whenever you think one component needs to handle the state of its siblings, the solution is usually to lift the state one level up.
export default class Modal extends React.Component {
render() {
const { isOpen } = this.props;
return <div style={{ color: 'red' }}>{isOpen && 'Hello Modal'}</div>;
}
}
export default class Home {
this.state = {
isOpen: false,
};
toggleModal = () => {
this.setState({ isOpen: !this.state.isOpen });
}
render() {
const { isOpen } = this.state;
return (
<>
<h1>Hello {name}!</h1>
<button onClick={() => this.toggleModal()}>open Modal!</button>
<Modal isOpen={isOpen}/>
<p>Start editing and see your changes reflected here immediately!</p>
</>
)
}
}
This way the Home handle the state and your problem is solved.
This can get annoying if the state needs to be "drilled down" to children, that's a problem than redux or react-context can solve.
Here <Modal /> is the child component. So to call a function in a child component you can simply use Ref.
You can refer this page to get more info about Ref.
You can assign a class variable as a ref to this child and use this class variable as an object to call its function.
I found if in special case, my way is okay.
Special case means something like customAlert component.
It is okay only one instance of customAlert component mounted at a time in App.
To achieve this...
1.Use ref to access and change DOM
2.attach state changing function or component to window and call window.function
3.my case: export state changing function and import it from other file.
And here is how to do with react Context
https://next.plnkr.co/edit/EpLm1Bq3ASiWECoE?preview
I think Redux is overkill if the main thing you are interested in is to make some states-like data available and updatable throughout your App without props drilling.
For that purpose, a much simpler approach (maybe not available at the time the question was posted?) is to use react context: https://frontend.turing.edu/lessons/module-3/advanced-react-hooks.html
"context - an API given to us by React, allowing for the passing of
information to child components without the use of props
[...]
useContext - a react hook, allowing functional components to take
advantage of the context API"
I've been trying to wrap my head around this problem for a while. I've hacked together a solution that works, until I get any nested divs, then things fall apart. Basically what I'm trying to do is create composition components that live within a higher order component and all share the same current state. I then need to export that so that any file can use those components. So here's what the JSX might look like:
<Panel countersStartAt=5>
<Counter incrementsBy=1 />
<div>
<Counter incrementsBy=2 />
</div>
<TotalCounter className="someclass" />
</Panel>
So the way I want something like this to work is that I have this wrapper Panel component that sets some initial state, say this.state.start = 5. Within Panel, a Counter component would have an onClick handler that increments state.start by incrementsBy. And TotalCounter would be a component that displayed state.start. Of course this is a contrived example, so it would be helpful not to bring up how I could make this particular component better. I'm looking to apply this to a more realistic situation.
The second thing would be how to export those components in a way that I can create the exact code above in a separate file within a stateless component. Hopefully that makes sense.
This is a snippet of what I'm doing to achieve this.
renderChildren = (children) => {
return React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.createElement(
(child.type.name ? this[child.type.name] : child.type),
child.props
);
}
return child;
});
};
render = () => {
return (
{this.renderChildren(this.props.children)}
)
};
Then outside of the Panel class I'm exporting like so:
export const Counter = () => null;
Just so it exposes Counter. The default render of null doesn't happen because I replace Counter with the this.Counter() method within Panel.
Questions asked in Comments and Other things to consider
I am not using Flux or Redux
Assume that the Panel code snippet is used in several render methods across several projects that do not implement Flux patterns or Redux
Assume that those code snippets can't be re-written
How can can Panel, Counter, and TotalCounter be exported? Is it possible to do this for Counter and TotalCounter as they are methods within the Panel class? My research led to no, and creating "dummy" components to be exported so that the current file can use them without errors.
To put in an answer here for what we talked about in the chat room
the best way to handle what you want to do without a data management framework like Redux or Flux is to pass your data as props through, like so.
class Panel extends Component {
constructor(){
super()
this.state = {count: 5}
}
incrementCount = (incrementer) => {
this.setState({count: this.state.count + incrementer});
}
render (){
return (
<div>
<Counter incrementCount={this.incrementCount} count={this.state.count} incrementsBy=2 />
</div>
);
}
}
then in your counter..
<someElement onClick={ (e) => {this.props.incrementCount(this.props.incrementsBy)} }>{this.props.count}</someElement>
I have a listview component which consists of a number of child listitem components.
Each child listitem have a showSubMenu boolean state, which display a few extra buttons next to the list item.
This state should update in response to a user event, say, a click on the component DOM node.
childcomponent:
_handleClick() {
... mutate state
this.props.onClick() // call the onClick handler provided by the parent to update the state in parent
}
However, it feels somewhat wrong to update state like, as it mutates state in different places.
The other way i figured i could accomplish it was to call the this.props.onClick directly, and move the child state into the parent as a prop instead, and then do change the state there, and trickle it down as props.
Which, if any, of these approaches is idiomatic or preferable?
First of all, I think that the question's title doesn't describe very well what's your doubt. Is more an issue about where the state should go.
The theory of React says that you should put your state in the higher component that you can find for being the single source of truth for a set of components.
For each piece of state in your application:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the
components that need the state in the hierarchy).
Either the common
owner or another component higher up in the hierarchy should own the
state.
If you can't find a component where it makes sense to own the
state, create a new component simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
However, a Software Engineer at Facebook said:
We started with large top level components which pull all the data
needed for their children, and pass it down through props. This leads
to a lot of cruft and irrelevant code in the intermediate components.
What we settled on, for the most part, is components declaring and
fetching the data they need themselves...
Sure, is talking about data fetched from stores but what im traying to say is that in some cases the theory is not the best option.
In this case i would say that the showSubMenu state only have sense for the list item to show a couple of buttons so its a good option put that state in the child component. I say is a good option because is a simple solution for a simple problem, the other option that you propose means having something like this:
var GroceryList = React.createClass({
handleClick: function(i) {
console.log('You clicked: ' + this.props.items[i]);
},
render: function() {
return (
<div>
{this.props.items.map(function(item, i) {
return (
<div onClick={this.handleClick.bind(this, i)} key={i}>{item} </div>
);
}, this)}
</div>
);
}
});
If, in a future, the list view has to get acknowledge of that state to show something for example, the state should be in the parent component.
However, i think it's a thin line and you can do wathever makes sense in your specific case, I have a very similar case in my app and it's a simple case so i put the state in the child. Tomorrow maybe i must change it and put the state in his parent.
With many components depending on same state and its mutation you will encounter two issues.
They are placed in component tree so far away that your state will have to be stored in a parent component very high up in the render tree.
Placing the state very high far away from children components you will have to pass them down through many components that should not be aware of this state.
THERE ARE TWO SOLUTIONS FOR THIS ISSUE!
Use React.createContext and user context provider to pass the data to child elements.
Use redux, and react-redux libraries to save your state in store and connect it to different components in your app. For your information react-redux library uses React.createContext methods under the hood.
EXAMPLES:
Create Context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
REDUX AND REACT-REDUX
import { connect } from 'react-redux'
const App = props => {
return <div>{props.user}</div>
}
const mapStateToProps = state => {
return state
}
export default connect(mapStateToProps)(App)
For more information about redux and react-redux check out this link:
https://redux.js.org/recipes/writing-tests#connected-components