Academind Logo

React Q&A

Stateless vs stateful components? Using 3rd party libraries? How to manage state? Hosting and deployment? Broken routing? Find out how it works!

Created by Maximilian Schwarzmüller
#

Summary

There are some questions I see quite a lot. Let me try to answer them - both in the video and this article!

  1. Do you need a complex project setup? (0:27)

#

Do I need a Complex Setup?

Or can you just import react.js and react-dom.js into your .html file and get started writing awesome React code?

The answer is: Yes, you can do that, you don't need a complex setup (i.e. one that uses Webpack, Babel and what have you).

The easiest way to get started with React is indeed to simply add the following imports to your HTML file:

<body>
...
<script src="path/to/react.js"></script>
<script src="path/to/react-dom.js"></script>
</body>

Alternatively, you can of course use CDN links.

You might want a more complex setup - that means a build workflow that includes JavaScript transpilation, linting, bundling etc - for bigger apps AND if you're building single page applications (SPAs).

Why?

Simply because it's easier for your as a developer!

You get a lot of convenience features, warnings if you're writing bad or incorrect code, highly optimized code and you can use next-gen JavaScript features as well as JSX - a nice way of creating HTML elements from within JavaScript.

But if you're just trying to add some React magic to your app, definitely go with the simple drop-in approach.

If you want a more complex workflow, I strongly recommend using the create-react-app though - it's basically a CLI that makes the project setup a breeze!

#

{ } vs {{ }}

Here's another thing that can be confusing: { } vs {{ }} - when do you use which for outputting dynamic content?

The answer is simple: Unlike in Angular or Vue, the syntax for outputting dynamic content in your "templates" (you don't really have templates in React - you're just writing JavaScript code in the end) is { } not {{ }}.

Since you're just writing JavaScript code in React, you stick closer to the original JavaScript syntax if you get used to using { }. It is a syntax you use regularly, after all, when writing JavaScript code.

You still sometimes see {{ }} in React apps, too, though. Let's have a look at an example:

<p style={{ color: 'red' }}>I'm red!</p>

This can be confusing but in the end, you still only used { } to output dynamic content here. The dynamic content just happens to be a JavaScript object: {color: 'red'}.

And that's all that's to it!

#

Functional (stateless) vs class-based (stateful) Components

In React Apps, you can create two types of components: Functional, stateless ones and class-based, stateful components.

Here's an example for a functional component:

import React from 'react';
const MyComponent = (props) => {
return <p>I'm a functional component!</p>;
};

And here's the class-based alternative:

import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return <p>I'm a class-based component!</p>;
}
}

Both components will render exactly the same result, so which approach should you use? What's the advantage of each of the two approaches?

#

The class-based - or stateful - Approach

Let's start with the class-based approach. It's also called stateful and this second name already shows what it's all about: Components that are written by creating a class that extends Component can manage an internal state.

Functional - stateless - components can't do that! More on that in a second.

Back to the class-based, stateful components for now. Here's an example where we do manage state:

import React, { Component } from 'react';
class NameInput extends Component {
state = { name: '' }; // Initialize state
nameChangedHandler = (event) => {
this.setState({ name: event.target.value }); // Update state with entered name
};
render() {
return (
<div>
<p>Hello {this.state.name}</p>
<input
type="text"
onChange={this.nameChangedHandler}
value={this.state.name}
/>
</div>
);
}
}

In this example, the component is able to output the user name. To allow the user to change that name, it's stored in the state property of the class. Since the class extends Component this is a special property.

The key thing about state is, that it can be changed, during runtime, from within the component. Changing the state will also trigger React to re-render the component.

Additionally to state, you can also implement lifecycle hooks to your stateful components.

#

The functional - stateless - Approach

So we learned about the class-based approach. You can use state and lifecycle methods there.

What about the functional - so called stateless - approach?

Well, as the name implies, you can't use state there.

The only datasource you have in such components is props - i.e. data received from outside. When props change, this will also trigger React to re-render the component.

Here's an example for a component using props:

import React from 'react';
const GreetUser = (props) => {
return <p>Hello {props.name}</p>;
};
export default GreetUser;

This component would be used like this:

import GreetUser from './path/to/greetUser' // omit the .js
...
<GreetUser name="Max" />
// OR something like this:
<GreetUser name={this.state.name} />

