Converting a React Class Component to a Function Component - reactjs

I've been trying to convert the following code from React Class Component to Function Component but I've been having problems since I've gotten the error "Expected an assignment or function call and instead saw an expression. eslint no-unused-expressions"
componentDidMount() {
this.startingSequence();
}
startingSequence = () => {
setTimeout(() => {
this.setState(
() => {
return {
textMessageOne: `A wild ${this.state.enemyName} appeared!`,
enemyFaint: false
};
},
() => {
setTimeout(() => {
this.setState(
{
textMessageOne: `Go ${this.state.playerName}!`,
playerFaint: false
},
() => {
setTimeout(() => {
this.setState({
textMessageOne: ""
});
}, 3000);
}
);
}, 3000);
}
);
}, 1000);
};
This is the code I ended up with while trying to convert it to Function Component:
const startingSequence = () => {
setTimeout(() => {
() => {
setTextMessageOne(state => {
state = (`Wild ${enemyName} appeared!`)
return state;})
setEnemyFaint(state => {
state = false
return state;})
}
,
() => {
setTimeout(() => {
setTextMessageOne(`Go ${playerName}!`),
setPlayerFaint(false)
,
() => {
setTimeout(() => {
setTextMessageOne("")
}, 3000);
}
}, 3000);
}
}, 1000);
};
useEffect(() => {
startingSequence();
})
EDIT:
Solution I got thanks to Kieran Osgood:
const startingSequence = () => {
setTimeout(() => {
setTextMessageOne(`Wild ${enemyName} appeared!`)
setEnemyFaint(false)
setTimeout(() => {
setTextMessageOne(`Go ${playerName}!`)
setPlayerFaint(false)
setTimeout(() => {
setTextMessageOne('')
}, 3000)
}, 3000)
}, 1000)
}
useEffect(() => {
startingSequence()
}, [enemyFaint])

In the functional component syntax you can pass the new state in directly OR use the function syntax if you need access to the previous state, however the state variable is not assignable so when you're doing this:
setTextMessageOne(state => {
state = `Wild ${enemyName} appeared!`
return state
})
You could do it simply like this:
setTextMessageOne(`Wild ${enemyName} appeared!`)
Function syntax is helpful for lets say a counter, where we're incrementing a number, and avoids getting stale closures overlapping each other.
setCounter(previousState => {
return previousState + 1
})
// OR
setCounter(previousState => previousState + 1)
So amending that, the other issue is theres a lot of nested arrow functions which seem to stem from the previous usage of the second argument to setState which is a callback to be executed immediately after the state is set - this doesn't exist in functional components, so you should probably refactor this function to be something more along the lines of
// this is just a basic representation, consider combining these to objects etc.
const [enemyName, setEnemyName] = React.useState('')
const [enemyFaint, setEnemyFaint] = React.useState(false)
const [playerFaint, setPlayerFaint] = React.useState(false)
const [textMessageOne, setTextMessageOne] = React.useState('')
const [playerName, setPlayerName] = React.useState('')
const startingSequence = () => {
setTimeout(() => {
setTextMessageOne(state => {
state = `Wild ${enemyName} appeared!`
return state
})
setEnemyFaint(false)
}, 1000)
}
React.useEffect(() => {
setTimeout(() => {
setTextMessageOne(`Go ${playerName}!`)
setPlayerFaint(false)
setTimeout(() => {
setTextMessageOne('')
}, 3000)
}, 3000)
}, [enemyFaint])
Then you want to take these further to extract into custom hooks so its more clear your intent in the flow of your component but generally this is the way in functional components to respond to state changes, via the useEffect

Related

Cannot setstate in nested axios post request in react

