React — Passing data between siblings and iterating it - reactjs

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

Related

How to refresh props with React/Redux when user enters a container

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

Force React container to refresh data

In my React application, I have two distinct types of components: presentations and containers. It's roughly after Dan Abromov's "Presentational and Container Components", except I don't use Flux or Redux.
Now, I have the following structure:
UsersContainer
├── UsersListContainer
| └── UsersListView
└── AddUserContainer
The UsersListContainer is responsible for loading data from some REST API, and if that succeeds, it delegates presenting that data to the UsersListView.
The AddUserContainer is responsible for adding a new user, again, by invoking an REST API. Now, when that was successful, I would like the UsersListContainer to refresh its data.
The best I can think of is this:
class AddUserContainer extends React.Component {
render() {
// Other UI elements omitted for brevity
return (<button onClick={ e => props.onUserAdded() }>Add user</button>);
}
}
class UsersListContainer extends React.Component {
componentWillMount() {
// start fetching data using window.fetch;
// the promise callback will but retrieved data into this.state
}
render() {
return (<table></table>);
}
}
class UsersContainer extends React.Component {
render() {
const forceUpdate = this.setState({ ...this.state, refreshToken: Math.random() });
// Other UI elements omitted for brevity
<UsersListContainer key={ this.state.refreshToken } />
<AddUserContainer onUserAdded={ forceUpdate } />
}
}
But this approach feels like mis-using the key prop. Is there a better / more elegant way to do this?
Check out react-refetch, which provides a nice API for fetching, and allows you to implement the Presentational and Container Components pattern, without using Flux/Redux for API calls.
It also lets you handle loading and errored states, which is definitely necessary for a decent web application today.
In the example below, I got rid of UsersListContainer but moved AddUserContainer into UsersContainer as well. This makes your UsersListView the presentational component for UsersContainer. Feel free to change the naming as you wish. This is so that I can get the refreshUsers prop to pass into AddUserContainer.
// UsersContainer.js
const Container = ({ usersFetch, refreshUsers }) => {
if (userFetch.pending) {
return <LoadingDisplay />
} else if (usersFetch.rejected) {
return <ErrorDisplay error={ usersFetch.reason } />
} else if (usersFetch.fulfilled) {
return (
<UsersListView users={ usersFetch.value } />
<AddUserContainer handleAddUser={ refreshUsers } />
);
}
};
const refetch = (props) => {
const usersFetch = `/api/users`;
return {
usersFetch: usersFetch,
refreshUsers: () => ({
usersFetch: { ...usersFetch, force: true, refreshing: true }
}),
};
};
export default connect(refetch)(Container);
Check out the documentation for more examples. I personally prefer to use react-refetch for API-heavy applications, rather than implementing the calls in Redux.
If you don't want to use Flux or Redux yet, then updating peer-components (in your case, UsersListContainer and AddUserContainer) is a bit, in my opinion, anti-pattern for React.
The main idea of React is, to me, passing props from parent to children, therefore, Irvin Lim's idea to "got rid of UsersListContainer but moved AddUserContainer into UsersContainer" will make it easier for you to control when to update your component!
Your current approaching and my idea are the same: in your UsersContainer, create a method to forceUpdate it, then pass it along to AddUserContainer, and after this AddUserContainer added a user, you trigger that updating method on the parent:
<AddUserContainer onUserAdded={ this.props.updatingParent } />
For your reference, or for anyone else who wants to understand about how to update the parent component whenever child (or grandchild or great-grandchild) updates, please refer to my answer to another similar issue:
Re-initializing class on redirect
If you still keep your current component-hierarchy, when UsersContainer is updated, its child-components (UsersListContainer and AddUserContainer) will be updated, too. However, AddUserContainer will once again be updated!
As a result, I still think in your case, using Flux or Redux is a nice approaching, which eliminates to complexity of passing props through many levels of deep & complicated component-hierarchy

Updating child props from parent state in ReactJS