Why would you use this "limited" version of a component instead of the "more powerful" stateful alternative?

Because it makes your apps easier to maintain and understand. You have clearly defined responsibilities and a predictable flow of data. Stateful and stateless components work well together!.

#

When to use which Type of Component

Let's see an example for how stateful and stateless components may work well together:

import React, { Component } from 'react';
import GreetUser from 'path/to/greetUser';
class NameInput extends Component {
state = { name: '' }; // Initialize state
nameChangedHandler = (event) => {
this.setState({ name: event.target.value }); // Update state with entered name
};
render() {
return (
<div>
<GreetUser name={this.state.name} />
<input
type="text"
onChange={this.nameChangedHandler}
value={this.state.name}
/>
</div>
);
}
}

Here, we have on stateful component - NameInput - where we allow the user to change his or her name.

And we use one stateless component - GreetUser - where we simply render a name. We receive it via props.

The advantages here are:

  • GreetUser is really re-usable. It makes no assumptions other than that it receives a name props.

  • We only change and manage our state in one component. This makes it easy for us to trace the flow of data.

#

Different setState Syntaxes

When working with this.setState, you might've come across two different ways of using it:

this.setState({ counter: this.state.counter + 1 });

and

this.setState((prevState) => {
return { counter: prevState.counter + 1 };
});

The first syntax is actually wrong/ suboptimal in this case.

Why?

It's not that widely known but setState actually updates the state asynchronously (Source: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous).

Due to this possible delay between your code execution and the actual state update, the first syntax could lead to a counter that's not incrementing correctly. this.state.counter simply may not have been updated yet when you use it to update the state again.

The recommend way of updating the state - if you depend on the old state - is to use the function-form of setState:

this.setState((prevState) => {
return { counter: prevState.counter + 1 };
});

With this syntax, you pass a function to setState that receives two arguments: prevState and prevProps - so the state and props AFTER the last successful state update.

Inside of the function, you then return the object that should be merged with the old state.

By using this approach, you're guaranteed to correctly access the old state you're depending on.

#

What does this map() Thing Mean?

When writing React apps, you'll use map() quite a bit. Typically to output an array of elements as JSX.

Here's an example:

const hobbies = ['Sports', 'Cooking', 'Reading'];
return hobbies.map((hobby) => <p key={hobby}>{hobby}</p>);

This will output three <p> elements that print the respective hobby.

map() does one simple thing: It maps one array into a new one (you can dive deeper here).

So in the above snippet, it transforms an array of strings into an array of JSX elements that print the text to the screen. This array can then be rendered to the DOM by React.

#

Styling React - So many Choices!

When it comes to styling React apps - we got plenty of choices.

You typically start with inline styles:

<p style={{ color: 'red' }}>Hello there!</p>

The advantage of this approach is, that the style is scoped to this component and only applies to the element you added it two. You can also set the styles dynamically like this:

<p style={{ color: this.state.chosenColor }}>Hello there!</p>

The disadvantage is, that you don't use real CSS here and that you can't use pseudo selectors or media queries therefore. Additionally, it quickly bloats your JSX code if you put more complex styling code in there.

There are many packages and approaches that try to solve these disadvantages and make styling of React apps easier. I can recommend looking into the following libraries and solutions (ordered in no particular order).

#

How to integrate 3rd Party CSS and JS Libraries

"How can I add library XY to my React project?"

That's a question I see quite a lot. The good thing is: It's really easy to add libraries to a React project. I'm going to assume a project which uses a setup created via create-react-app. If you just dropped React into a HTML file, you can do the same for other packages.

So how do you add a library then?

The easiest solution is to use npm:

npm install --save my-library

Once that's done, you can import it into your main.js or index.js (whatever you have) file.

Yes, you read this correctly - I didn't ask whether it's a CSS or JS library, you can import both types of files - Webpack (to be precise: some webpack module loader) will figure it out and make sure everything gets bundled and loaded correctly.

Here's an example how that would look like for some JS library:

import 'my-library';
// Or:
import 'path/to/my/file.js'; // though you can omit the .js

For a CSS file it would look like this:

import 'path/to/my/file.css'; // DON'T omit .css - Webpack needs that
#

What's up with this "Immutable" Thing?

Once you dive a little bit deeper into React, you'll read it all the time: "Update state immutably".

