In this tutorial we are going to add a NavBar component to the application we have been building in React. During this process we’ll need to do some refactoring in order to shift local state out of child components, and into the App parent component. By following this convention, we’ll need to raise events in the child components while handling them in the parent component. This allows our application to have a single source of truth. In other words, several components may reflect updates based on the same changing data. Let’s see how this works now.
Add The Navbar Component File
First off, we can add a new navbar.jsx file to the components folder we have been working with.
In the NavBar component we can add the following code. Notice we are using the props object to fetch the total number of items in the application so we can display them in the navigation area.
import React, { Component } from "react";
class NavBar extends Component {
render() {
return (
<React.Fragment>
<nav className="navbar navbar-dark bg-dark mb-3">
<a className="navbar-brand" href="#">
<h1>Total Items <span className="badge badge-secondary">{this.props.totalItems}</span></h1>
</a>
</nav>
</React.Fragment>
);
}
}
export default NavBar;
Reconfigure To Use App.js
We want to revert back to using App.js as the main component in the application. To do this, we can make an adjustment to index.js so that we now render <App/> instead of <Items/> like so.
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.css";
ReactDOM.render(<App />, document.getElementById("root"));
Lifting The State In React
When working in React you’ll sometimes hear about lifting state up. The React docs describe this concept as follows.
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor. Let’s see how this works in action.
In our case, that means we want to have the following application layout where the state now resides in the top level App component.
To do this we needed to refactor all of the child components to move their state into the App component. Here is our new App component. At the top is the shared state, then we have the event handlers, and lastly the render() method. See the prior React Tutorials if you need a refresher on these concepts. In order to add up the total items as we increment, we make use of the JavaScript reduce() method.
App.js
import React, { Component } from "react";
import NavBar from "./components/navbar";
import Items from "./components/items";
import "./App.css";
class App extends Component {
state = {
items: [{ id: 1, value: 0 }, { id: 2, value: 0 }, { id: 3, value: 0 }]
};
handleIncrement = item => {
const items = [...this.state.items];
const index = items.indexOf(item);
items[index] = { ...item };
items[index].value++;
this.setState({ items });
};
handleReset = () => {
const items = this.state.items.map(i => {
i.value = 0;
return i;
});
this.setState({ items });
};
handleDelete = itemId => {
const items = this.state.items.filter(item => item.id !== itemId);
this.setState({ items: items });
};
render() {
return (
<React.Fragment>
<NavBar
totalItems={this.state.items.reduce((prev, cur) => {
return prev + cur.value;
}, 0)}
/>
<Items
onReset={this.handleReset}
onDelete={this.handleDelete}
onIncrement={this.handleIncrement}
items={this.state.items}
/>
</React.Fragment>
);
}
}
export default App;
Refactored Children Components
Since all the state and logic has been lifted up to the top-level App component, our <Items/> and <Item/> components are now more streamlined. In fact, they no longer even have any local state at all. If they need to work with data, they must get it via the props object. Additionally, in order to update data events are raised in the child and handled in the parent.
items.jsx
import React, { Component } from "react";
import Item from "./item";
class Items extends Component {
render() {
return (
<React.Fragment>
<button
onClick={this.props.onReset}
className="btn btn-success btn-lg m-3"
>
Reset All
</button>
{this.props.items.map(item => (
<Item
key={item.id}
onDelete={this.props.onDelete}
onIncrement={this.props.onIncrement}
item={item}
/>
))}
</React.Fragment>
);
}
}
export default Items;
item.jsx
import React, { Component } from "react";
class Item extends Component {
render() {
return (
<React.Fragment>
<div className="card mb-2">
<h5 className={this.styleCardHeader()}>{this.styleCount()}</h5>
<div className="card-body">
<button
onClick={() => this.props.onIncrement(this.props.item)}
className="btn btn-lg btn-outline-secondary"
>
Increment
</button>
<button
onClick={() => this.props.onDelete(this.props.item.id)}
className="btn btn-lg btn-outline-danger ml-4"
>
Delete
</button>
</div>
</div>
</React.Fragment>
);
}
styleCardHeader() {
let classes = "card-header h4 text-white bg-";
classes += this.props.item.value === 0 ? "warning" : "primary";
return classes;
}
styleCount() {
const { value } = this.props.item;
return value === 0 ? "No Items!" : value;
}
}
export default Item;
Shared State In Action
Now when we interact with the application we can see a few concepts in action. Clicking the increment button on each individual item adds to the count of that item. In addition, the NavBar component is now keeping track of the sum of all item values. Finally, we can click a button to reset all counters back to zero.
Further Reading
- What does the “single source of truth” mean?
- ReactJS – Lifting state up vs keeping a local state
- Common React.js mistakes: Unneeded state
- Everything About React Events – React Armory
Create A Navbar Component In React Summary
In this tutorial we did some good refactoring to add a Navbar component to the application. We learned a bit about lifting up the state in react, as well as how to raise and handle events. If you haven’t already do check out the prior React Tutorials to see how we made it to this point.