Iterating through Firebase database object and assining keys to react components - javascript

I am working in ReactJS and I have a database on Firebase called posts as a collection of objects. I am trying to iterate through the database objects and return a component with each assigned with one of the unique keys that firebase creates for the objects as props.
As a result example of what I am trying to achieve:
<Post
key={-MQcz3BC4lbKnvFe8Jl}
title={post.title}
type={post.type}
body={post.body}
clicked={() => this.postAnswerHandler( post.id )}
/>
<Post
key={-MQxVra23HwWb8ogRJZ}
title={post.title}
type={post.type}
body={post.body}
clicked={() => this.postAnswerHandler( post.id )}
/>
...and so on. Can anyone help with iterating through the firebase Data and assigning the keys to my React components?
Here is the current code I am using for this:
class Posts extends Component {
state = {
posts: []
}
componentDidMount () {
axios.get( 'https://blog-6d4da-default-rtdb.firebaseio.com/posts.json' )
.then( response => {
let posts = Object.values(response.data);
let key = Object.keys(response.data);
const updatedPosts = posts.map( post => {
return {
...post,
}
} );
this.setState( { posts: updatedPosts } );
} )
.catch( error => {
console.log( error );
// this.setState({error: true});
} );
}
render () {
let posts = <p style={{ textAlign: 'center' }}>Something went wrong!</p>;
if ( !this.state.error ) {
posts = this.state.posts.map( (post) => {
return (
<Post
key={post.key}
title={post.title}
type={post.type}
body={post.body}
clicked={() => this.postAnswerHandler( post.id )}
/>
// </Link>
);
} );
}

I think you're looking for:
let keys = Object.keys(response.data);
const updatedPosts = keys.map( key => {
return {
key, ...response.data[key],
}
} );
this.setState( { posts: updatedPosts } );

Use Object.entries()
axios.get( 'https://blog-6d4da-default-rtdb.firebaseio.com/posts.json' )
.then( response => {
this.setState( { posts: response } );
} )
render () {
let posts = <p style={{ textAlign: 'center' }}>Something went wrong!</p>;
if ( !this.state.error ) {
posts = Object.entries(this.state.posts).map( ([post, key]) => {
return (
<Post
key={key}
title={post.title}
type={post.type}
body={post.body}
clicked={() => this.postAnswerHandler( post.id )}
/>
// </Link>
);
} );
}

Related

React response data is undefined when rendered

In my react app I am fetching data from API, the response object is as below:-
posts {
total: 3,
data:[
{some data},
{some data},
{some data}
] }
The data is rendered using a map() function.
but whenever the page renders the data is displayed only once. After the first render when the page is re-rendered the data array is undefined (in console.log).
code for component:-
const function = () => {
const dispatch = useDispatch();
const { posts, isSuccess } = useSelector((state) => state.posts);
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
return (
<>
<div className="main-content">
{posts.data.map((post) => (
<Post
postId={post._id}
postSubject={post.subject}
/>
))}
</div>
</>
);
}
export default function;
You can try this before map function.
<React.Fragment>
{typeof object.data === typeof [] && (
<React.Fragment>
{object.data.map((obj, index) => {
return <React.Fragment>return some code .....</React.Fragment>;
})}
</React.Fragment>
)}
</React.Fragment>
I hope it's work

how can I delete the element in react js

I want to create simple application with react js, which should show the users in the display and then when I click on the delete button, it should delete the following item, however I am having some errors.
import React, { useEffect, useState } from 'react'
const App = () => {
const [users, setUsers] = useState([])
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((users) => {
setUsers(users);
})
}, [users]);
const deleteMe = () => {
setUsers(prevState => {
return prevState.filter(e => e.name)
})
}
return (
<>
{users.map((user) => {
return (
<>
<div> {user.name}
<button onClick={deleteMe}> Delete </button>
{/* <button onClick={}> Update </button> */}
</div>
</>
)
})}
</>
)
}
export default App
To remove the user, the callback (onClick) must have enough information to identify the user to be removed.
In this example, you have some options:
Remove by name. Only if the user names are unique:
const deleteMe = (userName) => {
setUsers(prevState => {
return prevState.filter(e => e.name !== userName)
})
}
return (
<>
{users.map((user) => {
return (
<>
<div> {user.name}
<button onClick={() => deleteMe(user.name)}> Delete </button>
{/* <button onClick={}> Update </button> */}
</div>
</>
)
})}
</>
)
Remove by the element itself. Only if the element isn't repeated in the array (the object itself):
const deleteMe = (user) => {
setUsers(prevState => {
return prevState.filter(e => e !== user)
})
}
return (
<>
{users.map((user) => {
return (
<>
<div> {user.name}
<button onClick={() => deleteMe(user)}> Delete </button>
{/* <button onClick={}> Update </button> */}
</div>
</>
)
})}
</>
)
Remove by the array index. Only if the state is an array, usually:
const deleteMe = (userIndex) => {
setUsers(prevState => {
return prevState.filter((e, i) => i !== userIndex)
})
}
return (
<>
{users.map((user, i) => {
return (
<>
<div> {user.name}
<button onClick={() => deleteMe(i)}> Delete </button>
{/* <button onClick={}> Update </button> */}
</div>
</>
)
})}
</>
)
See how a second parameter i was added to the map and filter functions. That is usually ignored, but it may be useful sometimes.
As this method may fail if the array is reordered of an element is added/removed between the render and the callback, I wouldn't recommend it unless there is no other alternative.
Look at the useEffect code. Because you have users as a dependency the effect will pick up any changes to that state. State changes, you make an API call, then update users, the effect gets called again on the next render, you update users in state, users gets updated again... etc.
It sounds like you just need an empty dependency array so that the effect is only called once when the component is rendered.
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((users) => {
setUsers(users);
})
}, []);
try this , element get deleted and not refresh
import React, { useEffect, useState } from 'react';
const Example = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
setUsers(data);
// .then()
// .then(users => {
// setUsers(users);
// });
};
const deleteMe = index => {
setUsers(prevState => {
console.log(prevState);
return prevState.filter((e, i) => i !== index);
});
};
return (
<div>
{users.map((user, i) => {
return (
<div>
{' '}
{user.name}
<button onClick={() => deleteMe(i)}> Delete </button>
{/* <button onClick={}> Update </button> */}
</div>
);
})}
</div>
);
};
export default Example;