But what does this mean and why is this important?

It means that you update arrays and objects by replacing the old value with a totally new one. You don't mutate the old value.

Why is this recommended?

Because objects and arrays are reference types in JavaScript. This means, that a variable (or class property) storing an object (or array) doesn't really store the value but only a pointer to some place in memory where the object/ array value lives.

If you then assign the object to a new variable, that variable will only copy the pointer and therefore still point to the same, single object/ array in memory.

Here's an example to what this can lead:

const person = { name: 'Max' };
const copiedPerson = person;
console.log(copiedPerson.name); // prints 'Max'
copiedPerson.name = 'Sven';
console.log(person.name); // prints 'Sven'

In this example, console.log(person.name) also prints 'Sven' even though only copiedPerson.name was changed. This happens due to this reference-type behavior.

You can learn more about it - and the difference to primitive types - in this video:

Now that we know why we should update objects and arrays immutably - how does that actually work?

You can use different approaches - let's have a look at immutably updating objects first:

const person = { name: 'Max' };
const copiedPerson = Object.assign({}, person);

Object.assign() assigns all properties of the object which is passed as the second argument to its first argument - typically an empty object if you want to copy an object.

This will yield you a brand-new object which simply copies the values of all the properties of the old object.

If you use next-gen JavaScript syntax, this becomes even easier:

const person = { name: 'Max' };
const copiedPerson = { ...person };

Here, we use the next-gen Spread Operator for Objects.

This also creates a new object and copies all the old properties.

We can also copy arrays immutable:

const hobbies = ['Sports', 'Cooking'];
const copiedHobbies = hobbies.splice();
// OR:
const copiedHobbies = [...hobbies];

Just as with objects, we can use the spread operator for arrays, too.

Alternatively, you can use the slice method to create a brand-new copy of an array.

Important: All these approaches still only create a shallow copies of arrays and objects!

If your copied object or array had some nested properties or elements (i.e. properties/ elements that held another object or array), these objects would not be copied. You'd still only copy a pointer to them!

For deeply copying objects and arrays, have a look at the following article: https://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html

#

Can Everyone see your Code?

If you open your developer tools, you'll notice that you can view your source code (even the un-minified one, thanks to sourcemaps) relatively easy.

Everyone can see your code!

JavaScript runs in the browser and it's not pre-compiled - this means that it's readable by everyone who's on your web page. It may be minified and obfuscated but this can only make it harder, not prevent it. There's nothing you can do about that!

Is this a deal breaker though?

No! It just means that you shouldn't put security-relevant or sensible information into your frontend JavaScript code. All your other code may be accessible but it typically also doesn't hold any important information for hackers.

Due to your code being accessible to everyone, you should always rely on server-side validation of user input though. You can write the best validation code in the world using Angular's form validation capabilities but since any use can simply re-write or break that code, you always need to validate on the server, too!

Frontend code is all about providing a good user experience (e.g. via instant validation results) not about securing your database.

#

"Can I use React without Redux?"

When diving into React, you sometimes get the impression you can't use it without Redux.

This is a wrong impression though!

Redux is extremely helpful if you're building bigger React apps as it makes state management a lot easier and leads to far more maintainable code.

But especially in smaller projects, it might be overkill to include Redux and set everything up for it.

Additionally, there are alternatives - most importantly MobX.

MobX also makes state management easier, it's also suited for very big apps but it follows a different, observable-based approach. Definitely check it out and decide if you need Redux, no Redux or want to use MobX!

#

"Can I use React with PHP/Node/...?"

I often see the question whether you can use React with PHP, Node.js or some other server-side language.

And the answer is: Yes, absolutely!

React doesn't care about your backend and your server-side language, it's a client-side framework, it runs in the browser!

It also doesn't matter if you drop your React imports into the (server-rendered) HTML files or if you're building a SPA. In both cases, React only manages the frontend!

If you're building a SPA, you only communicate with servers through Ajax requests (via libraries like axios), hence your backend needs to provide a (RESTful) API to which React can send its requests. And that's all!

One exception is important though: If you're using server-side pre-rendering of React apps (e.g. via Next.js), you'll still not use React to write server-side code (i.e. to access databases, work with file storage or anything like that) but you pre-render your React apps on the server. That only works with Node.js as of now though, so if that's important to you, you should go with Node.js

#

