From being a student to my professional experience, I have always been working with backend development. I have always wanted to dip my toes into the frontend development realm, but the opportunity never came. Of course, some might say that’s an excuse — we can always pick up things on the side. However, without a proper project with a decent amount of architecture, the learning curve tend to have a lot of blockers.
With that said, I would like to share my experience when I started to pick up React.
1. JavaScript and TypeScript
When picking up something new, I tend to be Googling a lot. The first thing I would want to identify is, what language we are using. This was when I saw different file extensions, .js and .ts, appearing in React projects. And what confuses me is that both are very similar. So what exactly are the differences?
TypeScript is a superset of JavaScript
TypeScript to put it simply, is JavaScript with added features to make development better. Since it is a superset of JavaScript, we can directly change the file extension from .js to .ts to convert a JavaScript file to a TypeScript file. Therefore, TypeScript can gradually be adopted from a JavaScript application.
TypeScript shows compilation errors during development
As mentioned, TypeScript has added features on top of JavaScript. It allows the use of interfaces and declaration of variable types. This does make writing codes slower, but it gives a huge advantage over JavaScript. As we write codes, TypeScript points out type mismatch that can cause compilation error. Having this feature saves us a lot of time. We are able to catch bugs before it happens, giving us more confidence in the codes to run properly.
TypeScript takes time to compile
Codes written in TypeScript needs to be compiled and translated into JavaScript. Browsers cannot understand TypeScript, therefore we need to convert it to JavaScript. But in my opinion, taking time to compile is not much of a disadvantage compared to the features it brings.
2. Class Components and Functional Components
Now that I have the language cleared up, I hit my next question mark really fast. Articles I looked into have 2 very different looks. First one being declared as a class while the other as a function. From React 16.8, hooks were introduced and functional components started to gain popularity. Essentially, they return the same results and they can be converted to and from one another once you understands how to implement them. At the time of writing, using functional components is the most common practice.
Passing props
Functional Component:
const FunctionalComponent = ({name}) => {
return <h1>Hello, {name}</h1>;
};
Class Component:
class ClassComponent extends React.Component {
render() {
const {name} = this.props;
return <h1>Hello, {name}</h1>
}
}
In a functional component, we can pass the props directly to the function. We can then use the props just like a JavaScript function. Functional component is just a function that returns a React element.
Since the class component is a class, we need to extend React.Component
. This extension allows us to inherit React lifecycle methods. Render
being one of the lifecycle methods, returns the React element.
Handling states
Functional Component:
const FunctionalComponent = () => { const [count, setCount] = React.useState(0);
return (
<div>
<p>count: {count}</p>
<button onClick={() => {setCount(prevCount => prevCount++)}}>
Increase counter
</button>
</div>
);
};
Class Component:
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>count: {this.state.count}</p>
<button onClick={() => {
this.setState({count: this.state.count + 1})}}>
Increase counter
</button>
</div>
)
}
}
Handling states was only achievable in class components until React hooks were introduced. By using the useState
hook, we are able to get the state and it’s setter in the functional component. To work with the state, we can simply pass in the new state to the setter as a parameter.
Class components handle states a little differently. The constructor of the class for React component is called before mounting and super(props)
should be called before any other statement. Without doing these, all the state variables that are declared will be undefined.
3. Local and global states
In React, there are two ways you can store data. These data can come from an API response or simply from an input component from the user. Either way, we need to store this data in a state so that we can use it at a later time. We can do so by using a global state or local state.
Local State
React has a useState
hook, which helps us create a local state. it returns an array with the first element as our state and the second element as a setter to our state. Within the useState
hook, we can provide a parameter to set the initial value of our state. Let’s look at the example below:
import React, { useState } from 'react';
function Example() {
[count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
in our Example()
function, we initialised a local state called count
to a value of 0 and its setter function called setCount
. In the return
statement, we can immediately use our state to display its value to the UI through the <p>
tag. To update our state, we can use the setter function, setCount
, and pass the new value to it. In our example, we are adding 1 to our count each time a button is clicked.
The benefit of using a local state is that we can immediately use it within the component. It is usually used when we want to store temporary data and discard it once we finished using it. This is because it is initialised within the component and once the component unmount, the state is also lost. So how can we use the same state in other components? If the other component is a child component, we can simply pass it through the props like so:
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
function Example() {
[count, setCount] = useState(0);
return (
<div>
<ChildComponent data={count} />
</div>
);
}
In our Example, we are passing our count
state to the ChildComponent
data prop. This way, ChildComponent
is able to access the same count
state. We can then continue to use the same pattern and pass the count state further down to a child of ChildComponent. This is called prop drilling.
Now what if we want to use the state in another component that is not a child where it is initialised? This is where we use a global state.
Global State
React provides us with a useContext hook to help us create a global state. This state will be globally accessible to any component of the application. Let’s look at how we can use a global state in the example:
const globalState = {
count: 0,
};
const globalStateContext = React.createContext(globalState);
const dispatchStateContext = React.createContext(undefined);
const [state, dispatch] = React.useReducer(
(state, newValue) => ({ ...state, ...newValue }),
globalState
);
const App = () => (
<globalStateContext.Provider value={state}>
<dispatchStateContext.Provider value={dispatch}>
<FirstComponent />
<SecondComponent />
</dispatchStateContext.Provider>
</globalStateContext.Provider>
);
At the very top level of our application (usually /src/App.js), we create the global state with React.createContext. globalStateContext
will hold our state and dispatchStateContext
will help us dispatch updates to our state. React.useReducer
is used to connect the state and dispatcher so that we can dispatch updates to the state. Lastly, we simply wrap both context around all our components, FirstComponent
and SecondComponent
. Notice that we do not need to pass the state as a prop to each component, but both components are able to access the state now.
Let’s look at how we can use the state in the FirstComponent
:
import React from 'react';
const FirstComponent = () => {
const state = React.useContext(globalStateContext);
const dispatch = React.useContext(dispatchStateContext);
return (
<button onClick={() => dispatch({ count: state.count + 1 })}>
{state.count}
</button>
);
};
Using the React.useContext
, we can access the state
and dispatch
function. In the return
statement, we are able to use state.count
directly and use dispatch
to update the state in the onClick
handler.
Since the state is global, changes made to the state in FirstComponent
will be updated in SecondComponent
and all other components that is referencing the state. This gives us the ability to use our state in all the components within our application.
Conclusion
There are many more concepts that i have learnt on my journey developing with React. It would take a few more posts to discuss everything. However, I hope that this article has helped you clear some doubts as you begin developing with React. If you are already comfortable with the concepts discussed here, I would suggest looking into hooks next. You may read it in the official documentation here.