I'm still learning React and I'm trying to make a "design review app" where users signup as customers or designers and interact with each other.
I made the auth system and made sure that while signing up every user would get also some attributes in the firebase database.
Therefore, in my DB, I have a 'users/' path where every user is saved by uid.
Now I'm able to render a different dashboard if you're a customer or a designer.
In my customer dashboard, I just want to render a list of designers (and clicking on them go to their projects).
However, I'm having so many problems trying to get this stuff to work!
In the following code, I'm trying to fetch the users from the db and add their uid to an array.
Later I want to use this array and render the users with those uids.
import firebase from "firebase/app";
import "firebase/database";
export default function CustomerContent() {
const[designers, setDesigners] = useState([]);
function printUsers (){
var users = firebase.database().ref('/users/');
users.on('value', (snapshot)=>{
snapshot.forEach((user)=>{
console.log(user.key)
firebase.database().ref('/users/'+user.key).on('value', (snapshot)=>{
var role = snapshot.val().role
console.log(role)
if(role === 'designer'){
const newDesigners = [...designers, user.key];
setDesigners(newDesigners);
}
})
})
})
}
useEffect(() => {
printUsers();
console.log(designers);
}, [])
return (
<div>
designer list
</div>
)
}
Now the problem with this code is that:
it looks like it runs the printUsers functions two times when loading the page
the array is empty, however, if I link the function to a button(just to try it), it seems to add only 1 uid to the array, and always the same (I have no idea what's going on).
ps. the console.log(user.key) and the console.log(role) print the right user-role combination
It's not a stupid question. Here's what I'd change it to (of course you'd remove the console.logs later though). It's hard to know if this will work perfectly without having access to your database to run it, but based on my last react/firebase project, I believe it'll work.
The first thing was that you reference /users/, when you only need /users. I'm not sure if it makes a difference, but I did it the latter way and it worked for me.
Secondly, you're calling firebase more than you need to. You already have the information you need from the first time.
Third, and this is small, but I wouldn't call your function printUsers. You're doing more than just printing them- you're making a call to firebase (async) and you're setting the state, which are much larger things than just print some data to the console.
Lastly, I would store the entire object in your designers piece of state. Who knows what you'll want to display? Probably at least their name, then possibly their location, background, an icon, etc. You'll want all of that to be available in that array, and possibly you'll want to move that array into redux later if you're app is big enough.
I also added some JSX to the bottom that gives a simple output of what you could do with the designers array for the visual aspect of your app.
import firebase from 'firebase/app';
import 'firebase/database';
export default function CustomerContent() {
const [designers, setDesigners] = useState([]);
function printUsers() {
var users = firebase.database().ref('/users');
users.on('value', (snapshot) => {
snapshot.forEach((snap) => {
const userObject = snap.val();
console.log(userObject);
const role = userObject['role'];
console.log(role);
if (role === 'designer') {
const newDesigners = [...designers, userObject];
setDesigners(newDesigners);
}
});
});
}
useEffect(() => {
printUsers();
console.log(designers);
}, []);
return (
<div>
<h2>The designer are...</h2>
<ul>
{designers.map((designerObject) => {
return <li>{designerObject.name}</li>;
})}
</ul>
</div>
);
}
Related
I am working with ReactJS and Google Firestore. I have a component called GameEntryForm, where you can select from a list of users stored in Firestore. In order to get this list, when I render the GameEntryForm component, I make a query to Firestore. Below is how I am getting the list.
I was wondering if there was a better or faster way to do this. My concern is that as the number of users increases, this could be a slow operation.
function GameEntryForm() {
// prevent rendering twice
const effectRan = useRef(false);
const [usersList, setUsersList] = useState(new Map());
useEffect(() => {
if (effectRan.current === false) {
const getUsers = async () => {
const q = query(collection(firestore, "users"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
setUsersList(new Map(usersList.set(doc.data().uid, doc.data())));
});
};
getUsers();
return () => {
effectRan.current = true;
};
}
}, []);
}
Your code looks fine at first glance, but
here are many ways to mitigate this issue some of them are as follows:
Implement Pagination Functionality to limit the number of documents that are returned by the query, for more about this topic go through this docs
Use Firestore Offline Caching feature through persistence like one provided here. I understand that your user will be added constantly so there’s not much improvement with this method but you can trigger a new request to the db based on the changed type. This is nicely explained in this thread
You can also use the above caching with a global state management solution(Redux, Context API) and only fetch the list of users once. This way, the list of users would be accessible to all components that need it, and you would only have to make the query once. Someone has created an example for how this will work although not using firestore though.
Last but not least use Real Time lister to View changes between snapshots as provide here in official docs This works great with the offline Caching option.
I have a react query to get user data like this
const { data: queryInfo, status: queryInfoLoading } = useQuery('users', () =>
getUsers()),
);
I then have a sibling component that needs the same data from the get users query. Is there a way to get the results of the get users query without re-running the query?
Essentially, I would like to do something like this
const userResults = dataFromUserQuery
const { data: newInfo, status: newInfoLoading } = useQuery('newUserData', () =>
getNewUsers(userResults.name)),
)
As suggested in this related question (how can i access my queries from react-query?), writing a custom hook and reusing it wherever you need the data is the recommended approach.
Per default, react-query will trigger a background refetch when a new subscriber mounts to keep the data in the cache up-to-date. You can set a staleTime on the query to tell the library how long some data is considered fresh. In that time, the data will always come from the cache if it exists and no refreshes will be triggered.
I am building a single page application with Reactjs and MobX at the frontend (port 3000) and Nodejs and Express at the backend (API, port 4000). I am new to both, MobX and Reactjs and I am trying to set up a well-structured project.
My question is: Is it okay to have a Store for each view?
For example, I have a UserStore which stores the Session information and takes care of the login and logout of the user within the platform. However, after Logging in, I want to redirect the user to the dashboard page. This dashboard page must retrieve information regarding the user, but also it must contact the API and retrieve some data (i.e. Some Todos).
This is how I would do it:
This is the login function in which the redirection to Dashboard is made:
*UserStore.js*
[...]
import navigationStore from './NavigationStore';
[...]
login = async (user) => {
try {
const res = await axios.post('/session/login', {
username: user.username,
password: user.password
});
this.saveUser(res.data);
navigationStore.push('/dashboard');
} catch (error) {
[...]
}
}
And, then, I have created a DashboardStore.js which has the following code:
*DashboardStore.js*
[... imports and initializations ...]
class Store {
#observable todos = null
constructor() {
this.getDashboard();
}
#action('Load dashboard') getDashboard = async () => {
const res = await axios.get('/api/dashboard/', {});
this.todos = res.todos
}
}
const DashboardStore = new Store();
export default DashboardStore;
But this would mean that I'd end up doing another Store for the Todos page and another Store for whatever page I'd need.
In NodeJs you can make a controller for each class and there's nothing weird about it. However, I'm not sure that's how it works on MobX.
It depends on the complexity of your app. I wouldn't create a store for each view or concern, but you could create two, like the MobX docs recommend: https://mobx.js.org/best/store.html.
I'm working on a bigger project right now, and we started with a single store for everything. Obviously, it grew a lot as we kept adding functionality, so I think we might split it at some point to reduce complexity.
I'm trying to implement an external API library in a redux application.
I'm fresh new in redux so I don't know exactly how it works.
In my javascript using the API library, I wan't to access info from a container (the user firstanme if he's logged).
After reading some doc, I tried to import the store in my js file, to get the state of the user, but I can't reach the info I need.
Here's the code I tried :
import configureStore from '../store/configureStore';
const store = configureStore();
const state = store.getState();
I get many info in state, but not the one I need. Any help ?
First of all it looks like configureStore creates new store every time you call it. But you need the very that store that your components will use and populate. So you need to somehow access the store you are passing your Provider.
Then since store state is "changing" you can't simply read it once. So your user data might be initially empty but available some time later.
In this case you could make it a Promise
const once = selector => available => new Promise(resolve => {
store.subscribe(() => {
const value = selector(value)
if(available(value)) resolve(value)
})
})
And usage
const user = once(state => state.user)(user => user && user.fullName)
user.then(user => console.log(`User name is ${user.fullName}`)
Or if your data might be changing more than once during application lifecycle you might want to wrap it with something that represent changing data (observable). RX examle
I'm trying to create an HTML table that can be updated in real time with firebase as my users use my app. I'm really struggling on getting this to work, I don't really know where to start.
basically, all it needs to do is create a new table row and update its cells in real time as the user is interacting with my app.
Does anyone know of any good tutorials that could point me in the right direction? Examples of code on creating the HTML table would also be great! thanks.
UPDATE:
OK! thanks to Isaiah Lee, I went out and looked up some tutorials for React. I'm 90% there but there is one problem I can't seem to figure out...
I'm able to update my table with new rows dynamically as the users use my app, but I can't get them to fill up with data. I feel like I'm really close but my inexperience with React is holding me back here...
for some reason, this loop here doesn't populate the td's with any data
render() {
return (
<div className="App">
<h1>Talkeetna Numbers</h1>
<table id="numbers">
<tbody>
<tr>
<th>Driver</th>
<th>Coach</th>
<th>Time</th>
<th>Total People</th>
<th>AM Train</th>
<th>PM Train</th>
<th>MEX</th>
<th>Employees</th>
<th>Seats Left</th>
</tr>
{this.state.rows.map(row =>
<tr>
<td>{this.state.driver}</td>
<td>{this.state.coach}</td>
<td>{this.state.time}</td>
<td>{this.state.totalPeople}</td>
<td>{this.state.amTrain}</td>
<td>{this.state.pmTrain}</td>
<td>{this.state.THEMEX}</td>
<td>{this.state.Employees}</td>
<td>{this.state.seatsLeft}</td>
</tr>)}
</tbody>
</table>
</div>
);
}
Heres my components function
componentDidMount()
{
const logsRef = firebase.database().ref('logs');
logsRef.on('child_added', snap => {
this.addRow(snap.getKey());
//rows.push(snap.getKey());
console.log("adding key: ", snap.getKey());
});
console.log("loading rows...");
for(var rowKey = 0; rowKey < this.state.rows.length; rowKey++)
{
const root = firebase.database().ref().child('logs/' + rowKey);
const driverRef = root.child('Driver');
const coachRef = root.child('Coach');
const timeRef = root.child('Time');
const totalPeopleRef = root.child('Total People');
const AMTrainRef = root.child('AM Train');
const PMTrainRef = root.child('PM Train');
const MEXRef = root.child('MEX');
const EmployeesRef = root.child('Employees');
const seatsLeftRef = root.child('Seats Left');
//sync with DB in real time
driverRef.on('value', snap => {
this.setState({
driver: snap.val()
})
});
coachRef.on('value', snap => {
this.setState({
coach: snap.val()
})
});
timeRef.on('value', snap => {
this.setState({
time: snap.val()
})
});
totalPeopleRef.on('value', snap => {
this.setState({
totalPeople: snap.val()
})
});
AMTrainRef.on('value', snap => {
this.setState({
amTrain: snap.val()
})
});
PMTrainRef.on('value', snap => {
this.setState({
pmTrain: snap.val()
})
});
MEXRef.on('value', snap => {
this.setState({
THEMEX: snap.val()
})
});
EmployeesRef.on('value', snap => {
this.setState({
Employees: snap.val()
})
});
seatsLeftRef.on('value', snap => {
this.setState({
seatsLeft: snap.val()
})
});
}
}
Sounds like you should look into some frontend framework. In your particular use case, anything with 1-way binding will work (eg react). And of course, anything with 2-way binding will work as well (angular).
Egghead.io has really well done, bite-size tutorials that can get you running shortly: https://egghead.io/browse/frameworks
Edit:
Okay, it's been over a year since I've last been working with react, but I have some ideas that could help.
First, react is all about separating components and breaking things off into smaller bits. The idea is that you have some state change at the top level, those changes are propagated down into the nested components using props.
I'd recommend you have a top level component that listens for changes in Firebase (I havent worked with FB in a long time either), and then sends those down to components that render based on the data. I recommend reading this article written by Dan Abramov (creator of Redux), which discusses smart vs dumb components: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
Again, I haven't used react in a while, so I'll use pseudocode to describe how you might want to design this.
Top Level Component
This component should be the only place your app is interacting with Firebase. You'll configure all the listeners here such that when new data is received, it simply propagates down to child components.
constructor() {
this.firebaseRef = firebase
.database()
.ref('logs');
this.state = {
logs: []
};
// when the table first loads, use the
// firebase ref to get all the current data
// and initialize/render the table. After this,
// you should only be listening for when a new row
// (or "log") is added. Pseudocode:
firebase.database().ref('logs')
.all()
.then(logs => {
// project all the logs into the model we need and set to
// component's row state
this.state.logs = logs.map(logMapper);
});
}
// takes in a log that you get from firebase and maps
// it to an object, modeled such that it contains all
// the information necessary for a single table row
logMapper(log) {
return {
driver: log.child('Driver'),
time: log.child('Time'),
...
};
}
componentDidMount() {
const logsRef = firebase
.database()
.ref('logs');
firebaseRef.on('child_added', log => {
this.state.logs.push(logMapper(log));
});
}
render() {
return (
<table id="numbers">
<thead>
<th>Driver</th>
<th>Coach</th>
<th>Time</th>
<th>Total People</th>
<th>AM Train</th>
<th>PM Train</th>
<th>MEX</th>
<th>Employees</th>
<th>Seats Left</th>
</thead>
<tbody>
{this.state.logs.map(row => <TableRow rowData="logs" />}
</tbody>
</table>
);
}
Row Component
You have a separate component that receives a log object through props and renders a single row.
render() {
return (
<tr>
<td>{this.props.rowData.driver}</td>
<td>{this.props.rowData.coach}</td>
<td>{this.props.rowData.time}</td>
<td>{this.props.rowData.totalPeople}</td>
<td>{this.props.rowData.amTrain}</td>
<td>{this.props.rowData.pmTrain}</td>
<td>{this.props.rowData.THEMEX}</td>
<td>{this.props.rowData.Employees}</td>
<td>{this.props.rowData.seatsLeft}</td>
</tr>
);
}
The above TableRow is an example of a dumb component. It doesn't hold any internal state, nor really modify anything. It just takes what it's given via props and renders what it's supposed to.
Again, you'll have to consider my examples above as pseudocode, it wouldn't work if you tried to copy and run it. But hopefully this gives you some insight into how to design components in react. Remember, "everything is a component".
Just a couple last notes:
Components are more useful the more generic they are. The ideal
scenario is that you create a component, and if you need something
similar later, you can just take the component you already created
and plug it in with slight modifications (or in the best case
scenario, no change at all!). So if you see yourself needing a bunch
of similar tables in your app, you'll want to further generalize my
example. For example, the firebase ref explicitly connects with the
"logs" collection, making this table component only useful for that one object type.
You'll want to make the parent component receive either a table
name, or an already initialized firebase reference, that it can use.
That way, the component is not tied down to a specific firebase
table. The mapping function should also be passed in, as the one in
my example is hardcoded to map a "log" object, and you may be
wanting to grab other types from firebase.
I learned a lot from reading and messing around, but honestly this one Udemy class I took really nailed down all the concepts for me: https://www.udemy.com/react-redux/
I'd highly recommend taking it, although you'd probably want to wait until Udemy has a site-wide sale where you can get the course for $10-20 bucks. I've taken several of his courses and they've all been well worth the money.
Good luck!