How to prevent State Loss after Page Refreshes

Here's a question I observed a lot: "If I press the refresh button in my browser, my app state resets. How can I prevent this?"

It's true! If you hit that refresh button, your page reloads and since React runs entirely in JavaScript, your script restarts and you app state is lost. That's not a bug but the expected behavior.

If you're coming from a "traditional" web development environment, where you built web pages by rendering views in your server-side language, that's a strange behavior. You used to use Sessions to manage the user state for example. Page refreshes would therefore not lose that state.

In single page applications React (or other frameworks and libraries) creates, you don't use sessions though. The reason is simple: Your app is decoupled from your backend. You only have one single page in the end and your (RESTful) API to which you talk occasionally doesn't care about your app - it's stateless.

If you're dropping React into server-rendered HTML files though, this doesn't matter. Your server is taking care about all views and tying them together.

But back to SPAs: So you don't use sessions - what do you do instead?

You have two common options to still persist user state (e.g. "Is the user logged in"?):

  1. Use localStorage to store simple key-value pairs like a JSON Web Token (JWT) you got from your backend (a pattern commonly used for authentication in SPAs).

  2. Use a server-side database for state/ data that needs to persists long-term.

localStorage is a common choice to store client state. It's lost if the user clears all browser data, so it's not suitable for data that needs to be stored long-term. It's also accessible via JavaScript (and by the user), so you shouldn't store security-relevant information there. You do regularly use it to store JWTs though - tokens that are short-lived (for security reasons) and grant the user access to some protected resources on the backend.

You can use localStorage to store state across page refreshes by following this pattern:

  1. Store data in localStorage once you have it (e.g. store a JWT once you received it).

  2. When your app starts, check if the data is stored in localStorage - a good place is inside the componentDidMount lifecycle method in your main.js file since that will execute right at the start of your application life.

  3. Initialize your app with any (optionally validated) data you got from localStorage if available.

By using this pattern, you can start your app in the same state you left it.

Use server-side databases for any data that's not really relevant to the client upon page refreshes but which should instead be stored long-term or which should be synchronized across different devices (e.g. a selected user location).

#

"Can I host my App on Heroku etc?"

"Can I host my React single page app on Heroku or a comparable service"?

That's a question I also see a lot!

The answer is: Generally, yes - you can of course host your app on any web hosting service. But there are services which are better suited than others. Heroku for example is a service that's built to allow you to easily host PHP, Node.js etc. apps. It spins up fitting environments (i.e. configures the servers, interpreters etc needed by the selected language) and makes sure that you don't have to spend hours setting this all up.

But React doesn't use any server-side language! It's - after your ran npm run build - just a bunch of JavaScript and CSS files as well as the index.html file. You don't need a Node server for that!

Therefore, for React single page apps, static website hosts like AWS S3 or Firebase Hosting are better choices. They're typically cheaper, super-easy to setup and don't require you to add overhead code (like a dummy Node.js server) to just ship your files to your users.

If you're just using React in server-side rendere apps, i.e. where some server-side templating engine generates the views, you will need a host that runs that server-side language of course.

#

How to fix broken Routes after Deployment

After deploying a React single page app to a real server, I sometimes hear that the routing stops working. At least if users directly visit any other page than the main page - say https://my-app.com/products or if users refresh the browser whilst being on such a "sub-page".

To understand this "error" (it's not really an error), we have to understand how routing really works in an React app.

React stores the routes, not the server

React stores and manages your routes, not the server that's hosting your React app! Keep in mind that that server only serves the index.html file - that's about its only job!

Important: This is only true for React single page apps!

Therefore, your server can't do anything with an incoming request pointing at a /products route - it's totally unknown to it! Due to the way the web works, requests reach the server though and your client (i.e. the React app) doesn't even get loaded as the server throws a 404 error.

So what can we do to solve that issue?

The solution is simple: Your server should always serve the index.html file, especially in 404 error cases! If you configure your server to do that (static hosts like AWS S3 or Firebase provide easy-to-use configurations to achieve this behavior), your React app gets loaded and gets a chance to handle the request.

If you still want to render a 404 error page for routes that are really unknown, i.e. that are also not configured in your React app, you'll ne to add a catch-all route like this:

// Configure all other routes first or they'll not be considered!
<Route component={404Page}/>

Recommended Courses