I am trying to access the res.data.id from a nested axios.post call and assign it to 'activeId' variable. I am calling the handleSaveAll() function on a button Click event. When the button is clicked, When I console the 'res.data.Id', its returning the value properly, but when I console the 'activeId', it's returning null, which means the 'res.data.id' cannot be assigned. Does anyone have a solution? Thanks in advance
const [activeId, setActiveId] = useState(null);
useEffect(() => {}, [activeId]);
const save1 = () => {
axios.get(api1, getDefaultHeaders())
.then(() => {
const data = {item1: item1,};
axios.post(api2, data, getDefaultHeaders()).then((res) => {
setActiveId(res.data.id);
console.log(res.data.id); // result: e.g. 10
});
});
};
const save2 = () => {
console.log(activeId); // result: null
};
const handleSaveAll = () => {
save1();
save2();
console.log(activeId); // result: again its still null
};
return (
<button type='submit' onClick={handleSaveAll}>Save</button>
);
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, like in your example, the console.log function runs before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
console.log(activeId);
}, [activeId);
The callback will run every time the state value changes and only after it has finished changing and a render has occurred.
Edit:
Based on the discussion in the comments.
const handleSaveSections = () => {
// ... Your logic with the `setState` at the end.
}
useEffect(() => {
if (activeId === null) {
return;
}
save2(); // ( or any other function / logic you need )
}, [activeId]);
return (
<button onClick={handleSaveSections}>Click me!</button>
)
As the setState is a async task, you will not see the changes directly.
If you want to see the changes after the axios call, you can use the following code :
axios.post(api2, data, getDefaultHeaders())
.then((res) => {
setActiveId(res.data.id)
console.log(res.data.id) // result: e.g. 10
setTimeout(()=>console.log(activeId),0);
})
useEffect(() => {
}, [activeId]);
const [activeId, setActiveId] = useState(null);
const save1 = () => {
const handleSaveSections = async () => {
activeMetric &&
axios.get(api1, getDefaultHeaders()).then(res => {
if (res.data.length > 0) {
Swal.fire({
text: 'Record already exists',
icon: 'error',
});
return false;
}
else {
const data = {
item1: item1,
item2: item2
}
axios.post(api2, data, getDefaultHeaders())
.then((res) => {
setActiveId(res.data.id)
console.log(res.data.id) // result: e.g. 10
})
}
});
}
handleSaveSections()
}
const save2 = () => {
console.log(activeId); //correct result would be shown here
}
const handleSaveAll = () => {
save1();
save2();
}
return (
<button type="submit" onClick={handleSaveAll}>Save</button>
)

How to properly use clearInterval in this function?

I have a function that sets a reminder to pop up on the screen, but the message wont go away. Am I using the clearInterval with react hooks correctly in this function?
useEffect(() => {
const interval = setInterval(() => {
handleReminder(activeReminders);
}, 1000);
return () => clearInterval(interval);
}, [activeReminders]);
useEffect(() => {
const notesWithReminder = getNotesWithReminder(notes);
if (notesWithReminder.length > 0) {
setActiveReminders(notesWithReminder);
}
}, [notes]);
function getNotesWithReminder(notes) {
return notes.filter((note) => note.reminder && !note.isReminderShow);
}
function handleReminder(reminders) {
const activeRem = reminders.find((rem) => {
const now = Date.now();
const getRemTime = new Date(rem.reminder).getTime();
return getRemTime <= now;
});
setActiveReminder(activeRem);
setShowNotifyModal(true);
}
Message was not dismissing due to if statement which created a memory leak.
solution:
useEffect(() => {
const notesWithReminder = getNotesWithReminder(notes);
setActiveReminders(notesWithReminder);
}, [notes]);

Functions in a jest test only work when launched alone, but not at the same time

