TypeError: Cannot read properties of undefined (reading 'todos') - reactjs

When I try to run this code it gives me this error:
TypeError: Cannot read properties of undefined (reading 'todos')
how to fix it
I wanted to make a todo list that will work after the user registers, but todo does not work after registration
My code:
HomePage.js
const HomePage = () => {
const dispatch = useDispatch();
const { isAuth, email } = useAuth();
return isAuth ? (
<div>
<TodoForm />
<button onClick={() => dispatch(removeUser())}>
Log out from {email}
</button>
</div>
) : (
<Redirect to="/login" />
);
};
TodoForm.js
const TodoForm = () => {
const todos = useSelector((state) => state.todo.todos)
// const todos = useSelector((state) => state.todo.todos);
const dispatch = useDispatch();
const [todoValue, setTodoValue] = useState("");
const addTodoHandler = (e) => {
e.preventDefault();
const todo = {
id: v4(),
text: todoValue,
completed: false,
};
dispatch(addTodo(todo));
setTodoValue("");
};
const handleChange = (e) => {
setTodoValue(e.target.value);
};
console.log(todos);
return (
<>
<form onSubmit={addTodoHandler}>
<input
type="text"
value={todoValue}
onChange={handleChange}
placeholder="Add task"
/>
<button type="submit">Submit</button>
</form>
{todos.map((todo) => (
<TodoList key={todo.id} todo={todo} />
))}
</>
);
};
todoSlice.js
const initialState = {
todos: [],
};
export const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload);
},
removeTodo: (state, action) => {
state.todos = state.todos.filter((todo) => todo.id !== action.payload);
},
completedTodo: (state, action) => {
const toggleTodo = state.todos.find((todo) => todo.id === action.payload);
toggleTodo.completed = !toggleTodo.completed;
},
},
});
store
reducer: {
todo: todoSlice,
user: userReducer,
},
});
help me fix this, I will be very grateful

slicename is todos not todo
const todos = useSelector((state) => state.todos.todos)

Related

How to show the old contents after clearing the search

After showing the content for searched item, while removing the letters from search bar not showing the contents correctly. How to show the contents based on the word which is there in search bar. I have started to learn redux. So need some suggestions
import logo from "./logo.svg";
import "./App.css";
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
function App() {
const [name, setName] = useState("");
const [searchTerm, setSearchterm] = useState("");
const dispatch = useDispatch();
const data = useSelector((state) => state.add);
console.log(data, "dfata");
const handleChange = (e) => {
setName(e.target.value);
};
console.log(name);
if (data.length == 0) {
return <p>No data</p>;
}
const Submithandler = () => {
dispatch({ type: "ADD_ITEM", name });
setName("");
};
const handleSearch = (e) => {
setSearchterm(e.target.value);
};
const submitSerach = () => {
dispatch({ type: "SEARCH_ITEM", searchTerm });
};
const reset = () => {
dispatch({ type: "RESET", searchTerm });
};
return (
<div className="App">
{data.loading && <p>loading</p>}
<input value={searchTerm} onChange={(e) => handleSearch(e)} />
<button onClick={() => submitSerach()}>search</button>
<button onClick={() => reset()}>reset</button>
<input value={name} onChange={handleChange} />
<button onClick={Submithandler}>Add</button>
{data.item.length === 0 && <p>no item</p>}
{data.item.map((dta, i) => {
return (
<div>
{dta}
<button
onClick={() => dispatch({ type: "REMOVE_ITEM", name: dta })}
>
Remove
</button>
</div>
);
})}
</div>
);
}
export default App;
const INITIAL_STATE = {
item: [],
loading: false,
};
function addReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case "ADD_ITEM":
console.log(action, "ahghsgda");
return { item: [...state.item, action.name] };
case "REMOVE_ITEM":
console.log(action, "REMOPVE");
return {
item: state.item.filter((inditem) => inditem !== action.name),
};
case "SEARCH_ITEM":
console.log(action, "ahghsgda");
const data = [...state.item];
return {
loading: true,
item: [data.filter((product) => product.includes(action.searchTerm))],
};
case "RESET":
return {
item: [...state.item],
};
default:
return state;
}
}
export default addReducer;
After showing the content for searched item, while removing the letters from search bar not showing the contents correctly

Infinite Loop using React, Redux and Firebase