why isn't my router redirecting - interactive map?

I have this interactive map where if you click on a certain country it redirects you to a page for that specific country but Redirect isn't working for me and I don't understand what I'm doing wrong. This is the code:
onClick={() => {
const {NAME} = geo.properties;
if(NAME == 'Bulgaria')
{
<Redirect to='pages/services' />
}
}}
This is the full code of the .js file for my interactive map:
import React, { memo } from "react";
import {
ComposableMap,
Geographies,
Geography
} from "react-simple-maps";
import { Redirect } from "react-router-dom";
import { BrowserRouter } from 'react-router-dom';
...
const MapChart = ({ setTooltipContent }) => {
return (
<>
<ComposableMap data-tip="" projectionConfig={{ scale: 200 }}>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map(geo => (
<Geography
key={geo.rsmKey}
geography={geo}
onMouseEnter={() => {
const { NAME, POP_EST } = geo.properties;
setTooltipContent(`${NAME} — ${rounded(POP_EST)}`);
}}
onMouseLeave={() => {
setTooltipContent("");
}}
onClick={() => {
const { NAME } = geo.properties;
if (NAME === 'Bulgaria') {
this.setState({ redirect: true });
}
}}
...
/>
))
}
</Geographies>
</ComposableMap>
</>
);
};
export default memo(MapChart);
Issue
This isn't how JSX works. It isn't redirecting because the function can't return renderable JSX nor does it try to navigate. Everything that is rendered needs to be in the render function, or functional component return. This handler doesn't return anything.
Solution
You can either declaratively conditionally render a Redirect component, or imperatively issue a navigation action.
Using a Redirect component on condition.
// later in render/return
if (redirect) {
return <Redirect to='/pages/services' />;
}
return (
...
onClick={() => {
const { NAME } = geo.properties;
if (NAME === 'Bulgaria') {
setRedirect(true);
}
}}
...
);
MapChart
const MapChart = ({ setTooltipContent }) => {
const [redirect, setRedirect] = useState(false); // <-- add redirect state
return redirect ? ( // <-- conditional render redirect
<Redirect to="/pages/services" />
) : (
<ComposableMap data-tip="" projectionConfig={{ scale: 200 }}>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map((geo) => (
<Geography
...
onClick={() => {
const { NAME } = geo.properties;
if (NAME === "Bulgaria") {
setRedirect(true); // <-- set redirect state
}
}}
...
/>
))
}
</Geographies>
</ComposableMap>
);
};
Using history.replace.
onClick={() => {
const { NAME } = geo.properties;
if (NAME === 'Bulgaria') {
history.replace('/pages/services');
}
}};
MapChart
const MapChart = ({ history, setTooltipContent }) => { // <-- destructure history route prop
return (
<ComposableMap data-tip="" projectionConfig={{ scale: 200 }}>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map((geo) => (
<Geography
...
onClick={() => {
const { NAME } = geo.properties;
if (NAME === "Bulgaria") {
history.replace("/pages/services"); // <-- imperative navigation
}
}}
...
/>
))
}
</Geographies>
</ComposableMap>
);
};
Note: If MapChart isn't directly rendered by a Route component (i.e. it doesn't receive the route props) then you can use the useHistory react hook.
const history = useHistory();
And the navigation would be exactly the same from the click handler.

