How to implement usePrevious in react - javascript

I took this code from React documentation. But apparently, I am not using it right.
import { useEffect, useRef } from 'react';
export default function usePreviousState(state) {
const ref = useRef();
useEffect(() => {
ref.current = state;
});
return ref.current;
}
This is my functional component:
export default function personInfo({ data, setData }) {
const classes = useStyles();
const prevData = usePreviousState(data);
function handleFieldChange(event) {
setData({
...data,
[event.target.id]: event.target.value,
});
}
async function handleSaveClick(event) {
event.preventDefault();
const formData = normalizeData(data);
try {
if (JSON.stringify(data) !== JSON.stringify(prevData)) {
await updatePeronInfo(formData);
alert('Successfully updated personal information!');
} else {
await createPersonInfo(data);
alert('Your personal information has been saved!');
}
} catch (error) {
handleErrors(error);
}
}
The first problem is, that I cannot post the information, it is always giving me POST and PATCH, but it is throwing 404 error Page not found, so it did not create or update the information at all. If I change the if statement from (JSON.stringify(data) !== JSON.stringify(prevData)) to (JSON.stringify(data) === JSON.stringify(prevData)) somehow it manage to POST the data. But later on if I want to update the info it appears that it is trying to execute createPersonInfo, which leads to an error 500 Internal Server Error because it is already created. Any idea what I am doing wrong, and what I am missing in the "IF LOGIC". Apparently, prevData is undefined at the beginning. How should I fix that?

You need to add simple check:
if (typeof prevData !== 'undefined') {
if (JSON.stringify(data) !== JSON.stringify(prevData)) {...}
} else {...}

Related

Is there a reason why a promise will be undefined on Reactjs build but gets resolved on the localhost?

I have a react project setup with Redux and Axios. This is a function I am using to get data from an endpoint in my Redux actions:
export const getCSEfirstStageApplicants = () => async (dispatch) => {
try {
dispatch(LOADING());
const response = await axios.get(
`${baseUrl}/Franchisee/CSEFirstStageApplication`
);
if (response.status === 200) {
const { message, data } = response?.data || {};
return { message, data };
}
} catch (error) {
const { message } = error?.response?.data || {};
return message;
} finally {
dispatch(STOP_LOADING());
}
};
My component looks something like this:
import { useState, useEffect } from "react";
import {getCSEfirstStageApplicants} from "../../../redux/user/actions";
import { useDispatch } from "react-redux";
const MyComponent = () => {
const [cseApplicants, setCseApplicants] = useState([]);
const dispatch = useDispatch();
const getFirstStage = async () => {
const response = await dispatch(getCSEfirstStageApplicants());
if (response && response.data) {
console.log(response);
setCseApplicants(response.data);
return;
}
setCseApplicants([]);
};
useEffect(() => {
getFirstStage();
}, [dispatch]);
}
Apparently, this is working fine on my localhost. But when I build the app and push it to the server, it is giving an error on Chrome and Firefox and is working on Edge (browsers I have tested), indicating that response is undefined.
Chrome shows this error:
Firefox shows this error:
At first I thought it was the way the network call was made as preflight seemed to come after the xhr request. But checking Chrome showed that wasn't the error.
Another indication was an error that showed up as asyncgenerator error. I haven't been able to find a relation with this.
add properties to the empty object
const { message, data } = response?.data || {data:[], message:''};

React SWR - how to know that updating (mutating) is running?

Im mostly using SWR to get data, however I have a situation that I need to update data. The problem is, I need an indicator that this request is ongoing, something like isLoading flag. In the docs there's a suggestion to use
const isLoading = !data && !error;
But of course when updating (mutating) the data still exists so this flag is always false. The same with isValidating flag:
const { isValidating } = useSWR(...);
This flag does NOT change when mutation is ongoing but only when its done and GET request has started.
Question
Is there a way to know if my PUT is loading? Note: I dont want to use any fields in state because it won't be shared just like SWR data is. Maybe Im doing something wrong with my SWR code?
const fetcher = (url, payload) => axios.post(url, payload).then((res) => res);
// ^^^^^ its POST but it only fetches data
const updater = (url, payload) => axios.put(url, payload).then((res) => res);
// ^^^^^ this one UPDATES the data
const useHook = () => {
const { data, error, mutate, isValidating } = useSWR([getURL, payload], fetcher);
const { mutate: update } = useSWRConfig();
const updateData = () => {
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
return {
data,
updateData,
isValidating, // true only when fetching data
isLoading: !data && !error, // true only when fetching data
}
Edit: for any other who reading this and facing the same issue... didnt find any solution for it so switched to react-query. Bye SWR
const { mutate: update } = useSWRConfig();
const updateData = () => {
// this will return promise
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
By using react-toastify npm module to show the user status.
// first wrap your app with: import { ToastContainer } from "react-toastify";
import { toast } from "react-toastify";
const promise=update(getURL, updater(putURL, payload))
await toast.promise(promise, {
pending: "Mutating data",
success: "muttation is successfull",
error: "Mutation failed",
});
const markSourceMiddleware = (useSWRNext) => (key, fetcher, config) => {
const nextFetcher = (...params) =>
fetcher(...params).then((response) => ({
source: "query",
response,
}));
const swr = useSWRNext(key, nextFetcher, config);
return swr;
};
const useHook = () => {
const {
data: { source, response },
mutate,
} = useSWR(key, fetcher, { use: [markSourceMiddleware] });
const update = mutate(
updateRequest().then((res) => ({
source: "update",
response,
})),
{
optimisticData: {
source: "update",
response,
},
}
);
return {
update,
updating: source === "update",
};
};
Hmm based on that:
https://swr.vercel.app/docs/conditional-fetching
It should work that the "is loading" state is when your updater is evaluates to "falsy" value.
REMAINDER! I don't know react swr just looked into docs - to much time at the end of the weekend :D
At least I hope I'll start discussion :D

Vuex Data doesn't get updated on fetch when reloading browser (SSR) Nuxt

I found something about this bug I explained at end;
Component codes
async fetch(){ await this.$store.dispatch('bots/getBots') },
computed: { ...mapState('bots', ['bots']) },
Store codes
export const state = () => {
return {
bots: []
}
}
export const mutations = {
UPDATE_BOTS(state, bots) {
state.bots = bots
}
}
export const actions = {
getBots({commit}) {
this.$axios.$get('url', {headers: {uid: '12345'}})
.then(res => {
commit('UPDATE_BOTS',res.robots)
})
.catch(e => {
console.log(e)
})
}
}
Issue: When moving between pages via nuxt-link data loads perfectly but when I reload the page bots state is empty...
Found Issue:
I use nuxt-auth and I had one plugin for checking status of axios request that if it was 401 unauthorized I logout user if he was loggedIn, So status undefined error was from here but I commented the plugin codes and I got other error from nuxt-auth that causes that problem I had So I related that issue in other question u can see it here:
Nuxt-Auth Bug: Looks for autherization in any get request that has headers config
It is the expected behavior. Vuex state is kept in memory and when you reload the page it gets purged.
Instead of this state
export const state = () => {
return {
bots: []
}
}
try this
export const state = () => ({
bots: []
})

How to avoid warning: Cannot setState on unmounted component? REACT-NATIVE

im builiding a notebloc, so each time I go to the Edit note screen then go back, this warnings appears, heres the code:
when the app runs, the first component that rendes is Notes:
class Notes extends Component {
state = {
onpress: false,
array_notes: [],
selected_notes: [],
update: false,
}
note = "";
note_index = "";
note_number = "";
item_selected = 0;
onpress = false;
componentDidMount() {
const {navigation} = this.props;
this._unsubscribe = navigation.addListener("didFocus", () => this.fetch_notes());
}
componentWillUnmount() {
this._unsubscribe.remove();
}
fetch_notes = async() => { //fetch all the data
const data = await fetch_notes();
if (typeof data != "function") {
this.setState({array_notes: data.array_notes});
}else {
data();
}
}
as you can see, in the ComponentDidmount() I run the function fetch_data to set the data, heres the fetch_data function:
import AsyncStorage from "#react-native-community/async-storage";
export const fetch_notes = async () => {
try {
const data = JSON.parse(await AsyncStorage.getItem("data"));
if (data != null) {
return data;
}else {
return () => alert("with no data");
}
}catch (error) {
alert(error);
}
}
here everything works, because its only fetch the data, now when I go to edit note screen and I edit one of the notes, I need to save it, so when I go back I need to fetch the data again so it will update:
save_changes = async() => {
try {
const data = JSON.parse(await AsyncStorage.getItem("data"));
const index_to_find = this.array_notes.findIndex(obj => obj.note_number === this.note[0].note_number);
const edited_note = this.note.map((note) => {
note.content = this.state.content;
return {...note}
});
this.array_notes.splice(index_to_find, 1, edited_note[0]);
data.array_notes = this.array_notes;
await AsyncStorage.setItem("data", JSON.stringify(data));
}catch(error) {
alert(error);
}
when I get back to Notes screen the function runs and works, the data are updated, but the warning still appears, once I saved the edit note and go back, how can I avoid this?
This warning is thrown when you try to set the state of a component after it has unmounted.
Now, some things to point out here
The navigation flow, from what have you mentioned is like Notes --> Edit Notes --> Notes. Assuming you are using the StackNavigator from react-navigation, the Notes screen will not unmount when you navigate to Edit Notes screen.
The only screen unmounting is Edit Notes when you go back. So you should check the code to verify that you don't have any asynchoronous setState calls in that screen.
P.S : The syntax to remove the focus event listener is to just call the returned function as mentioned here.
So in your Notes screen inside componentWillUnmount it should be
componentWillUnmount() {
this._unsubscribe();
}
you need to use componentWillUnmount() function inside the function which you are unmounting.
You can use conditional rendering for mounting or unmounting any componets.

How can I get my firebase listener to load data to my redux state in a React Native app so I can read the data within my ComponentDidMount function?

I am trying to load a notification token (notificationToken) that I've stored within Firebase to a React Native component.
Once the notificationToken is loaded to my redux state, I want to check for my device permissions to see if the notificationToken has expired within the function getExistingPermission() that I run in the componentDidMount().
If the token has expired, then I'll replace the token within Firebase with the new token. If it's the same, then nothing happens (which is intended functionality).
When I'm running my function getExistingPermission() to check if the token is up-to-date the Firebase listener that pulls the notificationToken does not load in time, and so it's always doing a write to the Firebase database with a 'new' token.
I'm pretty sure using async/await would solve for this, but have not been able to get it to work. Any idea how I can ensure that the notificationToken loads from firebase to my redux state first before I run any functions within my componentDidMount() function? Code below - thank you!
src/screens/Dashboard.js
Should I use a .then() or async/await operator to ensure the notificationToken loads prior to running it through the getExistingPermission() function?
import {
getExistingPermission
} from '../components/Notifications/NotificationFunctions';
componentDidMount = async () => {
// Listener that loads the user, reminders, contacts, and notification data
this.unsubscribeCurrentUserListener = currentUserListener((snapshot) => {
try {
this.props.watchUserData();
} catch (e) {
this.setState({ error: e, });
}
});
if (
!getExistingPermission(
this.props.notificationToken, //this doesn't load in time
this.props.user.uid)
) {
this.setState({ showNotificationsModal: true });
}
};
src/components/Notifications/NotificationFunctions.js
The problem is probably not here
export const getExistingPermission = async (
notificationToken,
uid,
) => {
const { status: existingStatus } = await Permissions.askAsync(
Permissions.NOTIFICATIONS
);
if (existingStatus !== 'granted') {
console.log('status not granted');
return false;
} else {
let token = await Notifications.getExpoPushTokenAsync();
/* compare to the firebase token; if it's the same, do nothing,
if it's different, replace */
if (token === notificationToken) {
console.log('existing token loaded');
return true;
} else {
console.log('token: ' + token);
console.log('notificationToken: ' + notificationToken);
console.log('token is not loading, re-writing token to firebase');
writeNotificationToken(uid, token);
return false;
}
}
};
src/actions/actions.js
// Permissions stuff
watchPermissions = (uid) => (
(dispatch) => {
getPermissions(uid + '/notificationToken', (snapshot) => {
try {
dispatch(loadNotificationToken(Object.values([snapshot.val()])[0]));
}
catch (error) {
dispatch(loadNotificationToken(''));
// I could call a modal here so this can be raised at any point of the flow
}
});
}
);
// User Stuff
export const watchUserData = () => (
(dispatch) => {
currentUserListener((user) => {
if (user !== null) {
console.log('from action creator: ' + user.displayName);
dispatch(loadUser(user));
dispatch(watchReminderData(user.uid)); //listener to pull reminder data
dispatch(watchContactData(user.uid)); //listener to pull contact data
dispatch(watchPermissions(user.uid)); //listener to pull notificationToken
} else {
console.log('from action creator: ' + user);
dispatch(removeUser(user));
dispatch(logOutUser(false));
dispatch(NavigationActions.navigate({ routeName: 'Login' }));
}
});
}
);
export const loadNotificationToken = (notificationToken) => (
{
type: 'LOAD_NOTIFICATION_TOKEN',
notificationToken,
}
);
Tony gave me the answer. Needed to move the permissions check to componentDidUpdate(). For those having a similar issue, the component looks like this:
src/screens/Dashboard.js
componentDidUpdate = (prevProps) => {
if (!prevProps.notificationToken && this.props.notificationToken) {
if (!getExistingPermission(
this.props.notificationToken,
this.props.user.uid
)) {
this.setState({ showNotificationsModal: true });
}
}
};
Take a look at redux subscribers for this: https://redux.js.org/api-reference/store#subscribe . I implement a subscriber to manage a small state machine like STATE1_DO_THIS, STATE2_THEN_DO_THAT and store that state in redux and use it to render your component. Only the subscriber should change those states. That gives you a nice way to handle tricky flows where you want to wait on action1 finishing before doing action2. Does this help?

Categories