How to get only specific fields of data from firestore - reactjs

I need to get all the brand names of gas stations in my database and display those names in React component, but I am not sure what query should I use and how I am supposed to store this data.
I have already tried to get the needed information from Firestore and to store it into redux, but it is not working. Should I use separate reducers?
export const setStations = stations => ({
type: actionTypes.SET_STATIONS,
stations
});
export const setStationsNames = stationsNames => ({
type: actionTypes.SET_STATIONS_NAMES,
stationsNames
});
export const startSetStations = () => {
return dispatch => {
return db
.collection('stations')
.get()
.then(snapshot => {
const stations = [];
const stationsNames = [];
snapshot.docs.forEach(doc => {
stations.push(doc.data());
stationsNames.push(doc.data().brand);
});
dispatch(setStationsNames(stationsNames));
dispatch(setStations(stations));
});
};
};
{props.stations.map((station, i) => {
return (
<li key={i} className="station">
<h3 className="station__text">{station.brand}</h3>
</li>
);
})}
Reducer
export default (state = stationsReducerDefaultState, action) => {
switch (action.type) {
case actionTypes.SET_STATIONS:
return action.stations;
default:
return state;
}
};
My firebase database

Related

Components are not rendering while using .map() on array of objects in React

The component renders as it should at first. After refreshing the page then does not render. Again if I change the key to different value it works and after refreshing page its gone again.
My component
const Book = ({ membersDetail }) => {
const history = useHistory();
const takeToBookNav = () => {
history.push("/recordbook/booknav");
};
return (
<div className="book">
{membersDetail.map(({ name, phoneNumber }) => (
<div className="select-members">
<div key={phoneNumber} classname="member" onClick={takeToBookNav}>{name}</div>
</div>
))}
</div>
);
};
const mapStateToProps = createStructuredSelector({
membersDetail: selectMembersList,
});
export default connect(mapStateToProps)(Book);
MEMBER_REDUCER
import MemberActionTypes from "./members-action.type";
const ININTIAL_STATE = {
members: null,
errorMessage: "",
isGettingMembers: false,
isAddingMembers: false,
isRemovingMembers: false,
};
const memberReducer = (state = ININTIAL_STATE, action) => {
switch (action.type) {
case MemberActionTypes.GET_MEMBER_START:
return {
...state,
isGettingMembers: true,
};
case MemberActionTypes.GET_MEMBER_SUCESS:
return {
...state,
members: action.payload,
errorMessage: "",
};
case MemberActionTypes.GET_MEMBER_FAILURE:
return {
...state,
errorMessage: action.payload,
isAddingMembers: false,
isRemovingMembers: false,
isGettingMembers: false,
};
default:
return state;
}
};
export default memberReducer;
SAGAs
export function* gettingMembers() {
try {
const membersList = yield getMembers();
yield put(getMembersSucess(membersList));
} catch (error) {
yield put(getMembersFailure(error));
}
}
export function* getInintialMembersList() {
yield put(getMembersStart());
}
export function* onSigningInSucess() {
yield takeLatest(
UserActionTypes.GOOGLE_SIGNIN_SUCESS,
getInintialMembersList
);
}
export function* onGettingMembers() {
yield takeLatest(MemberActionTypes.GET_MEMBER_START, gettingMembers);
}
FIREBASE
//members data to firebase
const database = firebase.database(); //gets the database
const membersRef = database.ref("members");
export const createMember = (memberCredentials) => {
//pushing the object to the reference members
membersRef.push(memberCredentials);
};
//get array of members from firebase
export const getMembers = () => {
const membersList = [];
membersRef.on("value", function (snapshot) {
snapshot.forEach(function (childSnapshot) {
const item = childSnapshot.val();
item.key = childSnapshot.key;
membersList.push(item);
});
console.log(membersList);
});
return membersList;
};
firebase DATABASE is like
myproject-default-rtdb
| -members
|-MaApwTgulOH0bl1zSH
| -name: "Jhon Doe"
|-phoneNumber: "984215789"
Thank you for Helping.
The code const membersList = yield getMembers(); does not make any sense since getMembers in your firebase code is not a generator.
Since the value event triggers whenever something changes you should have that event listener dispatch a redux action to update the redux state and your saga can just be deleted. Your saga looks like code from a classic api where you fetch data but firebase doesn't work like that.
In your component you can start and stop listening to updates from members but you won't fetch data like with classic REST api.
So your getMembers can just be something like the following listenToMembers:
//your firebase code
let listening = false;
const onNewMembers = (() => {
let allMembers = [];
return (snapshot) => {
//concat new members to existing members
allMembers = allMembers.concat(
snapshot.map(function (childSnapshot) {
const item = childSnapshot.val();
item.key = childSnapshot.key;
return item;
})
);
//store is your redux store
store.dispatch(getMembersSucess(allMembers));
};
})(); //IIFE that has all members
export const listenToMembers = () => {
//check if we are already listening to members
if (listening) {
return;
}
listening = true;
membersRef.on('value', onNewMembers);
};
//you can call this function if you're no longer interested in
// changes in members
export const stopListenToMembers = () => {
membersRef.off('value', onNewMembers);
};
I have not worked with firebase much but I think you should set allMembers first when your app starts or use onSnapShot instead of .on('value'.

React-Redux Functional Component Multiple Renders

I created a very simple React-Redux App and fetching Users and Posts from https://jsonplaceholder.typicode.com/
In my components I am logging Users and Posts data into the console. As far as I see, in the network tab there is one request for Users and 10 requests for Posts. That's correct but in the console, I see 10 Posts requests for each User.
Does it mean ReactJS renders the component 100 times? What is my mistake in this code?
Any help will be greatly appreciated!
My code and codepen link are below
Please check the code in codepen
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapStateToProps = state => {
return { users: state.users, posts: state.posts };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, [getUsers]);
const renderUsers = () =>
props.users.map(user => {
return (
<div>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts' , props.posts);
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [getPosts, userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const PostsContainer = connect(mapStateToProps, mapDispatchToProps)(Posts);
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Does it mean ReactJS renders the component 100 times? What is my mistake in this code?
you have a UserContainer, that renders and requests for users;
once fetched users, you have an update state. UserContainer rerenders, and now you have 10 PostContainers;
each PostContainer makes a request to fetch posts, 10 on total;
it results in 10 state updates. UserContainer rerenders 10 times, and each PostContainer rerenders 10 times;
The component doesn't renders 100 times, each PostContainer renders the initial mount then rerenders 10 times. since there are 10 PostContainers and each rerenders 10 times that's why you might think that renders 100 times.
you have some issues. the dependency issue, which was pointed out is the first. getUsers useEffect should have an empty dependency, and userId useEffect, should depend on userId.
to solve the 10 rerenders on UserContainer due to posts, you need to have a different mapStateToProps to each. for UserContainer you will map only users, otherwise you will get 10 updates due to 10 posts requests:
const mapUserStateToProps = state => {
return { users: state.users };
};
with that it solves UserContainer 10 rerenders.
now about PostContainer there is something that needs to be fixed first, your reducer. your reducer replaces last posts with the current call. in the end you will have only the posts that arrived last, not all posts. to fix that you need to spread your state.
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
eventually, if in your project you could have a repeated request to same userId than it would be necessary to have some validation for not adding the same posts again
now it leads us to mapping props to PostContainer. you would need to have a filter on posts based on userId. mapStateToProps takes props as second argument, which enables us to accomplish that:
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
this looks the end to solve the issue, but each PostContainer still rerenders 10 times. why does this happens since posts will be the same? that happens because filter will return a new array reference, no matter if its content didn't change.
to solve this issue you can use React.memo. you need to provide the component and a equality function to memo. to compare an array of objects there are some solutions, also few libs that provide some deepEqual function. here I use JSON.stringify to compare, but you are free to use some other one:
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
you would validate also other props that could change but that's not the case
now apply React.memo to posts:
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
After all that applied, UserContainer will rerender one once, and each PostContainer will rerender only once as well.
here follows link with working solution:
https://codepen.io/rbuzatto/pen/BaLYmNK?editors=0010
final code:
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapUserStateToProps = state => {
return { users: state.users };
};
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, []);
const renderUsers = () =>
props.users.map(user => {
return (
<div key={user.id}>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapUserStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts');
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
useEffect() renders the component every time something is changed in the dependencies you provided.
Ideally, you should change your components to re-render only when something changes in props. getUser and getPost change on each render. So, it is better to change it to monitor users and posts from state.
In Users:
const { users, getUsers } = props;
useEffect(() => {
getUsers();
}, []); -- Leaving this empty makes it load only on mount.
In Posts:
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);

Redux dispatch data from component

How can I use the mapdispatchtoprops function correctly to dispatch to reducer? First, I get data from the server and want to send this data to the reducer. firebaseChatData function cannot be transferred to the mapdispatchtoprops because it is inside the component
Messages.js
const MessageUiBody = ( { messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// console.log(firebaseChat)
dispatch(firebaseChatAction(firebaseChat))
});
};
}, []);
return(
<div> // code </div>
);
};
//Action
const firebaseChatAction = (firebaseChat) => ({
type: 'FIREBASE_MESSAGE',
firebaseChat
});
const mapDispatchToProps = (dispatch) => {
return {
data : () => {
dispatch(firebaseChatData())
}
}
};
export default connect(null, mapDispatchToProps)(MessageUiBody)
Reducer
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
state.data.messages.push(action.firebaseChat);
return {
...state
};
default:
return state
}
}
You'll have to change your code, because you're defining data as the prop function that will dispatch your action:
const mapDispatchToProps = dispatch => {
return {
data: (result) => dispatch(firebaseChatAction(result)),
}
}
After that change the line after the console log in your promise and use the data prop that you defined in your mapDispatch function:
const MessageUiBody = ( { data, messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// here you call the data that will dispatch the firebaseChatAction
data(firebaseChat)
});
};
}, []);
return(
<div> // code </div>
);
};
Also is worth to notice that you don't have to push items in your state, you can't mutate the current state, so always try to generate new items instead of modifying the existing one, something like this:
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
return {
...state,
data: {
...state.data,
messages: [...state.data.messages, action.firebaseChat]
}
};
default:
return state
}
}
With the spread operator you are returning a new array that contains the original state.data.messages array and will add the firebaseChat item as well.