Why the state of a useState snaps back to false if I go back and forth on the Screen?

I've been trying to toggle favorite products and store their id and an isFav prop on a Firebase database. Then I use them to show the favorites on the FavoritesScreen.
If I go to the ProductDetailsScreen (where I toggle the favorite) I toggle it true/false with no problem.
Further if I then use the Bottom Tab Navigation to check the FavoritesScreen or the OrdersScreen etc and then go back to ProductDetailsScreen, nothing is changed.
But if (from the ProductDetailsScreen) I go back (to ProductsOverviewScreen) and then come back again on ProductDetailsScreen the state of isFav
snaps back to false! Nevertheless the id and isFav are saved on Firebase, but isFav is saved as false.
Note: I use a useState() hook...
One more thing that I don't understand happens when I try to log isFav.
I have two logs, one inside the toggleFavoriteHandler and one outside. When I first run the toggleFavoriteHandler, where I also have setIsFav(prevState => !prevState); I get:
Output:
outside: false
inside: false
outside: true
So I guess the first two false are from the initial state and then the true is from the above state-toggling. But why it gets it only outside true? Why actually the first two are false? I change the state to true before the log. I would expect it to immediately change to true and have them all true!
Then if I go back to ProductsOverviewScreen and then again to ProductDetailsScreen I get two logs from outside:
Output:
outside: true
outside: false
So it snaps back to its initial state! ?
I really do not understand how the work-flow goes. Are these logs normal?
Can anybody give some hints where the bug from going back and forth could be, please?
Thanks!
Here is the code:
ProductDetailsScreen.js
...
const ProductDetailScreen = (props) => {
const [ isFav, setIsFav ] = useState(false);
const dispatch = useDispatch();
const productId = props.navigation.getParam('productId');
const selectedProduct = useSelector((state) =>
state.products.availableProducts.find((prod) => prod.id === productId)
);
const toggleFavoriteHandler = useCallback(
async () => {
setError(null);
setIsFav((prevState) => !prevState);
console.log('isFav inside:', isFav); // On first click I get: false
try {
await dispatch(
productsActions.toggleFavorite(
productId,
isFav,
)
);
} catch (err) {
setError(err.message);
}
},
[ dispatch, productId, isFav setIsFav ]
);
console.log('isFav outside: ', isFav); // On first click I get: false true
return (
<ScrollView>
<View style={styles.icon}>
<TouchableOpacity style={styles.itemData} onPress={toggleFavoriteHandler}>
<MaterialIcons name={isFav ? 'favorite' : 'favorite-border'} size={23} color="red" />
</TouchableOpacity>
</View>
<Image style={styles.image} source={{ uri: selectedProduct.imageUrl }} />
{Platform.OS === 'android' ? (
<View style={styles.button}>
<CustomButton
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(selectedProduct))}
/>
</View>
) : (
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(selectedProduct))}
/>
</View>
)}
<Text style={styles.price}>€ {selectedProduct.price.toFixed(2)}</Text>
<Text style={styles.description}>{selectedProduct.description}</Text>
</ScrollView>
);
};
ProductDetailScreen.navigationOptions = ({ navigation }) => {
return {
headerTitle: navigation.getParam('productTitle'),
headerLeft: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="goBack"
iconName={Platform.OS === 'android' ? 'md-arrow-back' : 'ios-arrow-back'}
onPress={() => navigation.goBack()}
/>
</HeaderButtons>
),
headerRight: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="cart"
iconName={Platform.OS === 'android' ? 'md-cart' : 'ios-cart'}
onPress={() => navigation.navigate({ routeName: 'Cart' })}
/>
</HeaderButtons>
)
};
};
...styles
products.js/actions
export const toggleFavorite = (id, isFav) => {
return async (dispatch) => {
try {
// If it is a favorite, post it.
// Note it is initially false...
if (!isFav) {
const response = await fetch('https://ekthesi-7767c.firebaseio.com/favorites.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
isFav
})
});
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
const resData = await response.json();
// Note: No `name` property, that's why we use a `for_in` loop
// console.log('POST', JSON.stringify(resData));
dispatch({ type: TOGGLE_FAVORITE, productId: id });
} else if (isFav) {
// First get the key in order to delete it in second fetch(...).
const response = await fetch(`https://ekthesi-7767c.firebaseio.com/favorites.json`);
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
const resData = await response.json();
// Note: No `name` property, that's why we use a `for_in` loop
// console.log('fetch', JSON.stringify(resData));
for (const key in resData) {
console.log('resData[key].id', resData[key].id === id);
if (resData[key].id === id) {
await fetch(`https:app.firebaseio.com/favorites/${key}.json`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(
'Something went wrong.'
);
}
// console.log('fetch', JSON.stringify(resData));
dispatch({ type: TOGGLE_FAVORITE, productId: id });
}
}
}
} catch (err) {
// send to custom analytics server
throw err;
}
};
};
ProductsOverviewScreen.js
...
const ProductsOverviewScreen = (props) => {
const [ isLoading, setIsLoading ] = useState(false);
const [ error, setError ] = useState(); // error initially is undefined!
const [ isRefresing, setIsRefresing ] = useState(false);
const dispatch = useDispatch();
const categoryId = props.navigation.getParam('categoryId');
const products = useSelector((state) =>
state.products.availableProducts.filter((prod) => prod.categoryIds.indexOf(categoryId) >= 0)
);
const productId = props.navigation.getParam('productId');
const isFav = useSelector((state) => state.products.favoriteProducts.some((product) => product.id === productId));
const loadProducts = useCallback(
async () => {
setError(null);
setIsRefresing(true);
try {
await dispatch(productsActions.fetchProducts());
} catch (err) {
setError(err.message);
}
setIsRefresing(false);
},
[ dispatch, setIsLoading, setError ]
);
// loadProducts after focusing
useEffect(
() => {
const willFocusEvent = props.navigation.addListener('willFocus', loadProducts);
return () => willFocusEvent.remove();
},
[ loadProducts ]
);
// loadProducts initially...
useEffect(
() => {
setIsLoading(true);
loadProducts();
setIsLoading(false);
},
[ dispatch, loadProducts ]
);
const selectItemHandler = (id, title) => {
props.navigation.navigate('DetailScreen', {
productId: id,
productTitle: title,
isFav: isFav
});
};
if (error) {
return (
<View style={styles.centered}>
<Text>Something went wrong!</Text>
<Button title="Try again" onPress={loadProducts} color={Colours.chocolate} />
</View>
);
}
if (isLoading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colours.chocolate} />
</View>
);
}
if (!isLoading && products.length === 0) {
return (
<View style={styles.centered}>
<Text>No products yet!</Text>
</View>
);
}
return (
<FlatList
onRefresh={loadProducts}
refreshing={isRefresing}
data={products}
keyExtractor={(item) => item.id}
renderItem={(itemData) => (
<ProductItem
title={itemData.item.title}
image={itemData.item.imageUrl}
onSelect={() => selectItemHandler(itemData.item.id, itemData.item.title)}
>
{Platform.OS === 'android' ? (
<View style={styles.actions}>
<View>
<CustomButton
title="Details"
onPress={() => selectItemHandler(itemData.item.id, itemData.item.title)}
/>
</View>
<BoldText style={styles.price}>€ {itemData.item.price.toFixed(2)}</BoldText>
<View>
<CustomButton
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(itemData.item))}
/>
</View>
</View>
) : (
<View style={styles.actions}>
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Details"
onPress={() => selectItemHandler(itemData.item.id, itemData.item.title)}
/>
</View>
<BoldText style={styles.price}>€ {itemData.item.price.toFixed(2)}</BoldText>
<View style={styles.button}>
<Button
color={Colours.gr_brown_light}
title="Add to Cart"
onPress={() => dispatch(cartActions.addToCard(itemData.item))}
/>
</View>
</View>
)}
</ProductItem>
)}
/>
);
};
ProductsOverviewScreen.navigationOptions = (navData) => {
return {
headerTitle: navData.navigation.getParam('categoryTitle'),
headerRight: (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="cart"
iconName={Platform.OS === 'android' ? 'md-cart' : 'ios-cart'}
onPress={() => navData.navigation.navigate({ routeName: 'Cart' })}
/>
</HeaderButtons>
)
};
};
...styles
State updates are not synchronous. Considering the following:
const [isFav, setIsFav] = React.useState(true);
setIsFav(false); // state update here
console.log(isFav); // isFav hasn't updated yet and won't be `false` until next render
To get the latest state, you need to put your log in useEffect/useLayoutEffect.
From React docs,
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may batch or defer the update until later.
https://reactjs.org/docs/react-component.html#setstate
After the comment of #satya I gave it another try.
Now I get the state of isFav from the redux state.
Namely, I check if the current product is in the favoriteProducts array.
...imports
const ProductDetailScreen = (props) => {
const [ error, setError ] = useState(); // error initially is undefined!
const dispatch = useDispatch();
const productId = props.navigation.getParam('productId');
const selectedProduct = useSelector((state) =>
state.products.availableProducts.find((prod) => prod.id === productId)
);
// HERE !!! I get to see if current product is favorite!
const currentProductIsFavorite = useSelector((state) => state.products.favoriteProducts.some((product) => product.id === productId));
const toggleFavoriteHandler = useCallback(
async () => {
setError(null);
try {
await dispatch(productsActions.toggleFavorite(productId, currentProductIsFavorite));
} catch (err) {
setError(err.message);
}
},
[ dispatch, productId, currentProductIsFavorite, setIsFav ]
);
...
return (
<ScrollView>
<View style={styles.icon}>
<TouchableOpacity style={styles.itemData} onPress={toggleFavoriteHandler}>
<MaterialIcons name={currentProductIsFavorite ? 'favorite' : 'favorite-border'} size={23} color="red" />
</TouchableOpacity>
</View>
<Image style={styles.image} source={{ uri: selectedProduct.imageUrl }} />
...

React Native function bind parameters

I am trying to render a list which calls a function to render the rows, but how can I pass the paramater data to the function/handler?
The data is in json style.
I tried many things but I always get an error back, what am I doing wrong?
This the code:
componentDidMount() {
fetch('http://link/json.php', {
method: 'GET'
})
.then((response) => response.json())
.then((responseJson) => {
dataJson = responseJson;
this.setState({
preLoad: true
});
})
.catch((error) => {
});
}
renderRows(item){
if(item.type == 2){
return(
<ListItem>
<Thumbnail square size={80} source={{ uri: 'item.image' }} />
<Body>
<Text>{item.title}</Text>
<Text note>{item.desc}</Text>
</Body>
</ListItem>
)
}
}
renderMenu(){
if(this.state.preLoad){
return(
<List dataArray={dataJson}
renderRow={(item) =>
this.renderRows().bind(this,item)
}>
</List>
)
}else{
return(
<Body>{shimmerRows}</Body>
)
}
}
render() {
return (
<Container style={styles.container}>
{this.renderMenu()}
</Container>
);
}
It seems you just need to save your response to the state.
this.setState({
preLoad: true,
dataJson: responseJson,
});
And do the binding correctly.
<List dataArray={this.state.dataJson}
renderRow={(item) => this.renderRows.bind(this,item)}>
</List>

Categories

Resources