Does a change in a component refresh the whole page or just that component which was changed?

Viewed 54

so I am new to React. Loving it so far. However, I am having a basic question which doesn't have a clear answer right now.

So, I am learning how to lift the state of a component. So here's a reproducible example.

index.js

import React from "react";
import ReactDOM from "react-dom"
import {Component} from "react";

// import AppFooter from "./AppFooter";
import AppContent from "./AppContent";
import AppHeader from "./AppHeader";

import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min'
import './index.css'

class App extends Component{

    constructor(props) {
        super(props);
        this.handlePostChange = this.handlePostChange.bind(this)
        this.state = {
            "posts": []
        }
    }

    handlePostChange = (posts) => {
        this.setState({
            posts: posts
        })
    }

    render() {
        const headerProps = {
            title: "Hi Keshav. This is REACT.",
            subject: "My Subject is Krishna.",
            favouriteColor: "blue"
        }
        return (
            <div className="app">
                <div>
                    <AppHeader {...headerProps} posts={this.state.posts} handlePostChange={this.handlePostChange}/>
                    <AppContent handlePostChange={this.handlePostChange}/>
                </div>
            </div>

        );
    }
}

ReactDOM.render(<App/>, document.getElementById("root"))

I am trying to lift the state of posts which is changed in AppContent to AppHeader. Here's my AppContent.js and AppHeader.js

// AppContent.js
import React, {Component} from "react";

export default class AppContent extends Component{

    state = {
        posts: []
    }

    constructor(props) {
        super(props); // constructor
        this.handlePostChange = this.handlePostChange.bind(this)
    }

    handlePostChange = (posts) => {
        this.props.handlePostChange(posts)
    }

    fetchList = () => {
        fetch("https://jsonplaceholder.typicode.com/posts")
            .then((response) =>
                response.json()
            )
            .then(json => {
                // let posts = document.getElementById("post-list")
                this.setState({
                    posts: json
                })
                this.handlePostChange(json)
            })
    }
    clickedAnchor = (id) => {
        console.log(`Clicked ${id}`)
    }
    render() {
        return (
            <div>
                <p>This is the app content.</p>
                <button onClick={this.fetchList} className="btn btn-outline-primary">Click</button>
                <br/>
                <br/>
                <hr/>
                <ul>
                    {this.state.posts.map((item) => {
                        return (
                            <li id={item.id}>
                                <a href="#!" onClick={() => this.clickedAnchor(item.id)}>{item.title}</a>
                            </li>
                        )
                    })}
                </ul>
                <hr/>
                <p>There are {this.state.posts.length} entries in the posts.</p>
            </div>
        )
    }
}
// AppHeader.js
import React, {Component, Fragment} from "react";

export default class AppHeader extends Component{

    constructor(props) {
        super(props); // constructor
        this.handlePostChange=this.handlePostChange.bind(this)
    }

    handlePostChange = (posts) => {
        this.props.handlePostChange(posts)
    }

    render() {
        return (
            <Fragment>
                <div>
                    <p>There are {this.props.posts.length} posts.</p>
                    <h1>{this.props.title}</h1>
                </div>
            </Fragment>
        )
    }
}

So here's the main question. As we see, that I am calling the dummy posts api and trying to show the titles of the json object list returned by it.

The posts state is actually updated in AppContent and is shared to AppHeader by lifting it to the common ancestor index.js

However, here's what I have observed. When I keep this code running using npm start I see that anytime I make a change in any place, it refreshes. I was under the impression that it renders the whole page running on localhost:3000.

Say here's my current situation on the web page: Browser screen when the data is fetched.

Now, say I make a change in just AppContent.js, then here's how it looks then: After change in only AppContent

In here, we see that it's still showing 100 posts in case of AppHeader. Is this expected that react only reloads the component and not the whole page. When I refresh the whole page, it shows 0 posts and 0 posts in both the places. Now have I made a mistake in writing the code ? If yes, how do I fix this ?

Thank you. In case the question is not clear please let me know.

1 Answers

In here, we see that it's still showing 100 posts in case of AppHeader. Is this expected that react only reloads the component and not the whole page.

It's not React, per se, that's doing that. It's whatever you're using to do hot module reloading (probably a bundler of some kind, like Webpack or Vite or Rollup or Parcel or...). This is a very handy feature, but yes, it can cause this kind of confusion.

Now have I made a mistake in writing the code ?

One moderately-signficant one, a relatively minor but important one, and a couple of trivial ones:

  1. posts should either be state in App or AppContent but not both of them. If it's state in both of them, they can get out of sync — as indeed you've seen with the hot module reloading thing. If you want posts to be held in App, fetch it there and provide it to AppContent as a property. (Alternatively you could remove it from App and just have it in AppContent, but then you couldn't show the total number of posts in App.)

  2. When you're rendering the array of posts, you need to have a key on each of the li items so that React can manage the DOM nodes efficiently and correctly.

  3. There's no need to wrap a Fragment around a single element as you are in AppHeader.

  4. If you make handlePostChange an arrow function assigned to a property, there's no reason to bind it in the constructor. (I would make it a method instead, and keep the bind call, but others like to use an arrow function and not bind.)

  5. There's no reason for the wrapper handlePostChange functions that just turn around and call this.props.handlePostChange; just use the function you're given.

  6. Two issues with your fetch call:

    • You're not checking for HTTP success before calling json. This is a footgun in the fetch API I describe here on my very old anemic blog. Check response.ok before calling response.json.
    • You're ignoring errors, but should report them (via a .catch handler).
Related