how to work reusable state in class components? - reactjs

do class components support Reusable State in React 18? I wrote an example
componentDidMount() {
console.log("componentDidMount");
if (!this.mounted) {
this.fetchData();
this.mounted = true;
}
}
fetchData() {
fetch().then((data) => {
this.setState((statePrev) => {
console.log("prev data", statePrev); // always null after editing the file of this component and triggering Fast Refresh, how to work reusable state in class components?
return {
data: [
...(Array.isArray(statePrev?.data) ? statePrev?.data : []),
...data
]
};
});
});
}
I expected that when I edit and save the component file and React Refresh is triggered then statePrev will contain the previous state but it is always null
second problem is when React Refresh is triggered, the this.mounted property also does not work to determine that the mounting has occurred and you do not need to run fetch again

Related

Component did update works only after second click

My code adds a new item in the firebase databse when i click a button, then i want the list of objects in my page to automatically update, because i don't want to manualy reload the page. So i came up with this code
constructor(props){
super(props);
this.state = {
groups: [],
code:'',
name:'',
update:true
}
}
async fetchGroups (id){
fetchGroupsFirebase(id).then((res) => {this.setState({groups:res})})
};
async componentDidUpdate(prevProps,prevState){
if(this.state.update !== prevState.update){
await this.fetchGroups(this.props.user.id);
}
}
handleCreateSubmit = async event => {
event.preventDefault();
const{name} = this.state;
try{
firestore.collection("groups").add({
title:name,
owner:this.props.user.id
})
.then((ref) => {
firestore.collection("user-group").add({
idGroup:ref.id,
idUser:this.props.user.id
});
});
this.setState({update: !this.state.update});
}catch(error){
console.error(error);
}
What i was thinking, after i add the new item in firebase, i change the state.update variable, which triggers componentDidUpdate, which calls the new fetching.
I tried calling the fetchGroups function in the submit function, but that didn't work either.
What am i doing wrong and how could i fix it?
ComponentDidUpdate will not be called on initial render. You can either additionally use componentDidMount or replace the class component with a functional component and use the hook useEffect instead.
Regarding useEffect, this could be your effect:
useEffect(() => {
await this.fetchGroups(this.props.user.id);
}, [update]);
Since you can't use useEffect in class components so you would need to rewrite it as functional and replace your this.state with useState.

Why won't my changes to state show in my componentDidMount lifecycle?

I am building an app using React and for my homepage, I set state in the componentDidMount lifecycle:
export default class HomePage extends Component {
state = {
posts: [],
token: '',
};
//Display posts when homepage renders
componentDidMount() {
//If token exists, run lifecycle event
if (this.props.location.state.token) {
this.setState({ token: this.props.location.state.token });
}
Axios.get('http://localhost:3000/api/posts/all')
.then((req) => {
this.setState({ posts: req.data });
})
.catch((err) => {
console.log(err.message);
throw err;
});
console.log(this.state);
}
However when I run the console log at the end of the lifecycle method, it shows posts and token as still being empty. I know they are being populated because the posts from the req.data show up in my JSX. Why does it show state being empty when I console log inside the method?
React setState is asynchronous!
React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component.
Think of setState() as a request rather than an immediate command to update the component.
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});

using componentWillUnmount in context of next js

I'm trying to run a method on componentWillUnmount(i'm using the next js framework).The issue is that the componentWillUnmount method does not fire. However componentDidMount is working fine.
class TeamMember extends Component {
constructor(props)
{
super(props);
this.state = {
teamMember: this.props.teamMember,
startDate: null,
}
}
static async getInitialProps ( context ) {
const { slug } = context.query;
const res = await fetch(``);
const teamMember = await res.json();
return {
teamMember:teamMember
}
}
async componentDidMount()
{
this.setState({
startDate: Date.now()
})
Tracker.pushObjectToStorage('profilesViewed',{
title:this.state.teamMember[0].title.rendered,
id:this.state.teamMember[0].id
})
}
async componentWillUnmount(props)
{
alert("ddffff");
console.log("ddsds");
}
}
this is my code for the page. when you leave the page I want the componentWillUnmount to fire. i've put an alert there for test purposes.
This is expected and intended behavior of Nextjs routing. For more information, you can check this issue: https://github.com/zeit/next.js/issues/2819.
When you go to another page, no unmounting of components occurs, but instead, a whole new page is rendered.
This is the same behaviour as if you were refreshing (or landing for the first time) on a page. A React component will not unmount when you hit F5 on a page, because it is not unmounting, the page is simply refreshing.

