I am trying to update my data in redux but I get an error when I have more than one value in the state.
How I am transferring data into the AllPalletes component below:
<Route exact path='/' render ={(routeProps) => <AllPalletes data = {this.props.palleteNames} />} />
The AllPalletes component, where I am setting up the edit form:
class connectingPalletes extends Component {
render () {
console.log(this.props)
return (
<div>
<Menu inverted>
<Menu.Item header>Home</Menu.Item>
<Menu.Item as = {Link} to = '/createpalette'>Create a Palette</Menu.Item>
</Menu>
<Container>
<Card.Group itemsPerRow={4}>
{this.props.data.map((card) => {
let cardName = card.Name.replace(/\s+/g, '-').toLowerCase()
return (
<Card key = {card._id}>
<Image src = 'https://images.pexels.com/photos/1212406/pexels-photo-1212406.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500' wrapped ui={false}/>
<Card.Content>
<Grid>
<Grid.Column floated = 'left' width ={7}>
{card.edit? (
<PaletteEditForm {...card}/>
) : (
<Card.Header as = {Link} to = {`/palette/${cardName}`}>{card.Name}</Card.Header>
)}
</Grid.Column>
<Grid.Column floated = 'right' width = {5}>
<Icon name = 'pencil' />
<Icon name = 'trash' onClick = {() => this.props.dispatch(removePalette({id: card._id}))}/>
</Grid.Column>
</Grid>
</Card.Content>
</Card>
)
})}
</Card.Group>
<Divider></Divider>
<Divider hidden></Divider>
<Grid centered columns={1}>
<Button as = {Link} to = '/testing'>Go Back</Button>
</Grid>
</Container>
</div>
)
}
}
const AllPalletes = connect()(connectingPalletes)
export default AllPalletes
And here is the edit form:
class EditForm extends Component {
constructor(props) {
super(props)
this.state = {
paletteName: this.props.Name
}
}
handleChange = (e) => {
const val = e.target.value,
s_name = e.target.name
this.setState (() => {
return {
[s_name]: val,
}
})
}
handleSubmit = () => {
let updates = {Name: this.state.paletteName, edit: false}
this.props.dispatch(editPalette(this.props._id, updates))
}
render() {
console.log(this.props)
return (
<Form onSubmit = {this.handleSubmit}>
<Input type = 'text' name = 'paletteName' value = {this.state.paletteName} onChange={this.handleChange} />
</Form>
)
}
}
const PaletteEditForm = connect()(EditForm)
export default PaletteEditForm
My Reducer:
import uuid from 'uuid/v1'
const paletteDefault = [{
Name: "Material UI",
myArray: [],
_id: uuid(),
edit: false
}, {
Name: "Splash UI",
myArray: [],
_id: uuid(),
edit: true
}]
const PaletteReducers = (state = paletteDefault, action) => {
console.log(action)
switch(action.type) {
case 'ADD_PALETTE':
return [...state, action.palette]
case 'REMOVE_PALETTE':
return state.filter(x => x._id !== action.id)
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
default:
return state
}
}
export default PaletteReducers
My Action
// EDIT_PALETTE
const editPalette = (id, updates) => ({
type: 'EDIT_PALETTE',
id,
updates
})
export {addPalette, removePalette, editPalette}
I have a feeling that the problem could be in how I have set up the reducer case.
The edit dispatch only works when I have one value in the state. Otherwise, I am getting this error:
Uncaught TypeError: Cannot read property 'Name' of undefined
at AllPalletes.js:23
Please help..
I found the error. I had not give a return value in the 'EDIT_PALETTE' case, after the if-statement. It was
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
And instead should be:
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
return palette
})
Related
So I have this component set up, which has an onClick handler to like or unlike a certain recipe. I am using the useEffect hook to make sure that the icon is changed accordingly based on the favoriteId prop. When the onClick and the associated queries are executed however, the useEffect hook is not triggered at all, how come?
const RecipeCard = ({ name, image, id, favoriteId }) => {
const { user } = useContext(AuthenticatedUserContext);
const [isFavorite, setIsFavorite] = useState(false);
const onLikePress = async () => {
if (favoriteId) {
await deleteDoc(doc(db, "favorites", favoriteId));
favoriteId = null;
} else {
const res = await addDoc(collection(db, "favorites"), {
userId: user.uid,
recipeId: id,
});
favoriteId = res.id;
}
};
useEffect(() => {
console.log("hit");
favoriteId ? setIsFavorite(true) : setIsFavorite(false);
}, [favoriteId]);
return (
<TouchableWithoutFeedback
onPress={onPress}
style={{ flex: 1, padding: 10 }}
>
<View>
<AntDesign
onPress={() => {
if (!user) {
setShowNoAccountModal(true);
} else {
onLikePress();
}
}}
name={isFavorite ? "like1" : "like2"}
color="black"
size={30}
/>
</View>
</TouchableWithoutFeedback>
);
};
export default RecipeCard;
Parent component:
export const HomeScreen = () => {
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
getRecipes();
}, []);
const getRecipes = async () => {
const querySnapshot = await getDocs(collection(db, "receipes"));
const fetchedRecipes = [];
for (const d of querySnapshot.docs) {
const citiesRef = collection(db, "favorites");
const q = query(
citiesRef,
where("userId", "==", user.uid),
where("recipeId", "==", d.id)
);
const querySnapshot = await getDocs(q);
const isFavorite = false;
if (querySnapshot.empty) {
favoriteId = null;
} else {
favoriteId = querySnapshot.docs[0].id;
}
const recipe = {
...d.data(),
id: d.id,
favoriteId,
};
fetchedRecipes.push(recipe);
}
setRecipes(fetchedRecipes);
setLoading(false);
};
return (
<View style={styles.container}>
{/* <Button title="Sign Out" onPress={handleLogout} /> */}
<Text style={{ fontSize: 24, fontWeight: "bold", paddingBottom: 10 }}>
Recepten
</Text>
{recipes && recipes.length > 0 && (
<FlatList
data={recipes}
renderItem={({ item }) => (
<RecipeCard
name={item.title}
id={item.id}
image={item.thumbnail}
favoriteId={favoriteId}
/>
)}
keyExtractor={(item) => item.id}
horizontal
/>
)}
</View>
);
};
You are mutating your favoriteId variable, but not using setState, so it is not done properly and react is unaware your variable might have changed.
To fix this, you will need to pass a function to change your favoriteId prop inside of your component's parent:
// in parent:
const [favoriteId, setFavoriteId] = useState() // this code should be here already
return (
// your code here
<RecipeCard changeFavoriteId={(newId) => setFavoriteId(newId)} />
// just add this changeFavoriteId prop, the old props should be here still though
// rest of your code
)
// in RecipeCard.js
const RecipeCard = ({ name, image, id, favoriteId, changeFavoriteId }) => {
// your code here
const onLikePress = async () => {
if (favoriteId) {
await deleteDoc(doc(db, "favorites", favoriteId));
favoriteId = null;
} else {
const res = await addDoc(collection(db, "favorites"), {
userId: user.uid,
recipeId: id,
});
changeFavoriteId(res.id) // this bit here changed, now you are using setState
}
};
// the rest of your code
By using setState function that you obtained from parent useState, your component will trigger a rerender after the value is changed.
First time posting and have my Learner plates on.
Using axios with a json mock-server with gets through a ContextProvider to trying to map my data to present with Bootstrap cards.
(then) also click on the card to present on another page (not attempted yet)
My data was presenting fine in list form and in a card through Outlet - but I'm trying instead to present the whole array through cards.
I'd appreciate any help I can get. Apologies in advance for the lengthy code (I'm not sure where the problem is)
ProductList.js
import React from 'react'
import { ProductContext } from './ProductContext'
import ProductDetail from './ProductDetail'
function ProductList(props) {
function productList(products) {
if (products === null) return
return (
<div className='card-container'>
{
products.map(product => (<ProductDetail />))
}
</div>
)
}
return (
<div direction="horizontal" style={{ textAlign: 'center' }}>
<h1>Artworks</h1>
<ProductContext.Consumer>
{({ products }) => (
productList(products)
)}
</ProductContext.Consumer>
</div>
)
}
export default ProductList
ProductDetail.js
function ProductDetail(props) {
const hasFetchedData = useRef(false)
let params = useParams()
let navigate = useNavigate()
let { getProduct, deleteProduct } = useContext(ProductContext)
let [product, setProduct] = useState()
useEffect(() => {
if (!hasFetchedData.current) {
const res = axios.get("http://localhost:3002/products");
setProduct(res);
hasFetchedData.current = true;
}
}, [])
useEffect(() => {
async function fetch() {
await getProduct(params.productId)
.then((product) => setProduct(product))
}
fetch()
}, [params.productId]); // eslint-disable-line react-hooks/exhaustive-deps
let [error, setError] = useState()
useEffect(() => {
setError(null)
async function fetch() {
await getProduct(params.productId)
.then((product) => setProduct(product))
.catch((message) => setError(message))
}
fetch()
}, [params.productId, getProduct])
function errorMessage() {
return <Alert variant="danger">Stockroom is empty: {error}</Alert>
}
function handleDeleteProduct(id) {
deleteProduct(id)
navigate('/products')
}
function loading() {
return <div className="w-25 text-center"><Spinner animation="border" /></div>
}
function productCard() {
let { id, artistname, born, piecename, painted, imgurl, price } = product
return (
<Card className="w-25" key={product.id}>
<Card.Img variant="top" src={imgurl} />
<Card.Body>
<Card.Title>{artistname} {born}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">{piecename}</Card.Subtitle>
<Card.Subtitle className="mb-2 text-muted">{painted}</Card.Subtitle>
<Card.Text>
<strong>Price:</strong> <span>${price}</span>
</Card.Text>
<Link to={`/products/${id}/edit`} className="btn btn-primary mx-3">Edit</Link>
<Button variant="danger" onClick={handleDeleteProduct.bind(this, id)}>Delete</Button>
</Card.Body>
</Card>
)
}
if (error) return errorMessage()
if (product === undefined) return loading()
return product.id !== parseInt(params.productId) ? loading() : productCard()
}
export default ProductDetail
ProductContext.js
export const ProductContext = createContext()
export const ProductProvider = (props) => {
const [products, setProducts] = useState([])
useEffect(() => {
async function getProducts() {
await refreshProducts()
}
getProducts()
}, []);
function refreshProducts() {
return axios.get("http://localhost:3002/products")
.then(response => {
setProducts(response.data)
})
}
function getProduct(id) {
return axios.get(`http://localhost:3002/products/${id}`)
.then(response =>
new Promise((resolve) => resolve(response.data))
)
.catch((error) =>
new Promise((_, reject) => reject(error.response.statusText))
)
}
function deleteProduct(id) {
axios.delete(`http://localhost:3002/products/${id}`).then(refreshProducts)
}
function addProduct(product) {
return axios.post("http://localhost:3002/products", product)
.then(response => {
refreshProducts()
return new Promise((resolve) => resolve(response.data))
})
}
function updateProduct(product) {
return axios.put(`http://localhost:3002/products/${product.id}`, product)
.then(response => {
refreshProducts()
return new Promise((resolve) => resolve(response.data))
})
}
return (
<ProductContext.Provider
value={{
products,
refreshProducts,
getProduct,
deleteProduct,
addProduct,
updateProduct
}}
>
{props.children}
</ProductContext.Provider>
)
}
I am creating a simple game react app and when I try to add a player to my players list it seems to be creating an infinite loop and I'm not sure why. I tried to use useEffect to render the player list on initial load but that didn't help so I removed it for now to simplify. Any ideas what I could be doing differently?
App.js
import React, { useEffect } from 'react'
import {useDispatch, useSelector} from 'react-redux';
import './App.css';
import {setPlayerName, increaseCurrentPlayerId, decreaseCurrentPlayerId, addPlayerToList} from './redux/reducers/playerReducer';
function App() {
const dispatch = useDispatch()
const playerName = useSelector(state => state.playerName);
const playerList = useSelector(state => state.playerList);
const currentPlayerId = useSelector(state => state.currentPlayerId)
// dispatch(addPlayerToList('Test'))
const addPlayer = (player) => {
dispatch(addPlayer(player))
dispatch(setPlayerName(''))
}
const renderPlayerList = () => {
if (playerList.length < 1) {
return (
<div>
No Players
</div>
)
} else {
return (
playerList.map(p =>
<p>p.name</p>
)
)
}
}
return (
<div className="App">
<input
type='text'
name='playerName'
onChange={({ target }) => dispatch(setPlayerName(target.value))}
required
/>
Name<br/>
<button type='button'
onClick={() => addPlayer(playerName)}
>
Add Player</button> <br />
<br />
</div>
);
}
export default App;
playerReducer.js
export const playerNameReducer = (state = '', action) => {
switch (action.type) {
case 'SET_PLAYER_NAME':
return action.data;
default:
return state;
}
};
export const playerListReducer = (state = null, action) => {
switch (action.type) {
case 'ADD_PLAYER':
return [...state, action.data];
default:
return state;
}
};
Action Creators
export const setPlayerName = playerName => {
return {
type: 'SET_PLAYER_NAME',
data: playerName,
};
};
export const addPlayerToList = player => {
return {
type: 'ADD_PLAYER',
data: player,
};
};
addPlayer calls itself
const addPlayer = (player) => {
dispatch(addPlayer(player))
}
I have a react-in-rails application that utilizes the google-maps-react api. The app works fine locally but when deployed to heroku, any component that imports google-maps-react does not render. Since this is generally the landing page for most users, the app is not accessible at all.
When all the components that import or render google-maps-react are removed, the app deploys correctly.
import React from "react"
import MapContainer from "./MapContainer"
import StoreList from './StoreList'
class FindBar extends React.Component {
render () {
const {stores, openTab, success} = this.props
return (
<div className="findbar" >
<div className="mapcomponent">
<MapContainer
stores={stores}
openTab={openTab}
success={success}
/>
</div>
<br/>
<StoreList
stores={stores}
openTab={openTab}
/>
{this.props.success &&
<Redirect to="/user_home/opentabs" />
}
</div>
);
}
}
export default FindBar
import React, { Component } from 'react';
import { Button, Card } from 'reactstrap';
import { Map, GoogleApiWrapper, Marker, InfoWindow } from 'google-maps-react';
import UserHome from './UserHome.js'
import StoreMarkerWindow from './StoreMarkerWindow.js'
import InfoWindowEx from './InfoWindowEx.js'
const mapStyles = {
width: '100%',
height: '100vh',
};
class MapContainer extends Component {
constructor(props) {
super(props)
this.state = {
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
address: [],
location: {},
displayMarkers: [],
success: false,
}
}
componentDidMount = () => {
this.fetchMarkers()
}
componentDidUpdate = (prevProps) => {
if (prevProps.stores === this.props.stores){
return true
}
this.fetchMarkers()
}
openTab = () => {
console.log(this.state.selectedPlace.storeId)
// this.props.openTab(this.state.selectedPlace.storeId)
}
onClick = (props, marker, e) => {
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true
})
}
onClose = props => {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
});
}
}
fetchMarkers = () => {
const newMarkers = []
this.props.stores.map((store, index) => {
const location = `${store.address1}, ${store.city}, ${store.state}, ${store.zip}`
this.geocodeAddress(location)
.then((geoco)=>{
newMarkers.push({lat: geoco.lat,
lng: geoco.lng,
storeId: store.id,
name: store.establishmentname,
location: location,
info: store.additionalinfo,
})
this.setState({ displayMarkers:newMarkers})
})
})
}
// create a function that maps stores.address, stores.city, stores.state, stores.zipcode
// and returns it to the geocodeAddress and then geocodeAddress returns it to
// the displayMarkers
geocodeAddress = (address) => {
const geocoder = new google.maps.Geocoder()
return new Promise((resolve, reject) => {
geocoder.geocode({'address': address}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
resolve(results[0].geometry.location.toJSON())
} else {
reject()
}
})
})
}
render() {
const{
activeMarker,
showingInfoWindow,
selectedPlace,
onMapOver,
}=this.props
return (
<div className="mapContainer" style={mapStyles}>
<Map
google={this.props.google}
onMouseover={this.onMapOver}
zoom={14}
style={mapStyles}
initialCenter={{
lat: 32.7091,
lng: -117.1580
}}
>
{this.state.displayMarkers.map((coordinates, index) => {
const{storeId, lat, lng, name, location, info} = coordinates
return (
<Marker onClick={this.onClick}
key={index}
id={storeId}
name={name}
position = {{lat, lng}}
location={location}
info= {info}
>
</Marker>
)
})}
<InfoWindowEx
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.onClose}
>
<div>
<StoreMarkerWindow
name={this.state.selectedPlace.name}
location={this.state.selectedPlace.location}
info={this.state.selectedPlace.info}
id={this.state.selectedPlace.id}
openTab={this.props.openTab}
/>
</div>
</InfoWindowEx>
</Map>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey: 'xxxx'
})(MapContainer);
TypeError: t is not a function
at Object.a (windowOrGlobal.js:18)
at Object.<anonymous> (windowOrGlobal.js:5)
at Object.<anonymous> (windowOrGlobal.js:5)
at n (bootstrap:19)
at Object.<anonymous> (ScriptCache.js:3)
at n (bootstrap:19)
at Object.<anonymous> (GoogleApiComponent.js:5)
at n (bootstrap:19)
at Object.<anonymous> (index.js:5)
at n (bootstrap:19)
I'm trying to render out my calendar's event summaries, using a .map function. I've stored my calendar events object in state, but can't find a way to .map out the different event summaries. Any suggestions?
export default class Container extends React.Component{
calendarID="xxx"
apiKey="zzz";
state = { events: [] };
setEvents = (a) => {
this.setState(a);
}
componentDidMount() {
ajax.get(`https://www.googleapis.com/calendar/v3/calendars/${this.calendarID}/events?fields=items(summary,id,location,start)&key=${this.apiKey}`)
.end((error, response) => {
if(!error && response ) {
this.setEvents({events: response.body});
console.log("success");
console.log(this.state.events);
} else {
console.log("Errors: ", error);
}
});
}
render(){
let lista = this.state.events;
let arr = Object.keys(lista).map(key => lista[key])
return (
<div class = "container">
{arr.map((event, index) => {
const summary = event.summary;
return (<div key={index}>{summary}</div>);
})}
</div>
);
}
}
EDIT:
Thanks for your answers! This is the data that the ajax call returns when I console log this.state.items:
Object {items: Array[1]}
items: Array[1]
0: Object
id: "cmkgsrcohfebl5isa79034h8a4"
start: Object
summary: "Stuff going down"
If I skip the ajax call and create my own state, the mapping works:
state = { items: [
{ items: { summary: "testing"} },
{ items: { summary: "12"} },
{ items: { summary: "3"} }
]};
To get this working, however, I change my render-function to:
render(){
let lista = this.state.items;
let arr = Object.keys(lista).map(key => lista[key])
return (
<div class = "container">
{arr.map((item, index) => {
const summary = item.items.summary;
return (<div key={index}>{summary}</div>);
})}
</div>
);
}
So maybe it has something to do with the object that this.state.items returns from the ajax call?
Edit2: #Andrea Korinski, you were right! I changed my render function to this, and now it works:
render(){
let list = this.state.items;
const arr = (list.items || []).map((item, index) => {
const summary = item.summary;
return (<div key={index}>{summary}</div>);
});
return (
<div class = "container">
{arr}
</div>
);
}
}
The whole component:
export default class Container extends React.Component{
calendarID="xxx";
apiKey="zzz";
state = {items: []};
setEvents = (a) => {
this.setState(a);
}
componentDidMount() {
ajax.get(`https://www.googleapis.com/calendar/v3/calendars/${this.calendarID}/events?fields=items(summary,id,location,start)&key=${this.apiKey}`)
.end((error, response) => {
if(!error && response ) {
this.setEvents({items: response.body});
console.log("success");
console.log(this.state.items);
} else {
console.log("Errors: ", error);
}
});
}
render(){
let list = this.state.items;
const irr = (list.items || []).map((item, index) => {
const summary = item.summary;
return (<div key={index}>{summary}</div>);
});
return (
<div class = "container">
{irr}
</div>
);
}
}