Unable to render react-native Component after fetching data from firebase - firebase

I am able to fetch data from the Firebase API. I am able to set the state into the data received (I can see in Console). But when I am unable to render a Component (passing props as fetched data). Here' my code:
class NewMeals extends Component {
constructor(props){
super(props);
this.state= {
data:''
}
}
async componentDidMount(){
try{
const res = await fetch('https://frego-cb5b5.firebaseio.com/recipes.json?auth=api_key');
if(!res.ok){
throw Error(res.statusText);
}
const json = await res.json();
this.setState({data:json});
}catch(err){
console.log(err);
}
}
renderItems=()=>{
const items = this.state.data;
const arr = Object.values(items);
arr.forEach(i=>{
return <HomeMeals name={i.title} time={i.time} serve={i.serve}/>
})
}
render(){
const {mainView, CardSection, heading, } = styles;
return(
<View style={mainView}>
<Text style={heading}>New Meals This Week</Text>
<ScrollView contentContainerStyle ={CardSection} horizontal={true} showsHorizontalScrollIndicator={false}>
{this.renderItems()}
</ScrollView>
</ View>
);
}
}
I expect the HomeMeals Components will render one by one with particular names from fetched data upon calling renderItems() function. But I am getting nothing.
Any suggestions?

A couple of points here.
Do more debugging (logs).
const items = this.state.data;
const arr = Object.values(items);
console.log("items and arr", items, arr, this.state);
What values do you see from the logs above? That should give you a hint.
This one below (renderItems) doesn't work, as it doesn't return elements (or anything) to render (as you were trying to):
renderItems=()=>{
arr.forEach(i=>{
return <HomeMeals name={i.title} time={i.time} serve={i.serve}/>
})
...
What you would want is return elements (array of elements) from renderItems function like this:
renderItems=()=>{
return arr.map(i=>{
return <HomeMeals name={i.title} time={i.time} serve={i.serve}/>
})
...
Two things you will notice: (1) I added return keyword to return whatever arr.map returns. And (2) the use of arr.map vs arr.forEach. Try to figure out the reason of this on your own; why arr.map works and arr.forEach doesn't

Related

Dynamically look up data at request time using GetServerSideProps

I'm using getServerSideProps to dynamically look up data at request time.
First my application will get data from pageNumber = 1.
export default function Index({ cmsData }: IndexProps) {
return (
// soem code
<button> Button Example</button>
)
}
export const getServerSideProps: GetServerSideProps = async ({ pageNumber }) => {
//Some code to get data from page number
return {
props: {
.....
},
}
}
I want to when click on Button Example, my app will fetch data again with pageNumber = 2.
How can I do that ?
Based on your comment you can do this:
Have a function to make API calls.
Get the query parameter from the context object to get the pageNumber. (If you always want to make call for the first page, you can directly pass 1).
Pass the props to the NextPage component (named Index) and initialise state using that.
In index component, you can also use add an event handler on button to make another API call and set state.
function makeAPICall(pageNumber : number) {
//Code to make API Call and return data
}
export default function Index({ cmsData }: IndexProps) {
const [data,setData] = useState(cmsData);
return (
// soem code
<button onclick={async() => {
let result = await makeAPICall(2);
setData(result.data);
}}> Button Example</button>
)
}
export const getServerSideProps: GetServerSideProps = async (context ) => {
let pageNumber = context.query.pageNumber;
const result = await makeAPICall(pageNumber);
let data= result.data;
//Some code to get data from page number
return {
props: {
cmsData : data;
},
}
}
Your first API call will always be made when you request the page from the browser because you are using GSSP. After that it will be triggered by your click.

Mobx observale value is null when used in useEffect hook

I am trying to get userdata from firestore based on uid obtained from authStatechanged in mobx. But when i try to access it in useEffect, it shows value is undefined but if i print its value in body it shows after slight delay. What should i fo to get that value in useeffect.
index.js
useEffect(() => {
setposts(props.posts);
authUser && setuser(authUser) ; //it shows authuser is null
}, []);
return (
<Layout>
{authUser && authUser.uid} //it gives correct value after delay
</Layout>
store.js
class Store {
#observable authUser = null
constructor(){
const result = firebase.auth().onAuthStateChanged(authUser => {
if(authUser){
this.authUser = authUser
}
})
}
}
firebase.auth() is asynchronous function, so when you component is rendered first time authUser is null. After some time this call gets resolved with user, then onAuthStateChanged is called and you set the user. But your useEffect is already got called and since you specified empty array [] as effect dependency it does not get called again.
You can use second effect like this, for example:
useEffect(() => {
authUser && setuser(authUser);
}, [authUser]);
Every time authUser changes this effect will be called.
But at the same time I am not quite understand why you need to setuser anyway, you can use it right away since it is stored inside your Store.

React Native/Firebase: Issue with FlatList Re-Rendering & Duplicate Keys

I am using React Native and Firebase Realtime Database. I am experiencing two problems with the FlatList component:
I am getting a lot of "duplicate key" errors whenever the list re-renders. I am not sure why I am getting this problem because I am setting the key of every item in my list as the snap.key value generated by Firebase, which is unique (and I have verified this in my logs).
The list sometimes does not re-render, even when I scroll up or down on it. This "sometimes" behavior is throwing me off, and I have not been able to debug it. I am using the ".on" method for getting my list from the Firebase Realtime Database.
This is the code that I am using:
export default class FlatListPage extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
makeRemoteRequest = () => {
var items = [];
DB.on('value', (snap) => {
this.getItems(snap, items);
items = items.reverse();
this.setState(
{data: items}
);
console.log(this.state.data); //checking key properties are unique
});
}
getItems = (snap, items) => {
snap.forEach((child) => {
items.push({
key: child.key,
status: child.val().status,
location: child.val().location,
});
});
}
componentWillMount(){
this.makeRemoteRequest();
}
render() {
return (
<View>
<FlatList
data={this.state.data}
renderItem={({item}) => <MyListItem item={item} />}
/>
</View>
);
}
}
You're receiving duplicate keys because you're not resetting the items array when receiving a new value event from firebase. This means you're simply re-adding the same items over and over again.
Update your makeRemoteRequest method to recreate the array each time you get a value event as follows:
makeRemoteRequest = () => {
DB.on('value', (snap) => {
var items = [];
this.getItems(snap, items);
items = items.reverse();
this.setState(
{data: items}
);
console.log(this.state.data); //checking key properties are unique
});
}
I'm not sure about number 2 - it might be that the above fixes it as a side effect.
instead of getting the whole list every time I suggest to get all elements .once and then use 'child_added' event and .limitToLast(1) to get only new items and add them to your array. Also, I noticed that you are pushing items to the array and then reversing it. You can use .unshift method that inserts items at the beginning of the array so you do not have to reverse it later.

Redux: dispatch function to store?

How is it possible to save a function as state in redux store?
Example:
I pass a function as parameter to a redux-thunk dispatcher function and i want to save this filter function in my redux store:
export const SET_FILTERED_USERS = 'SET_FILTERED_USERS';
export function setFilteredUsers(filter) {
return (dispatch, getState) => {
const allUsers = getState().users.allUsers;
const filteredUsers = allUsers.filter(filter);
dispatch({
type: SET_FILTERED_USERS,
data: {
filteredUsers,
filter
}
});
const activeUser = getState().users.activeUser;
if (activeUser && !_.isEmpty(filteredUsers) && filteredUsers.indexOf(activeUser._id) === -1) {
dispatch(setActiveUser(filteredUsers[0]));
} else {
dispatch(setActiveUser(allUsers[0]));
}
}
}
In ReduxDevTools i can see, "filter" is not dispatched and saved in store. Is there a way to do this?
Thanks
Update: my shortend reducer:
import {
SET_FILTERED_USERS
} from '../actions/users';
import assign from 'object-assign';
export const initialState = {
filter: null,
filteredUsers: null
};
export default function (state = initialState, action = {}) {
const {data, type} = action;
switch (type) {
case SET_FILTERED_USERS:
return assign({}, state, {
filteredUsers: data.filteredUsers,
filter: data.filter
});
default:
return state;
}
}
As Sebastian Daniel said, don't do that. Per the Redux FAQ, that breaks things like time-travel debugging, and is not how Redux is intended to be used: Can I put functions, promises, or other non-serializable items in my store state?
What you could consider as an alternative is storing some kind of description of the filtering you want. As a sort of relevant example, in my current prototype, I'm creating a lookup table of dialog classes that I might want to show, and when I need to show one, I dispatch an action containing the name of the dialog type. My dialog manager class pulls that "dialogType" field out of state, uses that to look up the correct dialog component class, and renders it.
The other question, really, is why you actually want to store a function in your state in the first place. I see what you're trying to do there, but not actually what you're hoping to accomplish with it.

React redux separation of concerns

I'm trying to build a simple app to view photos posted from nasa's picture of the day service (https://api.nasa.gov/api.html#apod). Currently watching for keypresses, and then changing the date (and asynchronously the picture) based on the keypress being an arrow left, up, right, or down. These would correspondingly change the date represented by a week or a day (imagine moving across a calendar one square at a time).
What I'm having trouble with is this: I've created an async action creator to fetch the next potential date - however I need to know the current state of the application and the keypress to retrieve the new date. Is there a way to encapsulate this into the action creator? Or should I put the application state where the exported action creator is called in the application so I can keep my action creator unaware of the state of the application? I've tried to do this by binding the keydown function in componentDidMount for the top level Component, but the binding to the application store doesn't seem to reflect the changes that happen in the reducer.
The async logic relying on redux-thunk middleware and q:
// This function needs to know the current state of the application
// I don't seem to be able to pass in a valid representation of the current state
function goGetAPIUrl(date) {
...
}
function getAsync(date) {
return function (dispatch) {
return goGetAPIUrl(date).then(
val => dispatch(gotURL(val)),
error => dispatch(apologize(error))
);
};
}
export default function eventuallyGetAsync(event, date) {
if(event.which == 37...) {
return getAsync(date);
} else {
return {
type: "NOACTION"
}
}
}
Here's the top level binding to the gridAppState, and other stuff that happens at top level that may be relevant that I don't quite understand.
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
actions.eventuallyGetAsync(event, gridAppState.date);
});
}
render() {
const { gridAppState, actions } = this.props;
return (
<GridApp gridAppState={gridAppState} actions={actions} />
);
}
}
App.propTypes = {
actions: PropTypes.object.isRequired,
gridAppState: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
gridAppState: state.gridAppState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(GridActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
I've validated that the correctly modified date object is getting to the reducer - however the gridAppState seems stuck at my initial date that is loaded.
What is the right way to approach async logic in redux that relies on attaching event handlers and current application state? Is there a right way to do all three?
You should handle the event in your component and call the correct action depending on the key pressed.
So when you dispatch an async action you can do something like
export default function getNextPhoto(currentDate) {
return (dispatch) => {
const newDate = calculateNewDate(currentDate);
dispatch(requestNewPhoto(newDate));
return photosService.getPhotoOfDate(newDate)
.then((response) => {
dispatch(newPhotoReceived(response.photoURL);
});
};
}
You should handle the keypress event on the component and just dispatch your action when you know you need to fetch a new photo.
Your App would look like
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
if (event.which == 37) {
actions.getNextPhoto(gridAppState.date);
} else if (...) {
actions.getPrevPhoto(gridAppState.date);
}
// etc
});
}
}
By the way you re still missing your reducers that update your state in the Redux Store.

Resources