I am using same component multiple times in the same page, and I just realized that any event dispatched are intercepted by all the same companents and all the components are updated together.
This is not acceptable, as even if it is same component, if it is used to display different data, they should have totally independent behavior. Action performed in one component should never be listened by another component.
How can I fix this error?
You should have a container component which will get a data collection, which represents the component you are repeating. An action will change that data collection, and not the repeated component itself. In other words, the repeated component should not get data directly from the store.
You could see an full todomvc example, which has the same "TodoItem" component being rendered a few times in one page here: TodoMVC example
Example:
var ButtonStore = require('../stores/ButtonStore');
function getButtonState() {
return {
allButtons: ButtonStore.getAll()
}
}
const Button = (props) => {
return <button>{props.text}</button>
}
class ButtonList extends React.Component {
constructor(props) {
super(props)
this.state = getButtonState()
}
render() {
return <div>
{this.state.allButtons.map(button => <Button {...button} />)}
</div>
}
}
Here is a fiddle of the example, just without the store: Component List Example
It could be helpful, if you post some code example.
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 tried to search for this and I think the answer is being obfuscated by many questions and tutorials using similar keywords - but not with the specific issue I'm having. This issue is close - where a complex component (D3) is continuously being re-rendered. Lots of answers I find are for memoize and PureComponents - these don't work for d3, Google Maps, etc though - the rendering of Stateful_Class_Component_Widget happens without fail.
I have a React app with a structure similar to the following (very basic sample of general structure)
// big stateful component running everything with state
class AppView extends React.Component {
constructor(props) {
super(props);
this.state = {
documents: [] // array of simple JSON docs fetched from API
show_widget: true
}
this.function1 = this.function1.bind(this)
// more involved functions, set states, etc
render()
return (
<div>
<StatelessFC1
documents = {this.state.documents}
show_widget = {this.state.show_widget}
/>
<StatelessFC2
props={this.state.otherStateThing}
/>
</div>
)
}
class Stateful_Class_Component_Widget extends React.Component {
// assume D3, Google Maps, Leaflet - a big involved
constructor(props) {
super(props);
console.log("New instance")
this.state = {
documents: [] // array of simple JSON docs fetched from API
}
}
// some functions etc
render() {
return (
<div id="d3_uses_me">
// d3/google maps code
</div>
)
}
const StatelessFC1 = (props) =>
return (
<StatelessFC1_a
documents={props.documents}
show_widget = {props.show_widget}
/>
)
const StatelessFC1_a = (props) =>
return (
<StatelessFC1_a_i
documents={props.documents}
show_widget = {props.show_widget}
/>
// this only renders when AppView's state is 'show-widget')
const StatelessFC1_a_i = (props) =>
return (
{ props.show_widget &&
<Stateful_Class_Component_Widget
documents={props.documents}
/>
}
</div>)
So the idea is that in order to structure the application view, I have a function of dumb stateless components that get rerendered as necessary when the AppView state changes - all good there. State for everything else is handled fine by the AppView.
The problem is though - is that the Stateful_Class_Component_Widget (in my case it is a similar to a google map - so re-rendering it each time something trivial changes is pointless) keeps getting re-created - that is each time the state of AppView changes, a new instance of the Stateful_Class_Component_Widget is being created (likely because of the re-rendering triggered in its 'parent' stateless components - which is quite taxing since this is a third party API. Ideally it should only update when its state/props change - which is all good - but right now it is being created everytime AppView's state changes (can see that "new instance" in console each time)
Essentially I want one instance of the Stateful_Class_Component_Widget and want it to remain persistent (though not always visible) throughout the course of the application lifetime. Right now it is always being reconstructed. Is only way to do it to bring it directly into AppView?
I have CompetitionSection which repeats all the competitions from database. When user clicks on one, it redirects him to a Competition Page, loads for a second and renders the page with all the details in it. So far, so good.
But when users goes back to the Competition Section and then click on the second competition, it instantly loads up the previous competition, 0 loading time.
From my point of view, what is failing is that the props of the component are not updating when I render the component (from the second time). Is not a router problem, which was my first instinct because I'm seeing the route.params changing acordingly, but the actions I dispatch to change the props are not dispatching. Here's a bit of code of said component.
class CompetitionPage extends React.Component {
componentWillMount() {
let id = getIdByName(this.props.params.shortname)
this.props.dispatch(getCompAction(id));
this.props.dispatch(getCompMatches(id));
this.props.dispatch(getCompParticipants(id));
this.props.dispatch(getCompBracket(id));
}
render() {
let { comp, compMatches, compBracket, compParticipants } = this.props
...
I tried every lifecycle method I know. component Will/Did Mount, component Will/Did update and I even set shouldUpdate to true and didn't do the trick. As I understand, the problem will be solved with a lifecycle method to dispatch the actions everytime an user enters Competition Page and not just for the first time. I'm running out of options here, so any help will be appreciated.
NOTE: I'm a newbie at React/Redux so I KNOW there are a couple of things there are anti-pattern/poorly done.
UPDATE: Added CompetitionsSection
class CompetitionsSection extends React.Component {
render() {
const {competitions} = this.props;
return (
...
{ Object.keys(competitions).map(function(comp, i) {
return (
<div key={i} className={competitions[comp].status ===
undefined? 'hide-it':'col-xs-12 col-md-6'}>
...
<Link to={"/competitions/"+competitions[comp].shortName}>
<RaisedButton label="Ver Torneo" primary={true} />
</Link>
...
It helps to better understand the lifecycle hooks. Mounting a component is when it is placed on the DOM. That can only happen once until it is removed from the DOM. An UPDATE occurs when new props are passed or setState is called. There are a few methods to troubleshoot when updates are not happening when you think they should:
Ensure that you are changing state in componentDidMount or componentDidUpdate. You cannot trigger an update in componentWillMount.
Make sure that the new props or state are completely new objects. If you are passing an object down in props and you are just mutating the object, it will not trigger an update. For instance, this would not trigger a update:
class CompetitionPage extends React.Component {
constructor(props) {
super(props)
this.state = {
competitions: [ compA, compB ]
}
}
triggerUpdate() {
this.setState({
competitions: competitions.push(compC)
})
}
componentDidMount() {
triggerUpdate()
}
render() {
return(
<div>
Hello
</div>
)
}
This is due to the fact that a new competition is being appended to the array in state. The correct way is to completly create a new state object and change what needs to be changed:
const newCompetitions = this.state.competitions.concat(compC)
this.setState(Object.assign({}, this.state, { competitions: newCompetitions }))
Use ComponentWillRecieveProps on an update to compare previous and current prop values. You can setState here if clean up needs to be done:
Read more about this method in the React documentation:
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
As an example (real tried code)
I have a component of which I want to initiate a NEW instance for rendering.
import React, { Component } from 'react';
export default class TinyObject extends Component {
constructor(props) {
super(props);
console.log("TinyObject constructor");
}
render() {
console.log("TinyObject render");
return (
<div>HEY THIS IS MY TINY OBJECT</div>
);
}
}
Then in main App constructor I do the following:
var myTinyObject = new TinyObject();
var myArray = [];
myArray.push(myTinyObject);
this.state = {testing: myArray};
Then a created a function to render this:
renderTest()
{
const {testing} = this.state;
const result = testing.map((test, i) => {
console.log(test);
return {test};
});
}
And I call this from the App render function like this:
render() {
const { gametables, tableActive } = this.state;
console.log("render");
return <div><div>{this.renderTest()}</div></div>;
}
It runs, no errors.
I see console log of the following:
console.log("TinyObject constructor");
console.log(test);
But I don't see console log of the TinyObject render nor do I see the render output.
Thanks to lustoykov answer I got a little further
JSX: var myTinyObject = <TinyObject />;
works!
but in the real app I add a little more and don't know how to do it here.
return <GameTable key={'gt'+index} data={table} settings={this.settingsData} sendTableNetworkMessage={this.sendTableNetworkMessage} />
this is the way I was rendering; and I needed more instances of GameTable
now the question is; how do I add the arguments like data & settings to myTinyObject.
thanks for helping so far.
You don't manually instantiate react component, use JSX or createElement. For instance
via JSX
var myTinyObject = <TinyObject prop1={prop1} prop2={prop2} />;
via React.createElement
var myTinyObject = React.createElement(TinyObject, { prop1, prop2 }, null);
I would definitely check out some tutorials and how React works at a basic level. You aren't really going to call your react components like you would normally do in javascript since the render function returns jsx.
Fundamentally, React is what is called a single page application. That means that your browser will load up a single html file with a div. Now that div will be where React performs its magic by using Javascript to change stuff around.
It is easiest for me to think of React as a tree. You create these components that you place on the DOM or in your HTML and React will add and remove them downwards. For instance, take a look at this picture of twitter.
So first the Feed component is going to be put on the DOM. Then the Feed component will render the Tweet components. So as you can see the rendering goes in one direction, downwards.
Now, as you can see your render methods are not returning javascript. It is returning something that looks like HTML but we call it JSX. That means we want to render it a little differently with our react classes.
If we have a child component:
class Child extends React.Component {
render() {
return <h1>Hello, I am inside the parent component</h1>;
}
}
We can call the render method like this:
class Parent extends React.Component {
render() {
<Child /> //This is how I use the Child class
}
}
Now the reason why react is so performant is that the child cannot be re-rendered unless we do 1 of two things:
It is a component with a state and we call a method setState()
We pass down new props to a child component from the parent component
You can read about it here
Now the only way to get React to call that render function again is by doing those two things.
I have two questions, the first is about passing data between components and the second is about the component hierarchy.
Right now, in the Data component I am trying to set the name property and pass it to a ListItem component that should iterate based on the API request. I tried many things without success. What am I doing wrong? Does the data needs to be iterated when setting the new state? Am I passing it correctly?
Basic pseudocode
Read data from API
Set data to the component state
Create siblings components based on data stored
Render components
The second question is about the hierarchy of the components. I keep reading around the web that the data request should be setup at the top and separated. Having this in place, the other components would feed from this data and execute. Finally, the App component would render all this components accordingly. From the example below, am I going to the right track? Was I correct to creating a component specific for data request or should this be done in the App component?
I understand these questions might be basic but it is something that I am really struggling to understand and I would appreciate if someone can help me understand or point me to a basic example that I can digest.
Thank you in advance. (Sorry, I had more than two questions.)
class App extends React.Component {
render() {
return (
<ul>
<ListItem name={this.state.name} />
</ul>
)
}
}
class Data extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '',
requestFailed: false,
}
}
componentDidMount() { // Executes after mouting
fetch('https://api.tfl.gov.uk/BikePoint')
.then((response) => {
return response.json()
}).then((d) => {
console.log('parsed json', d[0].commonName)
this.setState({
name: d.commonName
});
}).catch(function(ex) {
console.log('parsing failed', ex)
this.setState({
requestFailed: true
})
})
}
render() {
if(this.state.requestFailed) return <p>Request failed.</p>
if(!this.state.name) return <p>Loading</p>
const namesList = names.map(function(name, index){
return <ListItem key={index} name={this.state.name} />
})
return <ul>{ namesList }</ul>
}
}
class ListItem extends React.Component {
render() {
return <li> { this.props.name } </li>
}
}
ReactDOM.render(<App />, document.getElementById('app'));
CodePen
Where to start -
First, your App component needs to render Data component. React works in the way that parent element always renders children elements and what is not rendered doesn't exist.
Then, you need to remap response to names, if that is what you wanted to do - I am not sure.
In render method, you want to take name out of mapping function, not from state. I also removed name state, you really want to keep names instead of one name. There is a lot of small thing I had to adjust to make it work, so I will just post working code pen here, so you can see what needed to be done.
https://codepen.io/anon/pen/eEmqxX?editors=0010