I try to get firestore database and dispatch this database in redux. and print this database in my listpage.
I succeed to get firestore database, and console.log are print all data. but I try to use map() function. print only one data. I don't know why this had happened.
I think my code is wrong. but I don't know where I got it wrong.
DictCard.js
import { collection, getDocs} from "firebase/firestore";
import React, { useEffect } from "react";
import { db } from "../firebase";
import { useSelector, useDispatch } from "react-redux";
import { getDict } from "../redux/ListReducer";
const Card = ({dict}) => {
return(
<div className="inbox">
<p className="text1">단어</p>
<p className="text2">{dict.word}</p>
<p className="text1">설명</p>
<p className="text2">{dict.explain}</p>
<p className="text1">예시</p>
<p className="text2" style={{color:"lightskyblue",paddingBottom:"0"}}>{dict.example}</p>
</div>
)
}
const DictCard = () => {
const dictList = useSelector((state) => state.dictList.dicts);
const dispatch = useDispatch();
useEffect( async () => {
const query = await getDocs(collection(db, "dict"));
query.forEach(doc => {
console.log([doc.id, doc.data()])
dispatch(getDict([{id: doc.id, text: doc.data()}]))
});
},[]);
return(
<div className="dict-card" >
{dictList.map((dict) => (
<Card dict = {dict.text} key = {dict.id}/>
))}
</div>
)
}
export default DictCard;
ListReducer.js
import { createSlice } from "#reduxjs/toolkit";
// const initialState = [{id:"",text:{word:"",explain:"",example:""}}]
const initState = {
dicts: [{
id:"",
text:{
word:"",
explain:"",
example:""
}
},]
}
export const ListReducer = createSlice({
name: "dictList",
initialState: initState,
reducers: {
addDict: (state, action) => {
state.dicts = action.payload
},
getDict: (state, action) => {
state.dicts = action.payload
},
updateDict: (state, action) => {
},
deleteDict: (state, action) => {
},
},
});
export const { addDict, getDict, updateDict, deleteDict } = ListReducer.actions;
export default ListReducer.reducer;
I think dispatch's position is wrong but i have no idea
I solved problem.
useEffect( async () => {
const arr = []
const query = await getDocs(collection(db, "dict"));
query.forEach(doc => {
console.log([doc.id, doc.data()])
// dispatch(getDict([{id: doc.id, text: doc.data()}]))
arr.push({id: doc.id, text: doc.data()})
});
dispatch(getDict(arr))
},[]);
need to make empty array
Try not to use async function for useEffect
useEffect(() => {
const fetchData = async () => {
const query = await getDocs(collection(db, "dict"));
query.forEach(doc => {
console.log([doc.id, doc.data()])
dispatch(getDict([{id: doc.id, text: doc.data()}]))
});
}
fetchData()
},[]);
I think the issue may caused from the useEffect function. If it not, please comment below so i can track the issue more clearly
Related
I'm building a simple venue review app using react/redux toolkit/firebase.
My Firestore has one collection, venues , and each document within this collection is structured as follows:
name:'',
photo:'',
reviews: [
{
title:'',
blurb:'',
reviewId:''
},
{...}
]
My app has one slice to control all the state related to venues, including the reviews - but should reviews have their own slice?
I'm asking this because my app is displaying glitchy behaving including crashing on page refresh, and not updating the components when new state is added. Is how I'm handling reviews responsible for causing the problems in my app, and if so, how do I handle it?
Currently my venue state is managed as follows:
Venues fetched in VenueList.js...
useEffect(() => {
dispatch(fetchVenues());
}, [dispatch]);
...which triggers thunk in venueSlice.js
export const fetchVenues = createAsyncThunk("venues/fetchVenues", async () => {
try {
const venueArray = [];
const q = query(collection(db, "venues"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) =>
venueArray.push({ id: doc.id, ...doc.data() })
);
return venueArray;
} catch (err) {
console.log("Error: ", err);
}
});
Reviews added in AddReview.js
const handleClick = (e) => {
e.preventDefault();
if (title && blurb) {
const reviewId = nanoid()
const review = { id, title, blurb, reviewId };
dispatch(postReview(review));
}
};
...handled with another thunk in venueSlice.js
export const postReview = createAsyncThunk("venues/postReview", async (review) => {
try {
const venueRef = doc(db,"venues",review.id)
await updateDoc(venueRef, {
reviews: arrayUnion({
title:review.title,
blurb:review.blurb,
reviewId:review.reviewId })
})
} catch (err) {
console.log('Error :', err)
}
})
Note: Reviews posted to firestore without updating redux global state:
const venueSlice = createSlice({
name: "venues",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchVenues.fulfilled, (state, action) => {
state.venues = action.payload;
})
},
});
I extract venue state into Venue.js. Venue state (containing reviews) is then passed as props to Reviews.js, where reviews are added/deleted.
Venue.js
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import AddReview from "../../components/AddReview";
import Reviews from "../../components/Reviews";
const Venue = () => {
const { id } = useParams();
const venues = useSelector((state) => state.venues);
console.log(venues)
const venue = venues.venues.filter((item) => item.id === id);
console.log(venues)
const content = venue.map((item) => (
<div className="venue-page-main" key = {item.name}>
<h2>{item.name}</h2>
<img src={item.photo} alt = "venue"/>
</div>
));
return (
<>
{content}
<AddReview id = {id}/>
<Reviews venue = {venue}/>
</>
);
};
export default Venue;
Reviews.js
import { useDispatch } from "react-redux";
import { deleteReview,fetchVenues } from "../features/venues/venueSlice";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
const Reviews = ({venue}) => {
const venueId = venue[0].id
const dispatch = useDispatch()
const removeReview = (review) => {
dispatch(deleteReview({...review, id:venueId}))
}
const content = venue && venue[0].reviews && venue[0].reviews.map(review => (
<div className="review" key = {review.reviewId}>
<h2>{review.title}</h2>
<h3>{review.blurb}</h3>
<div>
<p>Edit</p>
<button onClick = {() => removeReview(review)}>Delete</button>
</div>
</div>
))
return (
<div className="all-reviews">
{content}
</div>
)
}
export default Reviews;
Here's the entire venueSlice.js file
import { createSlice,createAsyncThunk } from "#reduxjs/toolkit";
import { collection,query,getDocs,doc,updateDoc,arrayUnion, arrayRemove, FieldValue } from "firebase/firestore";
import { db } from "../../firebaseConfig";
const initialState = {
venues: []
}
export const fetchVenues = createAsyncThunk("venues/fetchVenues", async () => {
try {
const venueArray = [];
const q = query(collection(db, "venues"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) =>
venueArray.push({ id: doc.id, ...doc.data() })
);
return venueArray;
} catch (err) {
console.log("Error: ", err);
}
});
export const postReview = createAsyncThunk("venues/postReview", async (review) => {
try {
const venueRef = doc(db,"venues",review.id)
await updateDoc(venueRef, {
reviews: arrayUnion({
title:review.title,
blurb:review.blurb,
reviewId:review.reviewId })
})
} catch (err) {
console.log('Error :', err)
}
})
export const deleteReview = createAsyncThunk("venues/deleteReview", async (review) => {
const newReview = {blurb:review.blurb, title: review.title, reviewId: review.reviewId}
try {
const venueRef = doc(db,"venues",review.id)
await updateDoc(venueRef, {
reviews: arrayRemove(newReview)
})
} catch (err) {
console.log('Error: ', err)
}
})
const venueSlice = createSlice({
name: "venues",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchVenues.fulfilled, (state, action) => {
state.venues = action.payload;
})
},
});
export default venueSlice.reducer
So I am building an e-commerce website checkout page with commerce.js. I have a context that allows me to use the cart globally. But on the checkout page when I generate the token inside useEffect , the cart variables have not been set until then.
My context is as below
import { createContext, useEffect, useContext, useReducer } from 'react';
import { commerce } from '../../lib/commerce';
//Provides a context for Cart to be used in every page
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = 'SET_CART';
const initialState = {
id: '',
total_items: 0,
total_unique_items: 0,
subtotal: [],
line_items: [{}],
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
useEffect(() => {
getCart();
}, []);
const getCart = async () => {
try {
const cart = await commerce.cart.retrieve();
setCart(cart);
} catch (error) {
console.log('error');
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={state}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
Now on my checkout page
const CheckoutPage = () => {
const [open, setOpen] = useState(false);
const [selectedDeliveryMethod, setSelectedDeliveryMethod] = useState(
deliveryMethods[0]
);
const [checkoutToken, setCheckoutToken] = useState(null);
const { line_items, id } = useCartState();
useEffect(() => {
const generateToken = async () => {
try {
const token = await commerce.checkout.generateToken(id, {
type: 'cart',
});
setCheckoutToken(token);
} catch (error) {}
};
console.log(checkoutToken);
console.log(id);
generateToken();
}, []);
return <div> {id} </div>; //keeping it simple just to explain the issue
};
In the above code id is being rendered on the page, but the token is not generated since on page load the id is still blank. console.log(id) gives me blank but {id} gives the actual value of id
Because CheckoutPage is a child of CartProvider, it will be mounted before CartProvider and the useEffect will be called in CheckoutPage first, so the getCart method in CartProvider hasn't been yet called when you try to read the id inside the useEffect of CheckoutPage.
I'd suggest to try to call generateToken each time id changes and check if it's initialised first.
useEffect(() => {
if (!id) return;
const generateToken = async () => {
try{
const token = await commerce.checkout.generateToken(id, {type: 'cart'})
setCheckoutToken(token)
} catch(error){
}
}
console.log(checkoutToken)
console.log(id)
generateToken()
}, [id]);
I have axios making a get request to fetch a request a list of vessels and their information,
i am trying to use redux slice, and populate the data using dispute, however the state is always empty dispute not having any errors
the vesselSlice :
import { createSlice } from "#reduxjs/toolkit";
import { api } from "../components/pages/screens/HomeScreen";
const vesselSlice = createSlice({
name: "vessels",
initialState: {
vessels: [],
},
reducers: {
getVessels: (state, action) => {
state.vessels = action.payload;
},
},
});
export const vesselReducer = vesselSlice.reducer;
const { getVessels } = vesselSlice.actions;
export const fetchVessels = () => async (dispatch) => {
try {
await api
.get("/vessels")
.then((response) => dispatch(getVessels(response.data)));
} catch (e) {
return console.error(e.message);
}
};
the HomeScreen.js :
import React, { useEffect } from "react";
import VesselCard from "../../VesselCard";
import axios from "axios";
import { useDispatch, useSelector } from "react-redux";
import { fetchVessels } from "../../../features/vesselSlice";
export const api = () => {
axios
.get("http://127.0.0.1:8000/api/vessels/info")
.then((data) => console.log(data.data))
.catch((error) => console.log(error));
};
function HomeScreen() {
const { vessels, isLoading } = useSelector((state) => state.vessels);
const dispatch = useDispatch();
// This part:
useEffect(() => {
fetchVessels(dispatch);
}, [dispatch]);
return (
<div>
Fleet vessels :
<div className="fleet-vessels-info">
{vessels.map((vessel) => (
<VesselCard vessel={vessel} />
))}
</div>
</div>
);
}
export default HomeScreen;
You receive dispatch this way. It is not being received from any middleware.
export const fetchVessels = async (dispatch) =>{}
If you want to continue using your approach, call function this way
useEffect(() => {
fetchVessels()(dispatch);
}, [dispatch]);
Api function error
export const api = async () => {
try {
const res = await axios.get("http://127.0.0.1:8000/api/vessels/info");
return res.data;
} catch (error) {
console.log(error);
}
};
export const fetchVessels = () => async (dispatch) => {
try {
await api()
.then((data) => dispatch(getVessels(data)));
} catch (e) {
return console.error(e.message);
}
};
I dont understant what is wrong with this code, im passing in ID and filtering out state, but it just wont remove, cant figure it out, would love some help.
slice:
import { createSlice } from "#reduxjs/toolkit";
const movieSlice = createSlice({
name: "movie",
initialState: { favoriteMovies: [] },
reducers: {
addMovie: (state, action) => {
state.favoriteMovies = [...state.favoriteMovies, action.payload];
},
removeMovie: (state, action) => {
state.favoriteMovies = state.favoriteMovies.filter(
(movie) => movie.imdbID !== action.payload
);
},
},
});
export const { addMovie, removeMovie } = movieSlice.actions;
export const selectMovie = (state) => state.movie.favoriteMovies;
export default movieSlice.reducer;
dispatching:
const MovieDetail = ({ movie }) => {
const [isFavorite, setIsFavorite] = useState(false);
const dispatch = useDispatch();
const imdbID = movie.imdbID;
const handleAddFavorite = () => {
dispatch(addMovie({ movie }));
setIsFavorite(true);
};
const handleRemoveFavorite = () => {
dispatch(removeMovie({ imdbID }));
console.log(imdbID);
setIsFavorite(false);
};
it does nothing when should remove, and then add it again. The ids i pass in are correct.
The way you are dispatching with
dispatch(removeMovie({ imdbID }));
imdbID will end up as action.payload.imdbID.
So you need to either access that, or dispatch like
dispatch(removeMovie(imdbID));
I'm trying to get access to the store from outside of the component and subscribe it for store changes. I have separate file which I'm using to make an API call.
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import store from '../store/store'
const currentWeatherApi = {
key: "",
base: "https://api.openweathermap.org/data/2.5/"
}
const API = () => {
const inputValue = useSelector(state => state.inputValue);
store.subscribe(() => {
console.log(inputValue)
})
if(inputValue) {
fetch(`${currentWeatherApi.base}weather?q=rzeszow&units=metric&APPID=${currentWeatherApi.key}`)
.then(res => res.json())
.then(result => {
const temp = (Math.floor(result.main.temp));
const tempMin = result.main.temp_min
const tempMax = result.main.temp_max;
const location = result.name;
const sunrise = new Date(result.sys.sunrise * 1000).toISOString().substr(11, 8);
const sunset = new Date(result.sys.sunset * 1000).toISOString().substr(11, 8);
const country = result.sys.country;
const wind = result.wind.speed;
const pressure = result.main.pressure;
const sky = result.weather[0].main;
})
}
export default API;
When I try to console.log anything, nothing happens, like it's not even read. What am I doing wrong?
API isn't a React component so the useSelector hook won't work. You can import the store and call getState on it to get the current state's value.
getState()
Returns the current state tree of your application. It is equal to the
last value returned by the store's reducer.
Returns
(any): The current state tree of your application.
const API = () => {
const state = store.getState();
if(state.searchingBar.inputValue) {
fetch(`${currentWeatherApi.base}weather?q=rzeszow&units=metric&APPID=${currentWeatherApi.key}`)
.then(res => res.json())
.then(result => {
....
})
}
}
Edit for Demo
Demo code:
const initialState = {
inputValue: ""
};
const slice = createSlice({
initialState,
name: "searchingBar",
reducers: {
updateValue: (state, action) => {
state.inputValue = action.payload;
}
}
});
const rootReducer = combineReducers({
searchingBar: slice.reducer
});
const store = configureStore({
reducer: rootReducer
});
const fetch = (url, options) => {
console.log("fetch", { url });
return new Promise((resolve) => {
const response = {
json: async () => "boom"
};
return resolve(response);
});
};
const currentWeatherApi = {
base: "base",
key: "key"
};
const API = () => {
const state = store.getState();
console.log(JSON.stringify(state));
if (state.searchingBar.inputValue) {
fetch(
`${currentWeatherApi.base}weather?q=rzeszow&units=metric&APPID=${currentWeatherApi.key}`
)
.then((res) => res.json())
.then((result) => {
console.log("Result", result);
store.dispatch(slice.actions.updateValue(""));
});
}
};
export default function App() {
return (
<Provider store={store}>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button type="button" onClick={API}>
Call API
</button>
<button
type="button"
onClick={() => store.dispatch(slice.actions.updateValue("test"))}
>
Update State
</button>
</div>
</Provider>
);
}