Async thunk breaks react app in useEffect - reactjs

I’ve been working on a react crud application to teach myself redux toolkit, and I can’t seem to get a particular async function to work. I’m trying to fetch recipes from a certain user from firebase - this function worked when I was just playing around and didn’t have any users or authentication (i.e., the database was just recipes), but now that I have authentication and users, I cannot fetch or display the recipes, and the recipes are not added to the recipes state array. Thanks to redux logger, I can see that the recipes are accessed from firebase, but they're not displayed or added to the recipes state. Once I refresh the page, the application breaks. I can add and delete recipes to a user in firebase, though.
Here’s what the async thunk looks like:
'user/getRecipes',
async(uid, thunkAPI) => {
const snapshot = await getDocs(collection(db, `users/${uid}/recipes`))
const array = []
snapshot.forEach((doc) => {
array.push(doc.data())
})
return array
}
)
Here’s where I call it inside a useEffect. I’ve tried using the uid as an argument and getting the uid with getState() inside the thunk - it doesn’t seem to make a difference but maybe I’m doing that wrong, as well?
const RecipeApp = () => {
const dispatch = useDispatch()
const recipes = useSelector((state) => state.recipes)
const auth = getAuth()
const navigate = useNavigate()
const [user, loading, error] = useAuthState(auth)
const uid = useSelector(state => state.users.uid.user)
useEffect(() => {
if (loading) return;
if (!user) return navigate("/");
}, [user, loading])
useEffect(() => {
dispatch(getRecipes(uid))
console.log(uid)
}, [dispatch])
const addRecipe = (name, ingredients, instructions, notes) => {
const date = new Date()
const newRecipe = {
name: name,
ingredients: ingredients,
instructions: instructions,
notes: notes,
recipeId: uuidv4(),
date: date.toLocaleDateString(),
createdAt: Date.now(),
}
dispatch(addRecipeToFirestoreAndRedux(newRecipe))
}
return (
<div className="recipeapp">
<h1 className='recipeapp__heading'>Recipes</h1><button onClick={logout}>Log out</button>
<div className="recipeapp__container">
<ErrorBoundary
FallbackComponent={ErrorFallBack}>
<RecipeList
recipes={recipes}
/>
</ErrorBoundary>
<AddRecipe
addRecipe={addRecipe}
/>
</div>
</div>
);
}
export default RecipeApp;
Here’s what the relevant code (and a little more) of the recipes reducer looks like:
export const recipeSlice = createSlice({
name: 'recipesSlice',
initialState: {
recipes: []
},
reducers: {
ADD_RECIPE: (state, action) => {
state.recipes.push(action.payload)
},
DELETE_RECIPE: (state, action) => {
state.recipes = state.recipes.filter((recipe) => recipe.recipeId !== action.payload.recipeId)
},
extraReducers: builder => {
builder.addCase(getRecipes.fulfilled, (state, action) => {
state.recipes = action.payload
})
}
})
export const { ADD_RECIPE, DELETE_RECIPE } = recipeSlice.actions;
export default recipeSlice.reducer
And here's what the store.js file looks like:
import { configureStore, combineReducers } from "#reduxjs/toolkit";
import recipeReducer from './features/recipeslice'
import userReducer from './features/authentication'
import thunk from "redux-thunk";
import logger from "redux-logger";
const rootReducer = combineReducers({
recipes: recipeReducer,
users: userReducer
})
export const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger]
})
Apologies if this is overkill. Hopefully this makes sense, and thank you in advance for any and all help!

Related

why is this redux toolkit users variable always undefined?

I'm trying to add a user to state.users in a redux toolkit slice. Here's userManagementSlice.js:
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
users: [],
}
export const userManagementSlice = createSlice({
name: 'userManagement',
initialState,
reducers: {
addUser: (state, action) => {
const user = action.payload;
state.users.push(user);
},
},
})
// Action creators are generated for each case reducer function
export const { addUser } = userManagementSlice.actions
export default userManagementSlice.reducer
Here's key code from a component that listens to the users in global state and attempts to add a user to that array via dispatch:
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const users = useSelector((state) => state.users)
const dispatch = useDispatch()
const addUser2 = () => {
const user = {
firstName,
lastName
}
dispatch(addUser(user));
}
However, the USERS UPDATED value logged to the console from the component is always undefined:
useEffect(() => {
console.log('USERS UPDATED: ' + users);
// reset inputs
setFirstName('');
setLastName('');
}, [users]);
Here's the configureStore setup:
import { configureStore } from '#reduxjs/toolkit'
import userManagementReducer from '../features/userManagement/userManagementSlice'
export const store = configureStore({
reducer: {
userManagement: userManagementReducer,
},
})
Any idea what I'm doing wrong here?
You are mounting the slice at userManagement, so your selector would have to be
const users = useSelector((state) => state.userManagement.users)

