ReactJS Interview Questions (Part 2)
Some ReactJS Interview Question I've been asked
Hello, I'm back to this series. I don't want to waste your time, so let's dive right into it now.
I think that the common answer for this question is very simple, if we update state directly, your app won't re-render, or won't work as you expected. But why? Let's get back to what we discussed last time in the part one of this series. React use Virtual DOM, it keeps track record of all its Virtual DOM. Whenever a change happens, all the components are rendered and this new virtual DOM is then compared with the old virtual DOM. Only the differences found are then reflected in the original DOM. When you change the state directly when React compares the old state and the new state, it sees no differences (because the references of them are still the same - this usually happens if you share state between multi-components, other components will not aware that the state was updated), and no differences mean no re-rendering needed. So that's the reason why update the state directly won't re-render the component as we expected.
⚠️ I have to say that in some cases if you mutate your state directly, your app might work perfectly fine, but using that kind of technique can lead to hidden bugs that really hard to figure out.
setState() causes re-rendering and it's an expensive operation and might leave the browser unresponsive.
So it's just for improving performance
But I want to go a little bit more in detail, setState() is not completely async, sometimes, it's sync. ReactJS takes many factors and variables into consideration then decides whether it should update state immediately or not.
In above question, we know that setState() is almost async. That means the state is not updated immediately, so what if I want to access the up-to-date value of state?
console.log(this.state.counter); // 3
this.setState({counter: this.counter + 1});
console.log(this.state.counter); // 3console.log(this.state.counter); // 3
this.setState({counter: this.counter + 1});
console.log(this.state.counter); // 3So how could I know that the state was updated successfully?
In the setState() func, you have a second parameter. That second parameter is a callback function and in that function, you can access to the updated state.
console.log(this.state.counter); // 3
this.setState({counter: this.state.counter + 1} ,() => {
console.log(this.state.counter); //3
});
console.log(this.state.counter); // 3console.log(this.state.counter); // 3
this.setState({counter: this.state.counter + 1} ,() => {
console.log(this.state.counter); //3
});
console.log(this.state.counter); // 3Okay, but now there is another problem
Consider the following piece of code:
console.log(this.state.counter); // 1
this.setState({counter: this.state.counter + 1});
this.setState({counter: this.state.counter + 1});
this.setState({counter: this.state.counter + 1});
console.log(this.state.counter); // 1console.log(this.state.counter); // 1
this.setState({counter: this.state.counter + 1});
this.setState({counter: this.state.counter + 1});
this.setState({counter: this.state.counter + 1});
console.log(this.state.counter); // 1As usual, we want it should be 4, but we get 1
To get rid of this issue, for guys who don't know the first parameter can not only object but a function.
Change above code to this and thing will work as we expected:
console.log(this.state.counter); // 1
this.setState(state => ({counter: state.counter + 1}));
this.setState(state => ({counter: state.counter + 1}));
this.setState(state => ({counter: state.counter + 1}));
console.log(this.state.counter); // 4console.log(this.state.counter); // 1
this.setState(state => ({counter: state.counter + 1}));
this.setState(state => ({counter: state.counter + 1}));
this.setState(state => ({counter: state.counter + 1}));
console.log(this.state.counter); // 4A key is a special string attribute you should include when creating arrays of elements. Key prop helps React identify which items have changed, are added, or are removed.
For example: If we have a list of 100 elements and render them without using a key, every time an element among these 100 elements change, React will re-render all 100 elements because it's not really knowing which element was changed. But using a key, React will know exactly which element was changed and changes that element only.
Redux is state management, but the downside of it is that when you create an action, you can only return plain objects. And it doesn't support async operation as well. So that is why Redux Thunk and Redux-Saga (actually there are some other libraries but these 2 are the most popular) come into play. In my opinion, the concepts of Redux Thunk and Redux Saga are the same. They all help us deal with the async operation, returning function instead of just object (and more stuff, but I think those are the main reason). Hmm, maybe you guys will wonder: hmm, why? we still can handle async operation (call API, get back data,..) on our own, right? Yeah, you can do that and it's fine. But it will be inconvenient when your app gets bigger and bigger like what I said in the previous post of this series, one of the benefits of Redux is helping us separate logic code and UI. The biggest difference that helps me distinguish Redux Thunk and Redux Saga is Redux-Saga built on top of Generator functions (a new feature since ES6 - I will have another post for this feature in the future) and that's it. -_- Yeah, just the way these 2 library are built. Up until now, I don't have any better answer for this question.
The Effect Hook lets you perform side effects in function components
You can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
When you use useEffect hook, you might notice that we can have a second parameter which is an array.
There are 3 forms of useEffect
1.useEffect with no second paraments useEffect(()=>) -> run on every component render
2.useEffect with second paraments as [] useEffect(()=>,[]) -> only run on the first time component render
3.useEffect with some arguments passed in the second parameter useEffect(()=>,[arg]) -> run if any of args changes
Custom Hook is a new feature represented since React 16.8. In my opinion, the custom hook is just another type of component, but instead of rendering UI, its purpose is for sharing common state, functions between components. the custom hook is a function started with the 'use' keyword (it's a rule according to ReactJS) and can call in other hooks. So note that you can only use custom hooks in the functional component. If you try using it in a class component, it will throw an error. Note that the custom hook is not a singleton, which means each time you call that custom hook in another hook/component, it will create a new instance of that custom hook so we will have useState, useEffect, custom functions for each instance, therefore, the state of each instance is isolated from the state in other instance. So keep an eye on it if you want to share the state of the custom hook between multi-component (maybe at that time you should consider using the custom hook and redux at the same time).
Note that it's just my personal answer to this question!
In real projects, I usually use a custom hook for separate logic code and UI code (yeah, that's one of the benefits of using Redux I mentioned just a few minutes ago). In my UI component, I want to make it as clean as possible and I want to keep the component just for rendering the UI, not for handling logic like filtering, mapping data, fetching data,... And it's the workflow in my project I'm following currently..
For example, every component in my project will have the following structure
badge (folder name)
---Badge.tsx (component)
---useBadge.ts (custom hook)
---badge.module.css (style)badge (folder name)
---Badge.tsx (component)
---useBadge.ts (custom hook)
---badge.module.css (style)In my Badge.tsx file, I will handle rendering UI and only UI
// Badget.tsx
const Badge = () => {
const {data} = useBadge();
return (
// UI here
)
}
export default Badge;// Badget.tsx
const Badge = () => {
const {data} = useBadge();
return (
// UI here
)
}
export default Badge;All other stuff like fetching data, data manipulation I will put them all in useBadge component
const useBadge = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData();
}, []); // this will denote that useEffect will run once
const fetchData = async () => {
const data = // ..handle call api for fetching data
setData(data);
}
return {
data
}
}const useBadge = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData();
}, []); // this will denote that useEffect will run once
const fetchData = async () => {
const data = // ..handle call api for fetching data
setData(data);
}
return {
data
}
}You use Higher Order Component to "wrap" some components which share a common state, layout...
The input of HOC is a component and the output is also a component.
This is an example of HOC:
// class component style
import React, { Component } from "react";
import Footer from "./Footer";
import Header from "./Header";
const BaseView = (WrappedComponent, props) => {
class HocComponent extends Component {
render() {
return (
<React.Fragment>
<Heade/>
<WrappedComponent {...this.props}></WrappedComponent>
<Footer/>
</React.Fragment>
);
}
};
export default BaseView;
// functional component style
import React from "react";
import Footer from "../Footer";
import Header from "../Header";
const BaseView =(WrappedComponent) => (props) => {
return (
<section>
<Header />
<WrappedComponent {...props} />
<Footer />
</section>
);
};
export default BaseView;
// class component style
import React, { Component } from "react";
import Footer from "./Footer";
import Header from "./Header";
const BaseView = (WrappedComponent, props) => {
class HocComponent extends Component {
render() {
return (
<React.Fragment>
<Heade/>
<WrappedComponent {...this.props}></WrappedComponent>
<Footer/>
</React.Fragment>
);
}
};
export default BaseView;
// functional component style
import React from "react";
import Footer from "../Footer";
import Header from "../Header";
const BaseView =(WrappedComponent) => (props) => {
return (
<section>
<Header />
<WrappedComponent {...props} />
<Footer />
</section>
);
};
export default BaseView;Usage:
import React from "react";
import BaseView from '[path to baseview component]';
const SomeComponent = () => {
return (
// your ui
);
};
export default BaseView(Home);import React from "react";
import BaseView from '[path to baseview component]';
const SomeComponent = () => {
return (
// your ui
);
};
export default BaseView(Home);So in the above example, the BaseView component will "wrap" the Home component into a new component which has Header and Footer components. If we don't use HOC, we will have to import Header, Footer in each component every time a component has a Header and Footer. That's the case when HOC is useful.
Update 2022:
The use case I used in this question is not really correct. Because we have another concept in React which solves the same thing. It's Wrapper Component.
const Parent = ({children}) => {
return <div>
{children}
</div>
}const Parent = ({children}) => {
return <div>
{children}
</div>
}Even though they might solve the same thing but the use cases of HOC are more than this. Except for the use case I used. We have other common use cases where HOC is useful. For example:
- State abstraction: Pass props as well as callbacks to handle those props
const Children = (props) => <input {...props} />
const hoc = WrappedComponent => props => {
const [name, setName] = useState('');
const onChange = (e) => setName(e.target.value);
return (
<div>
<WrappedComponent {...props} {name, onChange}/>
</div>
)
}
// use hoc
hoc(Children)const Children = (props) => <input {...props} />
const hoc = WrappedComponent => props => {
const [name, setName] = useState('');
const onChange = (e) => setName(e.target.value);
return (
<div>
<WrappedComponent {...props} {name, onChange}/>
</div>
)
}
// use hoc
hoc(Children)- Manipulate props: We can edit, remove, add props which passed to the WrappedComponent
- Inheritance Inversion
- Props proxy
For more detail, you can read this: https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e
This should be divided into 2 separate questions, but the answers for both of them are the same so I think putting them together is fine.
The first question here is: Can we directly update them? Yes, we definitely can and it works without any error.
With changing components’ props, People always say it is an anti-pattern but I don’t see any explanation for the reason why we shouldn’t do that. The answer is simple, just like updating the state of a component directly without calling setState, if you update the props of a component, it won’t be re-rendered and you will see no change (actually the props changed but your component doesn’t re-render so you won’t see any change at all). The interesting part here is that if you listen to changes of props using useEffect, it won’t work either.
The same thing happens when you try changing the ref.current, your component doesn’t re-render and if you create an useEffect to listen to changes of ref.current you even will get a warning that tells you that passing ref. The current dependency list is useless.
Another question for this one:
What if I update props that are passed to the initial value of some states of children's components? Is the state changed?
→ No, you can imagine that the initial value of state is only used when the first time the component is mounted. I think that it’s reasonable, imagine that the initial value is used at every render, how can our component up-to-date with our current state (let’s assume that we changed them some times before the component is re-rendered).
We all know that React does some inner stuff to batch the update of its states, it’s smart enough to determine when and where the states should be updated in order to reduce the number of times the component re-renders. But we should notice that React is only able to batch updating states on the function which is handled by React. For example, within the event handler functions (i.e onClick). In other cases, i.e in the callback function of setTimeout, setInterval, Promise, React won’t be able to handle batch updating states. Therefore, if you call multiple setState within setTimeout, setInterval, or any other function which is not handled by React, the outer component might be re-rendered multiple times.
⚠️ Since React 18, it seems that React find a way to deal with this issue. It’s now able to batch updating setStates .
Okay, that's it. I have so many other questions and want to share with you guys, but I think that those questions are too common and there are a ton of answers out there before me so I don't want to repeat that. I put these questions into my post just because for each question, I have some extra information for them and I think it's really helpful, other answers I found on the Internet usually answer the question in general, and sometimes we couldn't get the point of the answer. My English is not my mother tongue so sometimes I made some mistake, beer with me. I tried to explain in the easiest way that I could tell, but if you guys have any question or the answer for any question which you think it's not clear, you can comment and I'll reply and fix it. Thank you very much!
Ref:
https://medium.com/analytics-vidhya/why-we-should-never-update-react-state-directly-c1b794fac59b
https://stackoverflow.com/questions/36085726/why-is-setstate-in-reactjs-async-instead-of-sync