OrangeDurito.blog

An amalgamation of engineering and artistry

Learning ReactJS In Lockdown

When the Coronavirus lockdown was about to start, I realized that this would be a wonderful time to pick up new skills which I have been putting off. So, I made a list of things I wanted to do and went to accomplishing them one by one. This post is about learning ReactJS and the notes I made on the way.

React Notes

  • React is a JavaScript library (not framework)

JSX Syntax

const heading = <h1 className = "site-heading"> Hello React! </h1>

this compiles to

const heading = React.createElement('h1',{className: 'site-heading'}, 'Hello React!')
const name = 'Chandan'
const heading = <h1> Hello, {name}</h1>

Props vs. State

class Welcome extends React.Component{
    render(){
        return(
            <h1> Hello {this.props.name} </h1>
        );
    }
}

const element = <Welcome name="Chandan" />;

// rewriting the above component to be simpler
function Welcome(props){
    return <h1> Hello {props.name}</h1>;
}
  • A component can also have default props, so if a prop isn't passed through it can still be set. We can make the name property optional by adding defaultProps to the Welcome class:
class Welcome extends React.Component{
    render(){
        return(
            <h1> Hello {this.props.name} </h1>
        );
    }
}

Welcome.defaultProps = {
    name: "world",
};
  • During a component's lifecycle props should not change (consider them immutable)

  • Since props are passed in, and they cannot change, you can think of any React component that only uses props (and not state) as "pure", that is, it will always render the same output given the same input. This makes them really easy to test.

State

  • By default, a component has no state. The Welcome component from above is stateless.

  • We should use state when a component needs to keep track of information between renderings the component itself can create, update, and use state.

// An example that keeps track of how many times you have clicked the button
class Button extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            count: 0, // The initial data can be hard coded or come from 'props'
        };
    }

    updateCount(){
        this.setState((prevState, props) => {
            return { count: prevState.count + 1}
        });
    }

    render(){
        return(
            <button onClick={() => this.updateCount()}> 
                Clicked {this.state.count} times
            </button>
        );
    }
}
  • setState takes a function because setState can run asynchronously. It needs to take a callback function rather than updating the state directly.

Warning 1 - Always use setState not this.state.count = this.state.count + 1

Warning 2 -

this.setState({
    count: this.state.count + 1 //As I used in ButtonToggle.jsx
});  

Although this might look reasonable, doesn't throw errors, and you might find examples that use this syntax online, it is wrong. This does not take into account the asynchronous nature that 'setState' can use and might cause errors with out of sync state data.

onClick={() => this.updateCount()} - Need to use ES6's arrow function so updateCount will have access to this instance's state.

  • props contains information set by the parent component (although defaults can be set) and should not changed

  • state contains "private" information for the component to initialize, change, and use on it's own.

  • Props are a way of passing data from parent to child. State is reserved only for interactivity, that is, data that changes over time.

State and Lifecycle

  • To update state, use setState()
this.setState({comment: 'Hello'});
  • Second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
this.setState((state, props) => ({
    counter: state.counter + props.increment
}));

Shallow Merging

    constructor(props){
        super(props);
        this.state = {
            posts: [],
            comments: []
        };
    }

    componentDidMount(){
        fetchPosts().then(response => {
            this.setState({
                posts.response.posts
            });
        });

        fetchComments().then(response => {
            this.setState({
                comments: response.comments
            });
        });
    }

The merging is shallow, so this.setState({comments}) leaves this.state.posts intact, but completely replaces this.state.comments

Handling Events

  • In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.

This is not React-specific behavior; it is a part of how function work in javascript. Generally, you refer to a method without () after it, such as onClick={this.handleClick}, you should bind that method.

Passing Arguments to Event Handlers

<button onClick={this.deleteRow.bind(this,id)}> Delete Row </button>

Conditional Rendering

if(isLoggedIn){
    button = <LogoutButton onClick={this.handleLogoutClick} />;
}
else{
    button = <LoginButton onClick={this.handleLoginClick} />;
}

Inline If with Logical && Operator

  • You may embed any expressions in JSX by wrapping them in curly braces.
<div>
    <h2> Next line will only execute if the condition satisfies. </h2>
    {unreadMessages.length > 0 &&
        <h2>
            You have {unreadMessages.length} unread messages.
        </h2>
    }