I have a collection of profile documents in firebase and I want to render them in the profiles page, however after I have updated the userProfiles state and use useDispatch to store the state in the slice, I get an infinite loop when rendering the profile page.
I have tried putting the dispatch() into a useEffect, not in a useEffect and inside the querySnapshot promise but I'm still getting an infinite loop wherever I put it.
Any feedback is appreciated, thank you.
\\ profiles.js
export const Profiles = () => {
const [userProfiles, setUserProfiles] = useState([]);
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector(selectUser);
db.collection("customers")
.doc(user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
setUserProfiles(documents);
});
useEffect(() => {
dispatch(profiles(userProfiles));
}, []);
console.log({ userProfiles });
return (
<div className="profile_container">
<h1 className="profile_title">Who's Watching?</h1>
<div className="profile_row">
{userProfiles.map((profile) => {
return (
<div className="profile_individualProfile">
<img
src="https://occ-0-300-1167.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABY5cwIbM7shRfcXmfQg98cqMqiZZ8sReZnj4y_keCAHeXmG_SoqLD8SXYistPtesdqIjcsGE-tHO8RR92n7NyxZpqcFS80YfbRFz.png?r=229"
alt="profile"
/>
<p>{profile.name}</p>
</div>
);
})}
<div
onClick={() => navigate("/add-profile")}
className="profile_addProfile_container"
>
<img
src="https://img.icons8.com/ios-glyphs/30/FFFFFF/plus--v1.png"
alt="add profile"
/>
<h2>Add Profile</h2>
</div>
</div>
</div>
);
};
\\ userSlice.js
export const userSlice = createSlice({
name: "user",
initialState: {
user: {
info: null,
profiles: [],
},
},
reducers: {
login: (state, action) => {
state.user.info = action.payload;
},
logout: (state) => {
state.user.info = null;
},
profiles: (state, action) => {
state.user.profiles.push(action.payload);
},
},
});
In the current implementation, when your page is rendered, db.collections runs and you set state setUserProfiles(documents) which renders your app and again db.collections runs. to prevent this you should run db.collections in useEffect.
// fetch users only when your app renders
useEffect(() => {
db.collection("customers")
.doc(user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
setUserProfiles(documents);
});
}, []);
have another useEffect
useEffect(() => {
dispatch(profiles(userProfiles));
}, [userProfiles]);
this will NOT work neither. setUserProfiles will be causing issue. Because when app renders, you fetch data, you set the state, change the userProfiles, this will rerender app again.
The problem with your code is you do not need setUserProfiles. instead in db.collections() when you get the documents, you dispatch the documents and then access the profiles from redux with useSelector
// fetch users only when your app renders
useEffect(() => {
db.collection("customers")
.doc(user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
// setUserProfiles(documents); You do not need this
dispatch(profiles(userProfiles))
});
}, []);
Now use useSelector to reach the state in redux
// assuming reducers name is "users"
const usersState = useSelector((state) => state.users);
now when you use map guard your app
// make sure you use the correct data
// you migh need to destructure
{usersState && usersState.map((profile) => {
For anyone that runs into this issue you may find this useful. Following from yilmaz's helpful answer, I had to update the Profiles.js and userSlice.js as follows...
// Profiles.js
export const Profiles = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const usersState = useSelector(profiles);
useEffect(() => {
db.collection("customers")
.doc(usersState.payload.user.user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
!usersState.payload.user.user.profiles.includes((arr) =>
documents.every(arr)
) && dispatch(profiles(documents));
});
}, []);
return (
<div className="profile_container">
<h1 className="profile_title">Who's Watching?</h1>
<div className="profile_row">
{usersState.payload.user.user.profiles.map((profile) => {
console.log(profile);
return (
<div className="profile_individualProfile">
<img
src="https://occ-0-300-1167.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABY5cwIbM7shRfcXmfQg98cqMqiZZ8sReZnj4y_keCAHeXmG_SoqLD8SXYistPtesdqIjcsGE-tHO8RR92n7NyxZpqcFS80YfbRFz.png?r=229"
alt="profile"
/>
<p>{profile.name}</p>
</div>
);
})}
<div
onClick={() => navigate("/add-profile")}
className="profile_addProfile_container"
>
<img
src="https://img.icons8.com/ios-glyphs/30/FFFFFF/plus--v1.png"
alt="add profile"
/>
<h2>Add Profile</h2>
</div>
</div>
</div>
);
};
// userSlice.js
export const userSlice = createSlice({
name: "user",
initialState: {
user: {
info: null,
profiles: [],
},
},
reducers: {
login: (state, action) => {
state.user.info = action.payload;
},
logout: (state) => {
state.user.info = null;
},
profiles: (state, action) => {
state.user.profiles.length = 0;
state.user.profiles.push(...action.payload);
},
},
});

How do I pass a Redux param to a component?

I am doing a project with React y React-Redux
I am using an api, create a Search component to bring data from this api but I do not know how to pass the word (from what is searched) of redux to the component.
If I want to look for the word "pasta", I don't know how I should pass it on. I'm learning how to use Redux
----- REDUX ----
const INITIAL_STATE = {
search: '',
};
const reducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case SEARCH: return {
...state,
recipes: action.payload,
};
default:
return {...state}
}
};
export function getByName(query) {
return function (dispatch) {
return axios.get("https://www.themealdb.com/api/json/v1/1/search.php?s="+query).then((response) => {
dispatch({
type: SEARCH,
payload: response.data.meals
})
}).catch((error) => console.log(error));
}
}
---- COMPONENTE SEARCH ---
const [search, setSearch ] = useState('')
const query = useSelector((state) => state.recipeReducer.search);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getByName(query))
}, [dispatch])
const handleFilter = e => {
e.preventDefault();
setSearch(e.target.value)
dispatch(getByName(search))
}
return (
<div>
<form>
<label>
Search:
<input type="text" id="title" placeholder='Search recipe...' value={search} onChange={(e) => handleFilter(e)} />
</label>
</form>
</div>
)
}
One thing I noticed is that the "search" in your initial state is redundant. The results are the thing you care about for this toy problem. You should have:
const INITIAL_STATE = {
recipes: [],
}
Then the issue is the construction of your search component. This is the component which is defining your query, not reading it.. Something like this would be more like what you want:
const SearchComponent = ({}) => {
const [search, setSearch] = useState('')
const recipes = useSelector((state) => state.recipeReducer.recipes);
const dispatch = useDispatch();
const handleFilter = e => {
e.preventDefault();
setSearch(e.target.value)
getByName(search)(dispatch) // getByName returns a function.
// That function takes dispatch as an argument.
}
return (
<div>
<form>
<label>
Search:
<input type="text" id="title" placeholder='Search recipe...' value={search} onChange={(e) => handleFilter(e)} />
</label>
</form>
</div>
);
}

