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
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)
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))
);
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.
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 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,
]);