Why do I have an error: 'Actions must be plain objects. Use custom middleware for async actions.' when using redux?

So I have a movie app, and I have a page for a single movie. I have a section on that page where I display all of the videos from an API related to a certain movie.
So my Videos component looks like this:
const Videos = ({videos} :{videos:IVideos | null}) => {
return (
<div>{videos?.results.map((video, i) =>
<div key={i}>{video.name}</div>
)}</div>
)
}
It's just a basic component which gets props from a higher component. But the main thing is redux slice, which looks like this:
Initial state:
const initialState: IMovieVideosState = {
movieVideos: null,
fetchStatus: null,
}
export interface IMovieVideosState {
movieVideos: IVideos | null;
fetchStatus: FetchStatus | null;
}
And finally slice:
const videosSlice = createSlice({
name:'videos',
initialState,
reducers:{},
extraReducers(builder) {
builder
.addCase(fetchVideos.pending, (state, action) => {
state.fetchStatus = FetchStatus.PENDING
})
.addCase(fetchVideos.fulfilled, (state, action) => {
state.fetchStatus = FetchStatus.SUCCESS
state.movieVideos = action.payload
})
.addCase(fetchVideos.rejected, (state, action) => {
state.fetchStatus = FetchStatus.FAILURE
//state.error = action.error.message
})
}
})
As you see, these are basic reducers, where if promise is successful I assign payload to an existing array.
And also thunk function:
export const fetchVideos = createAsyncThunk('videos/fetchVideos', async (id: number) => {
const response = await axios.get<IVideos>(`${API_BASE}movie/${id}/videos?api_key=${TMDB_API_KEY}`);
console.log(response.data);
return response.data;
})
But in the browser I have the next error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
And also another one:
A non-serializable value was detected in an action, in the path: `<root>`. Value:
Promise { <state>: "pending" }
Take a look at the logic that dispatched this action:
Promise { <state>: "pending" }
I have no idea why I could have these errors, because my reducer is the same as another one in my project, but this one doesn't work for some reason.
UseEffect for dispatching all reducers:
useEffect(() =>{
dispatch(fetchDetail(Number(id)));
dispatch(fetchCredits(Number(id)));
dispatch(fetchPhotos(Number(id)));
dispatch(fetchRecommended(Number(id)));
dispatch(fetchSimilar(Number(id)));
dispatch(fetchVideos(Number(id))); //dispatching fetchVideos()
}, [dispatch, id])
So in my case, all of the other functions work fine besides fetchVideos().
Another example of a thunk for movie details:
export const fetchDetail = createAsyncThunk('detail/fetchDetail', async (id: number) => {
const response = await axios.get<IMovie>(`${API_BASE}movie/${id}?api_key=${TMDB_API_KEY}`);
console.log(response.data);
return response.data;
})
My store file:
import thunk from "redux-thunk";
export const store = configureStore({
reducer: {
popular,
top_rated,
playing,
upcoming,
detail,
credits,
videos,
photos,
recommended,
similar
},
middleware: [thunk]
})
export type RootState = ReturnType<typeof store.getState>;
instead of using create Async Thunk method add think malware where you create store of videos then you can pass Async actions into it without nothing.
import { applyMiddleware, combineReducers, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
// import your videos reducer here from file
export interface State {
videos: IVideos;
}
const rootReducer = combineReducers<State>({
videos: VideosReducer,
});
export const rootStore = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);

Redux variable considered null yet appears in console