I need to rerender functional component

I have a component
const MovieDetail = (props) => {
//const [state, setstate] = useState(initialState)
const { id } = useParams();
const movie = useSelector(getMovie)
const movies = useSelector(getAllMovies)
const dispatch = useDispatch();
useEffect(() => {
fetchMovieById(id)
.then((response) => dispatch(addMovie(response)))
}, [movies])
const onSubmit = (fromData) => {
//here
dispatch(addComments({fromData, id, movies}));
console.log(fromData);
}
return (
<div className='container'>
<br/>
{
<div className='detail'>
<div>
<h2>{movie.title}</h2>
<div>
<img src={movie.img} alt={movie.title}/>
</div>
</div>
<div className='description'>
<div>
{movie.details}
</div>
<div className='comments'>
<h5>Stay your comment here</h5>
<CommentReduxForm onSubmit={onSubmit}/>
</div>
<div className='comments'>
{movie.comments ? movie.comments : <div>no comments yet</div>}
</div>
<div className='ratio'>
<strong>Ratio: {movie.ratio}</strong>
</div>
</div>
</div>
}
</div>
)
}
export default MovieDetail
I'm using redux toolkit, I'm trying to add comment to movie. I come on the site and select the movie, in the form I put my comment, then I click bottun to add comment, then I go to devtools in my browser and I see My comment in the state, but on the page, I see "there are not comments", so I have updated state, but not updated page, and i think I need to rerender my component, or maybe I need another technic?
Also there is my slice with actions and reducers, help me please if anybody can
const initialState = {
movies: [],
movie: {}
}
const movieSlice = createSlice({
name: "movies",
initialState,
reducers: {
addMovies: (state, {payload}) => {
state.movies = payload;
},
addMovie: (state, action) => {
state.movie = action.payload[0];
},
addComments: (state, action) => {
debugger
let author = action.payload.fromData.yourName;
let comment = action.payload.fromData.yourComment;
let movieId = action.payload.id;
let moviesArr = action.payload.movies;
let obj = moviesArr.find((item) => item.id == movieId);
let newObj = {...obj, comments: comment}
const newArr = moviesArr.map(o => {
if (o.id === newObj.id) {
return newObj;
}
return o;
})
state.movies = newArr;
}
},
});
export const {addMovies, addMovie, addComments} = movieSlice.actions;
export const getAllMovies = (state) => state.movies.movies;
export const getMovie = (state) => state.movies.movie;
export default movieSlice.reducer;
In Redux, reducers are not allowed to mutate the original / current state values
You can see these rules in reducer rules
So you should return an updated state instead of changing the oringials
const initialState = {
movies: [],
movie: {}
}
const movieSlice = createSlice({
name: "movies",
initialState,
reducers: {
addMovies: (state, {payload}) => {
return {movies:payload,...state}
},
addMovie: (state, action) => {
return {movie:action.payload[0],...state}
},
addComments: (state, action) => {
let author = action.payload.fromData.yourName;
let comment = action.payload.fromData.yourComment;
let movieId = action.payload.id;
let moviesArr = action.payload.movies;
let obj = moviesArr.find((item) => item.id == movieId);
let newObj = {...obj, comments: comment}
const newArr = moviesArr.map(o => {
if (o.id === newObj.id) {
return newObj;
}
return o;
})
return {movies:newArr,...state}
}
},
});
export const {addMovies, addMovie, addComments} = movieSlice.actions;
export const getAllMovies = (state) => state.movies.movies;
export const getMovie = (state) => state.movies.movie;
export default movieSlice.reducer;

