I am trying to optimize my react code by fixing any memory leaks. For this i am using createAsyncThunk canceling-while-running to cancel requests in case my component unmounts. Below is my useEffect from my component
useEffect(() => {
const data = { page: curPage }
const promise = dispatch(blockedUsers(data))
return () => promise.abort()
}, [curPage])
Below is my async thunk reducer
export const blockedUsers = createAsyncThunk(
"admin/blocked",
async (data, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await axios.get(API_URL + "/blocked");
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
below is my buider for my async thunk reducer
.addCase(blockedUsers.pending, (state) => {
state.isLoading = true;
})
.addCase(blockedUsers.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.blocked.users = action.payload.result;
state.blocked.users.length === 0
? (state.blocked.total = 0)
: (state.blocked.total = action.payload.count.count);
})
.addCase(blockedUsers.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
console.log(action);
toast.error(
"Could not get blocked users. Reason: " + state.message,
unsuccessful
);
When my coponent loads two requests are made to the server due to react strictmode. The first request gets rejected everytime no matter what and the second one gets fullfiled.
my dev tools screenshot.
Because of this i get a toast error for rejected request but at the same time i get the required data rendered in my application.
And i get undefined error which is given below:
type: 'admin/blocked/rejected', payload: undefined, meta: {…}, error: {…}}
error
:
{name: 'AbortError', message: 'Aborted'}
meta
:
{arg: {…}, requestId: '9H3Z2GZ1_A1hBxH4Di', rejectedWithValue: false, requestStatus: 'rejected', aborted: true, …}
payload
:
undefined
type
:
"admin/blocked/rejected"
[[Prototype]]
:
Object
I am unable to fix this. And i am not sure how will this behave once deployed. Please help. Thanks in advance.
Related
I am writing a redux-toolkit create function and it is the first time I am using typescript. I fixed the error from yesterday and get directly a new one.
Currently Vs-code underlines accessToken and tells me:
The property accessToken does not exist for the User[] type.ts(2339)
I googled this and have found, that I should add the property to the interface but this brought no solution. And I ask myself why typescript says, this would be incorrect, when it works fine without typescript.
Here the current state:
type AsyncThunkConfig = {
state: RootState
}
export const createCardImage = createAsyncThunk<object, object, AsyncThunkConfig>('cardImages/create', async (cardImagesData, thunkAPI)=>{
try{
const token = thunkAPI.getState().auth.user!.accessToken;
return await cardImagesService.createCardImages(cardImagesData, token);
}catch (error:any) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
return thunkAPI.rejectWithValue(message as string)
}
})
My auth initialstate and slice:
const user = JSON.parse(localStorage.getItem('user') || '');
interface User {
vorname:string;
nachname:string;
username:string;
email:string;
street:string;
number:string;
plz:number;
city:string;
password:string;
isAdmin:boolean;
createdAt: Date;
accessToken?:string;
}
interface InitialState{
user:User[] | null;
isLoading:boolean;
isSuccess:boolean;
isError:boolean;
message: string;
auth?:boolean;
}
const initialState: InitialState = {
user: user ? user : null,
isLoading:false,
isSuccess:false,
isError:false,
message:"",
};
slice without extraReducers:
const authSlice = createSlice({
name: 'auth',
initialState,
reducers:{
reset: (state)=>{
state.isLoading = false;
state.isSuccess = false;
state.isError = false;
state.message = "";
},
},
AsyncThunkConfig is the third type parameter. You also need to provide types for Returned and ThunkArg.
Try something like:
const createCardImage = createAsyncThunk<
// Returned
void, // whatever your return type is
// ThunkArg
CardImagesData, // the type of cardImagesData
AsyncThunkConfig
>
const { response, setResponse } = useResponseState();
const handleNext = () => {
if (
response.currentResponse !== undefined &&
response.responses!== undefined
) {
if (response.currentResponse< response.responses.length) {
setResponse({
currentResponse: response.currentResponse + 1,
responses: response.responses,
});
}
}
};
const responseID= response.responses![response.currentResponse!].id ?? 0;
const { data, error } = useFetch<ExampleType>(
`${process.env.REACT_APP_API_ENDPOINT}example/${exampleID}`
);
return error || !data ? (
<>error</>
) : (
<>success</>
Can anyone help me understand why when handleNext is called data is undefined. In the success part of the return there is a button with an onclick but I have tried to show only what you need to see. Can anyone see anything wrong here?
ResponseState is a context.
Endpoint returns something like:
{"id":1,"exampleProp2: "test"}
Hook:
import { useEffect, useReducer, useRef, useState } from 'react';
import State from './State';
type Cache<T> = { [url: string]: T };
// discriminated union type
type Action<T> =
| { type: 'loading' }
| { type: 'fetched'; payload: T }
| { type: 'error'; payload: Error };
function useFetch<T = unknown>(url?: string, options?: RequestInit): State<T> {
const cache = useRef<Cache<T>>({});
// Used to prevent state update if the component is unmounted
const cancelRequest = useRef<boolean>(false);
const initialState: State<T> = {
error: undefined,
data: undefined,
};
// Keep state logic separated
const fetchReducer = (state: State<T>, action: Action<T>): State<T> => {
switch (action.type) {
case 'loading':
return { ...initialState };
case 'fetched':
return { ...initialState, data: action.payload };
case 'error':
return { ...initialState, error: action.payload };
default:
return state;
}
};
const [state, dispatch] = useReducer(fetchReducer, initialState);
useEffect(() => {
// Do nothing if the url is not given
if (!url) return;
const fetchData = async () => {
dispatch({ type: 'loading' });
// If a cache exists for this url, return it
if (cache.current[url]) {
dispatch({ type: 'fetched', payload: cache.current[url] });
return;
}
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(response.statusText);
}
const data = (await response.json()) as T;
cache.current[url] = data;
if (cancelRequest.current) return;
dispatch({ type: 'fetched', payload: data });
} catch (error) {
if (cancelRequest.current) return;
dispatch({ type: 'error', payload: error as Error });
}
};
void fetchData();
// Use the cleanup function for avoiding a possibly...
// ...state update after the component was unmounted
return () => {
cancelRequest.current = true;
};
}, [url]);
return state;
}
export default useFetch;
This is the exact hook used. Is there anything still not working here?
I depends on what's the API contract of useFetch are you calling (how the hook is working and what are the expected return values). But generally fetching is an asynchronous operation which is done on the background. The data can be really undefined or null at the first phase because the request has not been sent or response has not been received.
Let's say the hook returns the following stuff - { data, error, loading, requested }. The return value could be following:
fetch not sent: { loading: false, requested: false } (that's not probable in your case)
fetch sent, response not received: { loading: true, requested: true }
success response received: { loading: false, requested: true, data: {} }
failure response received: { loading: false, requested: true, error: {} }
As you can can see, there is just one state in which the data are expected to be available. This just a theoretical elaboration because you have not specified your useFetch hook enough.
For example, you could be using the hook from use-http. If you check the documentation then you should notice that they suggest the initialize data by own value to avoid undefined. In your case, it would be something like this:
const url = `${process.env.REACT_APP_API_ENDPOINT}example/${exampleID}`
const { data = {}, error, loading } = useFetch < ExampleType > (url);
return loading ? (
<>Loading...</>
) : error ? (
<>Failure: ${JSON.stringify(error)}</>
) : (
<>Success: ${JSON.stringify(data))</>
)
You should definitely check the documentation of useFetch<T> which you are using it should be written here.
Update for custom hook
In case of the hook from specified article, you should also consult the documentation for axios response. It clearly states that data are stored in so named attribute. It means that if you copied hook code as it is written article then it can't work. No part of the handler for Promise.then is using that value:
//checking for multiple responses for more flexibility
//with the url we send in.
res.data.content && setData(res.data.content);
res.content && setData(res.content);
It has to be fixed to access the response data correctly, for example:
// Give up the flexibility
setData(rest.?data)
// Keep some flexibility
// ... but you have to define data acquisition algorithm
// const data = res.content || rest.data
// setData(data)
What types of things would cause Immer only supports setting array indices and the 'length' property' from the code below? This FoodLogState type is a class. I've done something very similar with no issue. I notice I am not updating even the array from state yet. Only the status that is a string.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { FoodLogState, getFoodState } from "../AccountAPI";
export interface FoodLogs {
foodLogs: Array<FoodLogState>;
status: "idle" | "loading" | "failed";
}
const initialState: FoodLogs = {
foodLogs: null,
status: "idle",
};
export const getFoodLogsAsync = createAsyncThunk(
"foodsLogged/getFoodsLogged",
async (uid: string, { rejectWithValue }) => {
try {
const response = await getFoodState(uid).catch((error) => {
return rejectWithValue(error.message);
});
return response;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const foodsLogSlice = createSlice({
name: "foodsLogged",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getFoodLogsAsync.fulfilled, (state, action) => {
//state.foodLogs = action.payload;
state.status = "idle";
})
.addCase(getFoodLogsAsync.rejected, (state, action) => {
state.status = "failed";
})
.addCase(getFoodLogsAsync.pending, (state) => {
state.status = "loading";
});
},
});
export const selectFoods = (state) => state.foodLog;
export default foodsLogSlice.reducer;
This will get the code running but if anyone knows the underlying issue, I would accept that answer because it is 'more correct.' I believe this answer just bypasses Immer in Redux Toolkit, not exactly ideal.
const foodsLogSlice = createSlice({
name: "foodsLogged",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getFoodLogsAsync.fulfilled, (state, action) => {
//similar error to the link below,
//status: "idle" causes immer error.
//http://5.9.10.113/67418074/redux-thunk-modifying-state-unhandled-promise-rejection-error-immer-immer
return (state = {
...state,
status: "idle",
foodLogs: action.payload,
});
})
.addCase(getFoodLogsAsync.rejected, (state, action) => {
return (state = {
...state,
status: "failed",
});
})
.addCase(getFoodLogsAsync.pending, (state) => {
return (state = {
...state,
status: "loading",
});
});
},
});
I am using redux-thunk and had this same issue. Tried several things but couldn't get it to work.
I had the idea of running it in a private window. I logged in and opened the page where this error occurred (on my fetchUserTypes action) and it worked fine.
I realized it is caused by some cached data and that is when I had the idea to clear my local storage (I am using redux-thunk which stores the redux state in local storage).
This will not result in an error if you only use JavaScript - seems like it only happens when you use TypeScript and you modify the structure of your reducer.
I am leaving this here because this has happened to me twice and I opened this link both times and in both cases I was able to solve the problem only after clearing local storage. Next time I decide to modify the structure of my reducer and run into this error I will be one step ahead :).
What I am trying to do
I have a lobby that users can join. To persist the joined lobby on the client on a page refresh I decided to put the lobby that has been joined into the browser's session storage. Before it was just in a useState which doesn't persist through a page refresh.
Setting Session Storage is classified as a side effect as far as I know and should be handled in useEffect. The problem is when I set the lobby the useEffect that has the lobby as a dependency doesn't run.
Setting breakpoints shows that it doesn't run at all, but I can see that the joinedLobby has changed from undefined to an object (example : {success: "Successfully joined ...", payload : { id:"", ...}}).
The session store stays empty.
Code Sandbox
Sandbox
CSS is broken since I was using Emotion
Update
Fetching Data from the back end breaks the app. Making the data static made the app function like it should.
I have 0 ideas on why / how. The culprit seems to be play_index.jsx at line 165 const jsonResponse.
Setting the state that should update the useEffect
const { setJoinedLobby } = useContext(JoinedLobbyProviderContext);
const history = useHistory();
useEffect(() => {
if (joinState.result === undefined) return;
setJoinedLobby(joinState.result);
history.push('/lobby');
}, [joinState.result, history, setJoinedLobby]);
Provider inside router
<JoinedLobbyProviderContext.Provider
value={{ getJoinedLobby, setJoinedLobby }}>
<Route path='/play'>
<Play />
</Route>
<Route path='/lobby'>
<Lobby />
</Route>
</JoinedLobbyProviderContext.Provider>
The functions the provider takes
const [joinedLobby, setJoinedLobby] = useState(undefined);
useEffect(() => {
if (joinedLobby === undefined) return;
sessionStorage.setItem('joinedLobby', JSON.stringify(joinedLobby));
}, [joinedLobby]);
const getJoinedLobby = () => {
return JSON.parse(sessionStorage.getItem('joinedLobby'));
};
Edit : How joinState.result changes
const joinInit = {
errors: undefined,
loading: false,
result: undefined,
id: undefined,
};
const joinReducer = (state, action) => {
switch (action.type) {
case 'joinLobby': {
return { ...state, id: action.payload };
}
case 'loadingTrue':
return { ...state, loading: true };
case 'setResult':
return { ...state, loading: false, result: action.payload };
case 'setErrors':
return {
...state,
loading: false,
errors: action.payload,
};
case 'reset':
return joinInit;
default : {throw new Error('Didn't find action passed to reducer')}
}
};
const [joinState, joinStateDispatch] = useReducer(joinReducer, joinInit);
const passRef = useRef();
useEffect(() => {
const joinLobby = async () => {
joinStateDispatch({ type: 'loadingTrue' });
try {
const jsonResponse = await (
await fetch(`${BACKEND_URL}/play/joinLobby/${joinState.id}`, {
method: 'PATCH',
credentials: 'include',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({
password: passRef.current.value,
}),
})
).json();
joinStateDispatch({ type: 'setResult', payload: jsonResponse });
} catch (e) {
joinStateDispatch({ type: 'setErrors', payload: e });
}
};
if (joinState.id !== undefined) {
joinLobby();
}
}, [joinState.id, joinStateDispatch]);
I am creating an app using Expo SDK 26 - I am working on a piece where I need to upload a photo into firebase database. For android and ios - Expo provides Image Picker to allow native access to gallery/capturing an image.
When I receive an image - I am trying to capture the image uri, fetch the image and upload it to firebase storage with a reference to the images firebase storage url saved in my firebase database.
When I select the image from a device, my action creator is triggered, but when I dispatch an action - the app stops at the dispatch. Am I missing something obvious, or is the pattern not correct?
If so - what would be a good approach to this.
Select Image From Device Code:
async _pickImage(){
let result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
});
if (!result.cancelled) {
this.props.uploadPhoto(result.uri)
}
};
Upload Image Code:
export const uploadPhoto = (uri) => {
return async (dispatch) => {
dispatch({
type: ActionTypes.UPLOAD_PROFILE_IMAGE_REQUEST
})
const response = await fetch(uri);
const blob = await response.blob();
const currentUser = firebase.auth().currentUser
const userId = currentUser.uid;
const ref = firebase.storage().ref().child(userId);
const databaseRef = firebase.database().ref().child("/users/" + userId + "/user-details")
await ref.put(blob)
.then((snapshot) => {
databaseRef.update({
"photoURL": snapshot.downloadURL
}).then(() => {
console.log(snapshot.downloadURL)
dispatch({
type: ActionTypes.UPLOAD_PROFILE_IMAGE_SUCCESS,
payload: snapshot.downloadURL
})
})
})
.catch((error) => {
dispatch({
type: ActionTypes.UPLOAD_PROFILE_IMAGE_FAILURE,
error: error
})
})
}
}
Reducer: UPLOAD_PROFILE_IMAGE_REQUEST
import * as ActionTypes from '../ActionTypes'
const INITIAL_STATE = {
userInfo: {},
error: "",
isLoading: false,
image: undefined
};
export default (state = INITIAL_STATE , action) => {
switch (action.type) {
case ActionTypes.GET_USER_DETAILS_REQUEST:
return {...state, isLoading: true }
case ActionTypes.GET_USER_DETAILS_SUCCESS:
return {...state, isLoading: false, userInfo: action.payload}
case ActionTypes.GET_USER_DETAILS_FAILURE:
return {...state, isLoading: false, error: action.payload }
case ActionTypes.UPLOAD_PROFILE_IMAGE_REQUEST:
return {...state, isLoading: true}
case ActionTypes.UPLOAD_PROFILE_IMAGE_SUCCESS:
return {...state, image: action.payload, isLoading: false}
case ActionTypes.UPLOAD_PROFILE_IMAGE_FAILURE:
return {...state, error: action.payload, isLoading: false}
case ActionTypes.CLEAR_USER_DETAILS:
return {INITIAL_STATE, isLoading: false}
default:
return state
}
}
I have tried console.log directly after the first dispatch but nothing prints in the log after the UPLOAD_PROFILE_IMAGE_REQUEST action dispatch.
any help is much appreciated.