I am trying to render a homepage depending on user being connected or not.
App.js
const user = useSelector(selectUser);
{!user ? <Login></Login> : (
<div className="app__body">
<Sidebar></Sidebar>
<Feed></Feed>
<Widget></Widget>
</div>
)}
userSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
user: null
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.user = null;
},
},
});
export const { login, logout } = userSlice.actions;
export const selectUser = (state) => state.user;
export default userSlice.reducer;
Login.js
function Login() {
const dispatch = useDispatch()
const loginToApp = (e) => {
e.preventDefault();
auth.signInWithEmailAndPassword(email, password).then(userAuth => {
dispatch(
login({
email: userAuth.user.email,
uid: userAuth.user.uid,
displayName: userAuth.user.displayName,
photoUrl: userAuth.user.photoURL,
})
)
}).catch(error => alert(error))
}
Though <Login></Login> is always being rendered, even if user is not null, I don't understand as in the redux console, user is not null.
Your screenshot only includes the render part and the problem might not be render, I think. It must be related to the logic. For example, you might defined user as a state and use it.
To get help, I think you should check the part and show them, too.
Well, not really sure about this. I think the problem is here.
export const selectUser = (state) => state.user;
According to official document you need to use createSelector.
So it should try like this.
export const selectUser = createSelector(
(state) => state.user
)
...
const user = useSelector(selectUser);
Ok, my store name was wrong because I've npx created it with redux template and haven't changed store name from counter to user.
import { configureStore } from '#reduxjs/toolkit';
import userReducer from '../features/userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});
It all works now.

Why does my useSelector hook always return undefined?

I've been at this for a while and I can't figure it out. When I dispatch my populateDatasets action I can see that my store gets updated just fine in dev tools, but when I try to to access that state in a React component with useSelector it always returns undefined.
Here is how I've configured my store
import { configureStore } from '#reduxjs/toolkit'
import datasetReducer from './datasets'
export default configureStore({
reducer: {
datasets: datasetReducer
},
devTools: true
})
Here is the slice that I've created for this piece of state
import { createSlice } from '#reduxjs/toolkit'
export const datasetSlice = createSlice({
name: 'datasets',
initialState: [],
reducers: {
populateDataset: (state, action) => {
state.push(action.payload)
}
}
})
export const { populateDataset } = datasetSlice.actions
export default datasetSlice.reducer
And here is where I dispatch the action in my React component
const App = () => {
const { datasets } = useSelector((state) => state.datasets)
console.log('datasets: ' + datasets)
const dispatch = useDispatch()
useEffect(() => {
csv(FantraxHQData).then(data => {
data.map((player) => {
player.isDrafted = false
return player
})
dispatch(populateDataset(data))
})
csv(FantasyProsData).then(data => {
data.map((player) => {
player.isDrafted = false
return player
})
dispatch(populateDataset(data))
})
}, [])
return (
<div className={styles.outter}>
<MyTeam />
<div className={styles.container}>
<DataButtons />
<DraftBoard />
</div>
</div>
)
}
Again, my store updates just fine when I dispatch the action, but datasets is always undefined. Any help would be much appreciated.
Update Solution: The solution was to change { datasets } to datasets
Your datasets is an array in your redux store, but you're reading it as a object when
you're using useSelector(). Change the useSelector line to const datasets = useSelector((state) => state.datasets). Don't use the flower bracket on datasets.

My async actions endlessly called in my react redux toolkit slice

My Redux-async function endlessly called and hangs my system when i subscribe to the store using useSelector.
My Product.Slice
import { createSlice } from '#reduxjs/toolkit'
import { apiCallBegan } from './../../app/store/api/api.action';
const productSlice = createSlice({
name: 'product',
initialState: {
products: [],
},
reducers: {
productsReceived: (state, action) => {
state.products=action.payload
}
}
})
export const { addProducts,productsReceived } = productSlice.actions
export const loadProducts = () => apiCallBegan({
url: "/product/",
method: "get",
onSuccess: productsReceived.type
})
export const getProducts = (state) => state.product.products
export default productSlice.reducer
ProductList.js Use Reducer implementation:
const dispatch = useDispatch()
dispatch(loadProducts())
const products=useSelector(state=>state.product.products)
console.log(products)
I assume this is the problem:
const dispatch = useDispatch();
dispatch(loadProducts());
const products = useSelector(
(state) => state.product.products
);
console.log(products);
That is in a component that re renders when products change while also changing the products. Maybe you can only get the products on mount:
const dispatch = useDispatch();
React.useEffect(() => dispatch(loadProducts()), [
dispatch,
]);

Resources