I have a custom hook that updates a state. The state is made with immer thanks to useImmer().
I have written the tests with Jest & "testing-library" - which allows to test hooks -.
All the functions work when launched alone. But when I launch them all in the same time, only the first one succeed. How so?
Here is the hook: (simplified for the sake of clarity):
export default function useSettingsModaleEditor(draftPage) {
const [settings, setSettings] = useImmer(draftPage);
const enablePeriodSelector = (enable: boolean) => {
return setSettings((draftSettings) => {
draftSettings.periodSelector = enable;
});
};
const enableDynamicFilter = (enable: boolean) => {
return setSettings((draftSettings) => {
draftSettings.filters.dynamic = enable;
});
};
const resetState = () => {
return setSettings((draftSettings) => {
draftSettings.filters.dynamic = draftPage.filters.dynamic;
draftSettings.periodSelector = draftPage.periodSelector;
draftSettings.filters.static = draftPage.filters.static;
});
};
return {
settings,
enablePeriodSelector,
enableDynamicFilter,
resetState,
};
}
And the test:
describe("enablePeriodSelector", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches period selector", () => {
act(() => result.current.enablePeriodSelector(true));
expect(result.current.settings.periodSelector).toBeTruthy();
act(() => result.current.enablePeriodSelector(false));
expect(result.current.settings.periodSelector).toBeFalsy();
});
});
describe("enableDynamicFilter", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches dynamic filter selector", () => {
act(() => result.current.enableDynamicFilter(true));
expect(result.current.settings.filters.dynamic).toBeTruthy();
act(() => result.current.enableDynamicFilter(false));
expect(result.current.settings.filters.dynamic).toBeFalsy();
});
});
describe("resetState", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches dynamic filter selector", () => {
act(() => result.current.enableDynamicFilter(true));
act(() => result.current.enablePeriodSelector(true));
act(() => result.current.addShortcut(Facet.Focuses));
act(() => result.current.resetState());
expect(result.current.settings.periodSelector).toBeFalsy();
expect(result.current.settings.filters.dynamic).toBeFalsy();
expect(result.current.settings.filters.static).toEqual([]);
});
});
All functions works in real life. How to fix this? Thanks!
use beforeEach and reset all mocks(functions has stale closure data) or make common logic to test differently and use that logic to test specific cases.
The answer was: useHook is called before "it". It must be called below.

componentWillUnmount by using hooks with params dependecies

useEffect(() => {
return () => {
let tb = [...Array(row.length).keys()].map(index => {
return header.reduce((ob, key) => {
return {
...ob,
[key]: row[index][key]
}
}, {})
})
if (dimension) {
tb = tb.map((entity, index) => {
return {
...entity,
'': row[index].hdr
}
})
}
dispatch(change(form, `table.${activeButton}`, tb))
}
}, [])
I want to execute some code on component unmount, I know that achieve this you can by using this approach with hooks :
useEffect(() => {
return () => {
// some code here
}
}, [])
But in first code snippet I have some values which are not static , and for getting last version of this data , I have to pass it to [row,header], and this is the problem , does it mean that I can't execute this code on unmount ? or exist some another way to make it ?
The fact that you want to execute code on unmount and access the updated data, you would need to make use of useRef and useEffect
const rowRef = useRef(row);
const headerRef = useRef(header);
useEffect(() => {
rowRef.current = row;
headerRef.current = header;
}, [row, header]);
useEffect(() => {
return () => {
let tb = [...Array(rowRef.current.length).keys()].map(index => {
return headerRef.current.reduce((ob, key) => {
return {
...ob,
[key]: rowRef.current[index][key]
}
}, {})
})
if (dimension) {
tb = tb.map((entity, index) => {
return {
...entity,
'': rowRef.current[index].hdr
}
})
}
dispatch(change(form, `table.${activeButton}`, tb))
}
}, [])

When the return within useEffect got execute?

I have the following pseudo code
const handleUploadValidateResult = useCallback(e => {
if (everything good) {
do something
} else {
do something else
}
}, []);
useEffect(() => {
const eventName = `${context}_${type}_${index}`;
window.addEventListener(eventName, e => {
handleUploadValidateResult(e);
});
return () => {
window.removeEventListener(eventName, e => {
handleUploadValidateResult(e);
});
};
}, [type, index]);
What is the execution order for the return statement
return () => {
...
}
When type or index got changed, is return statement executed
before useEffect?
or after useEffect?
Your useEffect it called after type or index changes. return function is called before the component is unmounted.

Resources