I'm trying to understand how I can structure a ReactJS app with different "pages" or "views".
I have the following component as my base app and I'm using a currentState property in the React state to switch between which Components are active in the view.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {currentState: 'Loading', recipes: []};
this.appStates = {
'Loading': <Loading/>,
'Home': <RecipeList recipes={this.state.recipes}/>
}
}
dataLoaded(data) {
this.setState({
recipes: data,
currentState: 'Home'
});
}
componentDidMount() {
// AJAX Code that retrieves recipes from server and then calls dataLoaded
}
render() {
return this.appStates[this.state.currentState];
}
}
Which does the job, but the component never receives the updated recipes array when the dataLoaded callback is fired.
How can I cause the to update its props based on the updated state in the App?
Or am I approaching this whole thing the wrong way?
I think that your aproach isn't really react-like, and you have at least a couple of concepts that can be improved.
First of all, I would definitely use react-router to achieve any complex navigation between pages/Components in React.js. Implementing it yourself is more complicated and error-prone. react-router will allow you to assign Components to different routes easily.
Second, I think that you should almost never store things in the context this directly. Basically because it leads to errors like yours here: not realizing that appStates isn't changing at all. React's state is a great tool (which must sometimes be replaced/complemented with others like Redux) to store your application's state.
In the case of storing in the state what should be rendered in the Component, you should probably complement the react-router functionality with simple flags in the state initializated in the constructor that allow you to know what should you return in the render function.
Here is an example that shows how can you tell a component to change its view dynamically between loading and loaded by using just React's state. Of course, you could recreate a very similar behaviour making an AJAX call in componentDidMount and changing the state to stop loading when it's done instead of using a button.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {loading: true};
this.stopLoading = this.stopLoading.bind(this);
}
stopLoading() {
this.setState({
loading: false,
});
}
render() {
let view=<div><h1>loading</h1><button onClick={this.stopLoading}>FINISH</button></div>;
if(!this.state.loading){
view=<h1>loaded</h1>;
}
return <div>{view}</div>;
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<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="container"></div>
Your constructor method is only executed when the component mounts, at which point recipes is empty, and passes that empty array to appStates. Essentially, appStates never changes.
What I would recommend doing is pushing all the component code in the constructor to inside the render function. The beauty of react is that you can completely re-render everything at very little cost. React will do the hard work of comparing the difference in DOMs and only re-render the minimal differences.
I agree with #Ezra Chang. I think the code could be adjusted making use of just the state and the javascript spread function to pass the App props to the child RecipeList:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {currentState: 'Loading', recipes: [],'Loading': <Loading/>,
'Home': <RecipeList recipes={this.state.recipes} {...this.props}/>};
}
//I assume you want to pass the props of this App component and the data from the back-end as props to RecipeList.
dataLoaded = (data) => {
this.setState({
recipes: data,
currentState: 'Home',
'Loading': <Loading/>,
'Home': <RecipeList recipes={data} {...this.props}/>
});
}
componentDidMount() {
// AJAX Code that retrieves recipes from server and then calls dataLoaded
}
render() {
return this.state[this.state.currentState];
}
}

flux multiple components in same page are influenced with each other, bugs

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.

ReactJS server side async for search engines

I'm rendering my website server side with ReactJS + Router
and many of my components make a REST call to generate content. This content won't be send as HTML server side since it's an async call.
A component which could look like this:
import React from 'react'
import ReactDOM from 'react-dom'
// Imports omitted
export default class MyPage extends React.Component {
constructor() {
super();
this.state = {items: []};
fetch("http://mywebservice.com/items").then((response) => {
response.json().then((json) => {
this.setState({items: json.items})
}).catch((error) => {});
});
}
render() {
if (this.state.items && this.state.items.length > 0) {
var rows = [];
// Go through the items and add the element
this.state.items.forEach((item, i) => {
rows.push(<div key={item.id}></div>);
});
return <div>
<table>
{rows}
</table>
</div>;
}
else {
return <span>Loading...</span>
}
}
}
a search engine would index "Loading..." while I obviously want my elements (items) indexed. Is there a way to solve this?
Checkout react-async (https://www.npmjs.com/package/react-async) or react-async-render (https://www.npmjs.com/package/react-async-render)
Sounds like it's just what you need.
In theory, I think in order to accomplish that you will need on server side request handler, collect all necessary data and pass it to ReactDOM.RenderToString() as initial state JSON string, then create custom fetch() which will check if the initial data exists, if yes then it will render html right away.
One of the reasons why redux is so popular is that it enforces data separation. Ideally, you have a single place where you specify all the data application needs to render. In redux that'll be a store, but let's assume you store all the data in the topmost element's props. If that is the case, all you need to do is to provide all the relevant props to that topmost element and it will render everything nicely. The way to collect all the data needed is up to server though. I would suggest getting clues for that from current url and logged-in user info (be it a token or a cookie).

Resources