how to prevent re-render react-redux

In the categories component, I render a random image from each category. I also added a onClick event to each image. When the image is clicked, it will dispatch the action getCategory(target.alt) and the DOM will render the products from the clicked category. The problem I got is that every time I clicked a random category image, the DOM will re-render and new random images will appear on the DOM. How do I prevent this re-render? Below is my codes.
const Categories = ({selectedCategory}) => {
const isLoading = useSelector(state => state.productsReducer.isLoading);
const productsByCategory = useSelector(state =>
state.productsReducer.productsByCategories);
const getRandomProductsByCategory = () => {
const randomProducts = []
for(let categories in productsByCategory) {
const randomCategory = productsByCategory[categories][getRandomIndex(productsByCategory[categories].length)];
productsByCategory[categories].map(category => {
if(category === randomCategory) {
randomProducts.push(category)
}
})
}
return randomProducts;
}
return (
<div class='categories-container'>
{getRandomProductsByCategory().map(randomProduct => (
<img onClick={selectedCategory} src={randomProduct.image} />}
</div>
)
}
function App() {
const dispatch = useDispatch();
const category = useSelector(state => state.productsReducer.category)
useEffect(() => {
dispatch(getProducts())
}, [dispatch])
const handleCategoryClick = ({target}) => {
return dispatch(getCategory(target.alt))
}
return (
<>
{/* <ProductsList /> */}
<Categories selectedCategory={handleCategoryClick} />
{category.map(product => <img src={product.image} />)}
</>
)
}
const populateProductsStarted = () => ({
type: 'POPULATE_PRODUCTS/fetchStarted'
})
const populateProductsSuccess = products => ({
type: 'POPULATE_PRODUCTS/fetchSuccess',
payload: products
})
const populateProductsFailed = error => ({
type: 'POPULATE_PRODUCTS/fetchFailed',
error
})
export const getCategory = (category) => ({
type: 'GET_CATEGORY',
category
})
const getProducts = () => async dispatch => {
dispatch(populateProductsStarted())
try {
const response = await fetch(url)
if(response.ok) {
let jsonResponse = await response.json();
return dispatch(populateProductsSuccess(jsonResponse))
}
} catch (err) {
dispatch(populateProductsFailed(err.toString()))
}
}
const initialState = {
isLoading: false,
isError: null,
allProducts: [],
productsByCategories: {},
category: []
}
const productsReducer = (state=initialState, action) => {
switch(action.type) {
case 'POPULATE_PRODUCTS/fetchStarted':
return {
...state,
isLoading: true
}
case 'POPULATE_PRODUCTS/fetchSuccess':
return {
...state,
isLoading: false,
allProducts: action.payload,
productsByCategories: action.payload.reduce((accumulatedProduct, currentProduct) => {
accumulatedProduct[currentProduct.category] = accumulatedProduct[currentProduct.category] || [];
accumulatedProduct[currentProduct.category].push(currentProduct);
return accumulatedProduct;
}, {})
}
case 'POPULATE_PRODUCTS/fetchFailed':
return {
...state,
isError: action.error
}
case 'GET_CATEGORY':
return {
...state,
category: state.allProducts.filter(product => product.category === action.category)
}
default:
return state
}
}
One way to achieve this is through memoization provided by React's useMemo.
const images = React.useMemo(getRandomProductsByCategory().map(randomProduct => (
<img onClick={selectedCategory} src={randomProduct.image} />, [productsByCategory])
return (
<div class='categories-container'>
{images}
</div>
)
This will keep the srcs consistent across re-renders.

Resources