In FeedScreen, I try to get "userData" from redux store and feed it into a Flatlist.
But nothing is shown. I check console.log with Chrom dev tool, I can see the element in "userData" but .length is zero and "userData[0]" is undefined.
If I refreshed the app with expo, the flatlist is shown.
I think it is related to timing as "userData" is fetched from firebase store and FeedScreen is nested in MainScreen Tab Navigator.
How to fix it?
the code shown below is extracted only from the relevant section, and I log the redux store in MainScreen.js. The redux reducer is working as expected.
FeedScreen.js
export default function FeedScreen(props,{ navigation }) {
const [useData2, setUserData2] = useState({});
const userData = useSelector(state=> state.userState.userdata)
console.log(userData)
console.log(userData.length)
}
MainSceen.js
....
let tempPostArr2=[];
let subUserPost = followingList.map(uid => {
const q = query(collection(db, "post", uid, "userPosts"), orderBy("timestamp", "asc"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
let tempPosts = querySnapshot.docs.map((doc) => {
const data = doc.data();
const postId = doc.id;
return { postId, ...data, uid }
})
tempPostArr2.push(tempPosts)
})
})
dispatch(fetchAllUsersPost(tempPostArr2))
return (
<Tab.Navigator initialRouteName='Feed' labeled={false}>
<Tab.Screen name="Feed" component={FeedScreen}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="home" color={color} size={size} />
),
}} />
</Tab.Navigator>
)
Minor point about console.log: if you feed it an object directly like you've done, it tends to be "live" if you try to open it to look at object contents. So if you want to truly know what the object was at the time of logging, console.log(JSON.stringify(x)) is necessary.
I found the answer, onSnapshot provides a callbackfunction. The dispatcher runs before the callback function runs. I make a state change in the callback function and use useEffect to relaunch the dispatcher.
reference
Related
Hello I am working on a pop up window where the user can filter a table of data.
The filter is selected using checkboxes.
My issue:
On page load there is a useEffect that changes every checkbox to false. This is based on the props coming in from the API.
I'd like on page load (and when the filter opens) that the checkbox state is stored based on what the user has selected previously in their session
code:
Filter component*
[...]
import FilterSection from "../FilterSection";
const Filter = ({
open,
handleClose,
setFilterOptions,
[..]
roomNumbers,
}) => {
const [roomValue, setRoomValue] = React.useState();
const [roomListProp, setRoomListProp] = React.useState(); // e.g. [["roomone", false], ["roomtwo", true]];
const sendRoomFilterData = (checkedRoomsFilterData) => {
setRoomValue(checkedRoomsFilterData);
};
const setCheckboxListPropRoom = (data) => {
setRoomListProp(data);
};
// extract, convert to an object and pass back down? or set local storage and get
// local storage and pass back down so that we can get it later?
const convertToLocalStorageFilterObject = (roomData) => { // []
if (roomData !== undefined) {
const checkedRooms = roomData.reduce((a, curval) => ({ ...a, [curval[0]]: curval[1] }), {});
localStorage.setItem("preserved", JSON.stringify(checkedRooms)); // sets in local storage but values get wiped on page load.
}
};
React.useEffect(() => {
const preservedFilterState = convertToLocalStorageFilterObject(roomListProp);
}, [roomListProp]);
const applyFilters = () => {
setFilterOptions([roomValue]);
handleClose();
};
const classes = CurrentBookingStyle();
return (
<Dialog
fullWidth
maxWidth="sm"
open={open}
onClose={() => handleClose(false)}
>
<DialogTitle>Filter By:</DialogTitle>
<DialogContent className={classes.margin}>
<FilterSection
filterName="Room number:"
filterData={roomNumbers}
setFilterOptions={sendRoomFilterData}
setCheckboxListProp={setCheckboxListPropRoom}
/>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={applyFilters}>
Apply Filters
</Button>
</DialogActions>
</Dialog>
);
};
Filter Section used in Filter
import {
TableCell,
Typography,
FormControlLabel,
Checkbox,
FormGroup,
} from "#material-ui/core";
const FilterSection = ({
filterData, filterName, setFilterOptions, setCheckboxListProp
}) => {
const [checkboxValue, setCheckboxValue] = React.useState({});
const [checkboxFilterList, setCheckboxFilterList] = React.useState([]);
const handleCheckboxChange = (event) => {
setCheckboxValue({
...checkboxValue,
[event.target.name]: event.target.checked, // room1: true
});
};
const = () => filterData // ["room1" "room2"]; comes from API
.filter((val) => !Object.keys(checkboxValue).includes(val))
.reduce((acc, currval) => ({
...acc, [currval]: false, // converts array to object and sets values to false
}), checkboxValue);
React.useEffect(() => {
const transformedCheckboxListItems = Object.entries(convertToObject());
setCheckboxFilterList(transformedCheckboxListItems);
setFilterOptions(transformedCheckboxListItems.filter(([, val]) => val).map(([key]) => key));
setCheckboxListProp(transformedCheckboxListItems);
}, [checkboxValue]);
return (
<>
<Typography style={{ fontWeight: "bold" }}>{filterName}</Typography>
<FormGroup row>
{checkboxFilterList.map(([key, val]) => (
<TableCell style={{ border: 0 }}>
<FormControlLabel
control={(
<Checkbox
checked={val}
onChange={handleCheckboxChange}
name={key}
color="primary"
/>
)}
label={key}
/>
</TableCell>
))}
</FormGroup>
</>
);
};
What i have tried:
I have created a reusable component called "FilterSection" which takes takes data from the API "filterData" and transforms it from an array to an object to set the initial state for the filter checkboxes.
On page load of the filter I would like the checkboxes to be true or false depending on what the user has selected, however this does not work as the convertToObject function in my FilterSection component converts everything to false again on page load. I want to be able to change this but not sure how? - with a conditional?
I have tried to do this by sending up the state for the selected checkboxes to the Filter component then setting the local storage, then the next step would be to get the local storage data and somehow use this to set the state before / after page load. Unsure how to go about this.
Thanks in advance
I am not sure if I understand it correctly, but let me have a go:
I have no idea what convertToObject does, but I assume it extracts the saved filters from localStorage and ... updates the filter value that has just been changed?
Each time the FilterSection renders for the first time, checkboxValue state is being initialised and an useEffect runs setCheckboxListProp, which clears the options, right?
If this is your problem, try running setCheckboxListProp directly in the handleCheckboxChange callback rather than in an useEffect. This will ensure it runs ONLY after the value is changed by manual action and not when the checkboxValue state is initialised.
I solved my problem by moving this line:
const [checkboxValue, setCheckboxValue] = React.useState({});
outside of the component it was in because every time the component re-rendered it ran the function (convertToObject() which reset each checkbox to false
by moving the state for the checkboxes up three layers to the parent component, the state never got refreshed when the or component pop up closed. Now the checkbox data persists which is the result I wanted.
:D
To start with I'm a beginner. Any help would be appreciated.
So I'm getting my data from mongoDB atlas using node+express API. I'm successfull at getting the array to show up in console log using following code.
const [product, setProduct] = useState();
const url = "http://localhost:5000/api/items";
useEffect(() => {
axios.get(url).then((res) => {
setProduct(res.data);
// setProduct(
// JSON.stringify({
// title: setProduct.title,
// price: setProduct.price,
// image: setProduct.image,
// details: setProduct.details,
// })
// );
})
}, [url])
console.log(product)
The console log displays the array properly as collection named 'items' with content of arrays. As you can see I tried to stringify the response as the response returns JSON but again I didn't know how to map Following is the code where I tried to map the contents like id, name etc as props to component.
<div>
{product.map((product) => {
<Product name={product.title} />
})}
</div>
When I do this I get error that the map is not a function. I don't know what I'm doing wrong here. I know I'm supposed to use redux or reducer/context here but I want to get this to work before updating it with those.
[![Response from res.data][1]][1]
[1]: https://i.stack.imgur.com/auxvl.png
you didnt get yours products.
As we can see from screenshot
res.data equal to object with one property items:
res.data= {items: []}
and we need to take/access to these items
use this: setProducts(res?.data?.items || [])
const [products, setProducts] = useState();
useEffect(() => {
axios.get(url).then((res) => {
setProducts(res?.data?.items || []);
})
}, [url])
<div>
{products?.map((product) => {
<Product name={product.title} />
})}
</div>
On the first render the value for types will be undefined ( on sync code execution ), try using it as
<div>
{product?.map((product) => {
<Product name={product.name} />
})}
</div>
? will make sure to run map once value for types is there ( also make sure it is mappable ( is an array ))
Hello I am building photo gallery where I would like to add feature that user will be able filter by Category. I tried some solutions but there are two bugs that I am not able to fix. First is that if I go to the GalleryPage (using Swtich) it does NOT render dynamically added buttons from FilterButton component. I have to click one more time on the link and then it DOES render the buttons. I dont know why it does not work on the first render.
Other issue is that I am able to filter by category but it causes the infinite loop in the useEffect and I dont know how to fix it.
I have got GalleryPage component where I am getting data from API and parsing the data for using later in other components. Here it seems that is all working fine.
const GalleryPage = () => {
const url = 'someurl';
const [data, setData] = useState([]);
const [categoryList, setCategoryList] = useState([]);
const [category, setCategory] = useState('All');
useEffect(() => {
const fetchData = async () => {
const result = await axios(url,);
setData(result.data)
result.data.forEach(item => {
imageUrl.push(item.image)
if (categoryList.indexOf(item.group) === -1) {
categoryList.push(item.group)
}
})
}
fetchData();
}, [])
return (
<FilterButton setCategory={setCategory} categoryList={categoryList}/>
<Gallery data={data} category={category}/>
)
}
If I go to the GalleryPage the h3 and 'All' button is rendered. But I have to click on the link one more time to render the buttons inside the map function:
const FilterButton = ({setCategory, categoryList}) => {
return(
<h3>Gallery</h3>
<button onClick={()=> setCategory('All')}>All</button>
{categoryList.map(item => (
<button key={item} onClick={()=> setCategory(item)}>{item}</button>
))}
)
};
export default FilterButton;
And here I am not able to fix the infinite loop:
const Gallery = ({data, category}) => {
const [photos, setPhotos] = useState([]);
useEffect(()=>{
let temp = []
if (category === 'All'){
setPhotos(data)
}else{
data.map(item => {
temp.push(item)
})
}
setPhotos(temp)
})
return(
photos.map((item =>
<img key={item.id} src={item.image}/>
))
)
};
export default Gallery;
If I add empty array to the useEffect it does not work at all. Also I am using styled components and framer motion but it should not have affect on this I hope.
First, I see that you're never setting your state for categoryList.
After modifying categoryList, you should call setCategoryList() with the new category list. This way, the state variable will be 'remembered' when the component is re-rendered.
You can read about the useState hook here.
Additionally, for the useEffect hook, the 'empty array' you pass in at the end is actually an array of variables to 'watch' for changes. If you pass an empty array, the useEffect will only run once, at the first page load. However, you can pass in something like [category] so that the useEffect is only called when the category variable is modified, which I persume is what you want to do.
I am for the first time learning about state, and followed a simple tutorial to create a react native app. The tutorial did not cover using firebase, so this is what I've pieced together. It "works", but does not pull the database data on the first render. I know it's because of the delay in time it takes to grab the data vs my app rendering. But I don't know how I should move logic around to fix it. I feel like I should be using the .then somehow? Or am I doing all of this completely wrong...
import {db} from '../../src/config.js';
let initialMessages = [];
db.ref().once('value', (snapshot) =>{
snapshot.forEach((child)=>{
child.forEach(function(childChildSnapshot) {
initialMessages.push({
id: childChildSnapshot.key,
title: childChildSnapshot.val().title,
})
})
})
})
.then()
.catch((error) => {console.error('Error:', error)});
function MessagesScreen(props) {
const [messages, setMessages] = useState(initialMessages);
return (
<Screens>
<View style={styles.wholeThing}>
<FlatList
data={messages}
keyExtractor={(messages) => messages.id.toString()}
renderItem={({ item }) => (
<Card
title={item.title}
onPress={() => console.log("hi")}
/>
)}
ItemSeparatorComponent={ListItemSeparator}
contentContainerStyle={styles.messagesList}
refreshing={refreshing}
onRefresh={}
/>
</View>
</Screens>
);
}
export default MessagesScreen;
By the time you pass initialMessages to the state hook (as its initial value), the initialMessages.push(...) hasn't been called yet.
Instead, you need to call setMessages when the data has loaded:
db.ref().once('value', (snapshot) =>{
snapshot.forEach((child)=>{
child.forEach(function(childChildSnapshot) {
initialMessages.push({
id: childChildSnapshot.key,
title: childChildSnapshot.val().title,
})
})
setMessages(initialMessages);
})
})
Calling setMessages will then rerender the (affected) UI, and that will show the messages.
This of course means that you'll need to pull the useState hook out of MessagesScreen, so that it's also visible in the location where you now call setMessages.
Edit: I don't understand the reason for downvotes, this was a good question and no other questions on this site solved my issue. I simply preloaded the data to solve my issue but that still doesn't solve the problem without using functional components.
I'm trying to pass users last message into the ListItem subtitle prop but I can't seem to find a way to return the value from the promise/then call. It's returning a promise instead of the value which gives me a "failed prop type". I thought about using a state but then I don't think I could call the function inside the ListItem component anymore.
getMsg = id => {
const m = fireStoreDB
.getUserLastMessage(fireStoreDB.getUID, id)
.then(msg => {
return msg;
});
return m;
};
renderItem = ({ item }) => (
<ListItem
onPress={() => {
this.props.navigation.navigate('Chat', {
userTo: item.id,
UserToUsername: item.username
});
}}
title={item.username}
subtitle={this.getMsg(item.id)} // failed prop type
bottomDivider
chevron
/>
);
You could only do it that way if ListItem expected to see a promise for its subtitle property, which I'm guessing it doesn't. ;-) (Guessing because I haven't played with React Native yet. React, but not React Native.)
Instead, the component will need to have two states:
The subtitle isn't loaded yet
The subtitle is loaded
...and render each of those states. If you don't want the component to have state, then you need to handle the async query in the parent component and only render this component when you have the information it needs.
If the 'last message' is something specific to only the ListItem component and not something you have on hand already, you might want to let the list item make the network request on its own. I would move the function inside ListItem. You'll need to set up some state to hold this value and possibly do some conditional rendering. Then you'll need to call this function when the component is mounted. I'm assuming you're using functional components, so useEffect() should help you out here:
//put this is a library of custom hooks you may want to use
// this in other places
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => (isMounted.current = false);
}, []);
return isMounted;
};
const ListItem = ({
title,
bottomDivider,
chevron,
onPress,
id, //hae to pass id to ListItem
}) => {
const [lastMessage, setLastMessage] = useState(null);
const isMounted = useIsMounted();
React.useEffect(() => {
async function get() {
const m = await fireStoreDB.getUserLastMessage(
fireStoreDB.getUID,
id
);
//before setting state check if component is still mounted
if (isMounted.current) {
setLastMessage(m);
}
}
get();
}, [id, isMounted]);
return lastMessage ? <Text>DO SOMETHING</Text> : null;
};
I fixed the issue by using that promise method inside another promise method that I had on componentDidMount and added user's last message as an extra field for all users. That way I have all users info in one state to populate the ListItem.
componentDidMount() {
fireStoreDB
.getAllUsersExceptCurrent()
.then(users =>
Promise.all(
users.map(({ id, username }) =>
fireStoreDB
.getUserLastMessage(fireStoreDB.getUID, id)
.then(message => ({ id, username, message }))
)
)
)
.then(usersInfo => {
this.setState({ usersInfo });
});
}
renderItem = ({ item }) => (
<ListItem
onPress={() => {
this.props.navigation.navigate('Chat', {
userTo: item.id,
UserToUsername: item.username
});
}}
title={item.username}
subtitle={item.message}
bottomDivider
chevron
/>
);