Returning Promises instead of data

I've had a great deal of frustration lately when I was trying to use axios to fetch data from a localhost withing a React/Redux bookstore project.The reason why I used axios is to connect the App to a rails api(backend) and try to update the store from the DB whenever it is needed(namely the list of books). However, when I try to pass the response.data(array of books) of the api axios call to the Action Creator I get a Promise in the React component.
Book List Component
import React from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import Book from '../components/book';
import { removeBookAction, getBooksAction } from '../actions/index';
class BookList extends React.Component {
UNSAFE_componentWillMount() {
this.props.getBooks();
}
render() {
const { books, handleRemoveBook } = this.props;
console.log(books) // prints PromiseĀ {<resolved>: Array(12)} :(
return (
<tbody>
{books.length > 0 ? books.map((book) => (
<Book key={book.id} item={book} handleRemoval={handleRemoveBook} />
))
: (
<tr>
<td>Empty List</td>
</tr>
)}
</tbody>
);
}
}
const displayableBooks = (books, theFilter) => {
//console.log(books)
if (theFilter === 'All') {
return books;
}
return books.filter(item => item.category === theFilter);
};
// inject store state as props to Booklist component
const mapStateToProps = (state) => {
return {
books: displayableBooks(state.books, state.filter),
};
};
const mapDispatchToProps = (dispatch) => ({
handleRemoveBook: (book) => {
dispatch(removeBookAction(book));
},
getBooks: () => axios.get('api/v1/books').then(response => {
dispatch(getBooksAction(response.data));
}),
});
BookList = connect(mapStateToProps, mapDispatchToProps)(BookList);
export default BookList;
Books Action creators
const creatBookAction = (book) => {
return {
type: 'CREATE_BOOK',
book,
}
};
const removeBookAction = (book) => ({
type: 'REMOVE_BOOK',
id: book.id,
});
const getBooksAction = (books) => {
return {
type: 'GET_BOOKS',
books,
};
}
const changeFilterAction = (filter) => ({
type: 'CHANGE_FILTER',
filter,
});
export { creatBookAction, removeBookAction, changeFilterAction, getBooksAction };
Books Reducer
const CREATE_BOOK = 'CREATE_BOOK';
const REMOVE_BOOK = 'REMOVE_BOOK';
const GET_BOOKS = 'GET_BOOKS';
const booksReducer = async (state = [], action) => {
switch (action.type) {
case CREATE_BOOK:
return [
...state,
{ ...action.book },
];
case REMOVE_BOOK:
const index = state.findIndex((value) => value.id === action.id);
return state.slice(0, index).concat(state.slice(index + 1));
case GET_BOOKS:
return action.books;
default:
return state;
}
};
export default booksReducer;
As you can see the code above, I am trying to create some sort of synchronization between the Redux store and the DB, but I am stuck at the fist step (i.e getting the data). I am a beginner with React/Redux and axios, so please consider explaining other alternatives if my approach (which is a combination of other approaches mentioned in tutorials) is inefficient of impossible to apply. Thanks :)
I realized that I made a grave mistake. The reason why I am getting a promise instead of the actual data is because of my books reducer. I made it an asynchronous function which I suppose will inevitably return a promise.
You should use a middleware to make async call action like redux thunk
const mapDispatchToProps = (dispatch) => ({
handleRemoveBook: (book) => {
dispatch(removeBookAction(book));
},
getBooks: () => dispatch(fetchBooksAction());
});
Here your async action
const fetchBooksAction = () => (dispatch) => {
axios.get('api/v1/books').then(response => {
dispatch(getBooksAction(response.data);
});
}

Redux Dom Not refreshing

I am updating my redux state, and the state doesn't seem to be getting mutated, however the DOM is still not refreshing.
//update filters for events
setFilters = (name) => async () => {
const {onSetActiveEventTypes, authUser} = this.props;
let array = this.props.activeEventTypes
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
await Promise.resolve(onSetActiveEventTypes(array));
}
render() {
return <Accordion title="Filters" collapsed>
{
(this.props.eventTypes && this.props.activeEventTypes ?
<EventFilter eventTypes={this.props.eventTypes} activeEventTypes={this.props.activeEventTypes} action={this.setFilters}/>
: '')
}
</Accordion>
}
const mapStateToProps = (state) => ({
eventTypes: state.eventsState.eventTypes,
activeEventTypes: state.eventsState.activeEventTypes
});
const mapDispatchToProps = (dispatch) => ({
onSetEventTypes: (eventTypes) => dispatch({ type: 'EVENT_TYPES_SET',
eventTypes }),
onSetActiveEventTypes: (activeEventTypes) => dispatch({ type:
'ACTIVE_EVENT_TYPES_SET', activeEventTypes })
});
const authCondition = (authUser) => !!authUser;
export default compose(
withAuthorization(authCondition),
connect(mapStateToProps, mapDispatchToProps)
)(DashboardPage);
I have placed my code in my component above, it should be all that is needed to debug. I will put the reducer below
const applySetEventTypes = (state, action) => ({
...state,
eventTypes: action.eventTypes
});
const applySetActiveEventTypes = (state, action) => ({
...state,
activeEventTypes: action.activeEventTypes
});
function eventsReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case 'EVENT_TYPES_SET' : {
return applySetEventTypes(state, action);
}
case 'ACTIVE_EVENT_TYPES_SET' : {
return applySetActiveEventTypes(state, action);
}
default : return state;
}
}
export default eventsReducer;
Above is my reducer, I think I am following the correct patterns for managing redux state and maintaining immutability. What am I missing?
setFilters is a method that the checkboxes use to update active filters compared to all the filters available.
You are definitely mutating state:
const {onSetActiveEventTypes, authUser} = this.props;
let array = this.props.activeEventTypes
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
That mutates the existing array you got from the state, and then you are dispatching an action that puts the same array back into the state. So, you are both A) reusing the same array all the time, and B) mutating that array every time.
The approaches described in the Immutable Update Patterns page in the Redux docs apply wherever you are creating new state values, whether you're generating the new state in a reducer based on a couple small values, or before you dispatch the action.
//update filters for events
setFilters = (name) => async () => {
const {onSetActiveEventTypes, authUser} = this.props;
let array = []
this.props.activeEventTypes.map((type) =>{
array.push(type)
})
let index = array.indexOf(name);
if (index > -1) {
array.splice(index, 1);
}else {
array.push(name)
}
//use this once server sending active filters
// await eventTable.oncePostActiveEventTypes(authUser.email, array).then( data
=> {
// Promise.resolve(onSetActiveEventTypes(data));
// })
await Promise.resolve(onSetActiveEventTypes(array));
}

Resources