Rendering same component both in try and catch block in React without loosing focus of fields in ReactJS

I am using an specific component in ReactJS which it may happen to throw some errors sometimes so I need to wrap it by try-catch block. My problem is that when it throws error I still need the component to be rendered and show the error to the user so I pass error to the component as prop if it is thrown. Look at this sample code:
myComponent = (error, data) => { /*render component*/}
renderComponent = () => {
try {
/*THE CODE WHICH MAY THROW ERRORS
BUT MAY ALSO RECEIVE DATA WITHOUT PROBLEM*/
return this.myComponent(undefined,data); //if everything is fine
} catch (e) {
return this.myComponent(e,undefined);
}
render {
return ({this.renderComponent()});
}
Everything is fine about this structure except one thing which is the fact that each time some errors happen the whole component resets which is logical because we are rendering whole another component (although it looks the same) but it loses the focus of text fields and scrolls up again and so on.
I tried to put data and error as state so they will all be set there inside the renderComponent and I'll call myComponent separately and pass the state, but in any case I had to use setState inside render so it causes problem as you know.
So I thought to ask you that what is your idea about this case. Am I unaware about some React capabilities?
Thanks
Since React 16 there's a new component-lifecycle called componentDidCatch, which is basically a try-catch for components.
With this lifecycle you can catch errors for specific components, so only that subtree of components break instead of the whole application - and you can render different content based on the state.
You can read more about it here.
In your case you can do something like:
class TryCatchComponent extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: undefined, errorInfo: undefined };
}
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true, error, errorInfo });
}
render() {
const { hasError, ...errorProps } = this.state;
if (hasError) {
const { fallback: FallbackComponent } = this.props;
return <FallbackComponent {...errorProps} />;
}
return this.props.children;
}
}
const SomeErrorDisplayComponent = ({ error, errorInfo }) => (
<div>{error} or {errorInfo}</div>
);
const SomeComponent = () => (
<TryCatchComponent fallback={SomeErrorDisplayComponent}>
<ComponentThatMightCrashSometime />
</TryCatchComponent>
);

Dispatching an action from another action

Let's say I've got these Flux actions;
{
"type": "USER_LOADED",
"payload": { ... }
}
{
"type": "USER_LOAD_FAILED",
"payload": { ... }
}
{
"type": "SHOW_ERROR_MODAL",
"payload": { ... }
}
{
"type": "HIDE_ERROR_MODAL"
}
I've got a UserStore (which listens to USER_LOADED and USER_LOAD_FAILED and updates accordingly) and a ModalStore (which listens to SHOW_ERROR_MODAL and updates accordingly).
I have a Modal component which is always present on the page, which renders content from the ModalStore.
What's the best way of showing an error modal when USER_LOAD_FAILED occurs? Should by ModalStore listen to it? I am going to end up with a lot of different types of *_LOAD_FAILED actions, so is this a good idea?
I can't dispatch from the UserStore in response to USER_LOAD_FAILED, as you can't dispatch during a dispatch.
I could dispatch from some "Controller" component, which does something along these lines;
class UserController extends PureComponent {
constructor(...args) {
super(...args);
this.state = { error: null, notified: false };
}
componentDidMount = () => this.props.flux.store('UserStore').on('change', this.onUserChange)
componentDidUpdate = () => {
if (this.state.error && !this.state.notified) {
this.props.flux.actions.showErrorModal(this.state.error);
this.setState({ notified: true });
}
}
componentWillUnmount = () => this.props.flux.store('UserStore').off('change', this.onUserChange)
onUserChange = () => {
const userStore = this.props.flux.store('UserStore');
// UserStore#getError() returns the most recent error which occurred (from USER_LOAD_FAILED).
const error = userStore.getError();
this.setState({ error, notified: error !== this.state.error });
}
render = () => ...
}
But I feel like this is just a workaround as opposed to an actual solution.
One last way I thought of was to just dispatch a SHOW_ERROR_MODAL inside the action creator which originally dispatched USER_LOAD_FAILED, but I still don't know if this is the "advised" way, as you could end up putting lots of logic in there for other cases.
If you are using Promises to make API calls, if you dispatch actions in your resolve or reject functions, it will not be triggered during the current dispatch.
Same is the case with callbacks. By the time your resolve/reject/callback functions execute, your original dispatch will have completed.
Having said that, I'm not sure why you chose to have your modal component be driven by a store. Are you making any API calls or doing some other processing before showing the modal? If not, perhaps you can consider showing/hiding your modal based on the state of your component.

Resources