</div>
  • It works because in JavaScript, true && expression always evaluates to expression, and false && expression always evaluates to false.

  • Therefore, if the condition is true, the element right after && will appear in the output. If it is false, React will ignore and skip it.

Inline If-Else with Conditional Operator

  • Another method for conditionally rendering elements inline is to use the JavaScript conditional operator condition ? true : false
render(){
    const isLoggedIn = this.state.isLoggedIn;
    return(
        <div>
            The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
        </div>
    );
}

It can also be used for larger expressions -

render(){
    const isLoggedIn = this.state.isLoggedIn;
    return(
        <div>
            {isLoggedIn
                ? <LogoutButton onClick={this.handleLogoutClick} />
                : <LoginButton onClick={this.handleLoginClick} />
            }
        </div>
    );
}

List and Keys

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number*2);
console.log(doubled);
  • Keys help React identify which items have changes, are added, or are removed.
const todoItems = todos.map((todo) =>
    <li key={todo.id}>
        {todo.text}
    </li>
);
const todoItems = todos.map((todo, index) =>
    // Only do this if items have no stable IDs
    <li key={index}>
        {todo.text}
    </li>
);
  • Correct key usage - elements inside the map() call need keys
function ListItem(props){
    //Correct! There is no need to specify the key here:
    return (
        <li>{props.value}</li>
    );
}

function NumberList(props){
    const Numbers = props.numbers;
    const listItems = numbers.map((number) => 
        // Correct! Key should be specified inside the array.
        <ListItem key={number.toString()} value = {number} />
    );

    return(
        <ul> {listItems} </ul>
    );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList nubmers={numbers} />, document.getElementById('root')
);
  • Keys used within arrays should be unique among their siblings. However they don't need to be globally unique. We can use the same keys when we produce two different arrays:
function Blog(props){
    const sidebar = (
        <ul>
            {props.posts.map((post) =>
                <li key={post.id}>
                    {post.title}
                </li>
            )}
        </ul>
    );

    const content = props.posts.map((post) =>
        <div key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
        </div>
    );

    return(
        <div>
            {sidebar}
            <hr />
            {content}
        </div>
    );
}

const posts = [
    {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
    {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];

ReactDOM.render(
    <Blog posts={posts} />, document.getElementById('root')
);
  • Keys serve as a hint to React but they don't get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name:
const content = posts.map((post) =>
    <Post
        key={post.id}
        id={post.id}
        title={post.title} 
    />
);

Forms

  • In React, a <textarea> uses a value attribute instead. This way, a form using a <textarea> can be written very similarly to a form that uses a single-line input.

  • React, instead of using selected attribute, uses a value attribute on the root select tag. This is more convenient in a controlled component because you only need to update it in one place.

Overall, this makes it so that <input type="text">, <textarea>, and <select> all work very similarly - they all accept a value attribute that you can use to implement a controlled component.

  • You can pass an array into the value attribute, allowing you to select multiple options in a select tag:
<select multiple={true} value={['B', 'C']}>
  • setState() automatically merges a partial state into the current state, we only need to call it with the changed parts.

Controlled Input Null Value

  • Specifying the value prop on a "controlled component" prevents the user from changing the input unless you desire so. If you're specified a value but the input is still editable, you may have accidentally set value to undefined or null.

In the following code the input is locked at first but become editable after a short delay -

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function(){
    ReactDOM.render(<input value={null} />, mountNode);
},1000);
  • Full-fledged React form solution including validation, keeping track of the visited fields and handling form submission - Formik

Lifting State Up

  • In React, sharing state is accomplished by moving it up to the closest common ancestor of the components that need it. This is called "lifting state up".

  • There should be a single "source of truth" for any data that changes in a React application. Usually, the state is first added to the component that needs rendering. Then, if other component also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on the 'top-down data flow'.


  • To insert a variable in the output text -
alert(`Welcome aboard, ${this.state.login}!`);

Thinking In React

  • Single Responsibility Principle - A component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller sub-components.

  • UI and data models tend to adhere to the same information architecture. Separate your UI into components where each component matches one piece of your data model.

  • React's one-way data flow (also called one-way binding) keeps everything modular and fast.

Comments