How to create a dynamic HTML table with Firebase? - javascript

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!

Related

Vue: UI only renders data when I save changes in VS Code

I am new to Vue and I am struggling with an extremely weird issue:
In my component, in the creates() function I fetch data and save them into my items Array. I display the content of the items with v-for as a paragraph.
Now here comes the weird thing: When I start the App, nothing shows, although I can see in the Vue dev-tools that the proper data is stored in the items array. When I refresh, same happens. Only when I make changes in my code and save it, I see the proper data being displayed in the UI.
Code:
WeeklyRanking.vue:
<script>
import { computed } from "vue"
import RankingItem from "./RankingItem.vue";
import {getUsersWithPoints} from "../../../data/data";
export default {
name: "WeeklyRanking",
data() {
return {
items: [],
};
},
created (){
getUsersWithPoints().then((users) => {
this.items = users;
console.log(users);
});
},
computed: {
sortedRankings() {
return [...this.items].sort((a, b) => b.points - a.points);
},
maxPoints() {
return 95;
}
},
components: { RankingItem },
}
</script>
<template>
<div class="weekly-ranking-container">
<h1>Wochen - Ranking</h1>
<div class="ranking-chart">
<p v-for="r in items"> {{r.username}} </p>
{{items}}
</div>
<button>
<router-link to="/taskDone">+ Aufgabe erledigt</router-link>
</button>
</div>
</template>
<style>
.weekly-ranking-container{
padding: 24px;
max-width: 400px;
min-width: 300px;
}
.ranking-chart{
display: flex;
flex-direction: column;
align-items: flex-start;
}
</style>
The console.log shows the following:
The Vue inspector show the data as follows:
What I have tried so far
Use fake data directly in the data() part - works. The UI shows the initial data that I mock here.
get fake data from data source with fake delay - works as well. Shows the proper data after the artificial delay.
The interesting thing in this case, is that the Array in the console log looks different:
I could not make sense of the difference between those two logs, especially because the Vue inspector displays it exactly the same. Also, the part with the code saving does not make sense to me.
Below I show the data call code. I use firebase with the modular api.
Thanks for your help!
Data call code:
async function getUsersWithPoints(){
// With the commented part it works
// const fakeUsers = [
// {username:"Marcelo", points:95, id:"PRirMre5IUeHP7BA08wh"},
// {username:"Sebse", points:80, id:"PRirMasdoiHP7BA08wh"},
// {username:"Simi", points:20, id:"yhaHRRxN7PFmfyHZqZS1"},
// ];
// await new Promise(r => setTimeout(r, 2000));
// console.log("FAKE USERS:");
// console.log(fakeUsers);
// return fakeUsers;
//with the below part it does not
let users = [];
const usersQuery = query(collection(db, `groups/${wgKey}/users`));
const usersSnap = await getDocs(usersQuery);
usersSnap.forEach(async(user)=>{
const tasksQuery = query(collection(db, `${user.ref.path}/tasks`));
const tasks = await getDocs(tasksQuery);
let points = 0;
tasks.forEach((task)=>{
points+=task.data().points;
});
users.push({
username: user.data().name,
points: points,
id: user.id
});
});
return users;
}
```
I can remember having an identical issue with an Vue 2 project some years ago. As far as I can see Valentin Rapp gave you the correct explanation already. The documentation also states:
Array Mutators
Vue is able to detect when an reactive array's mutation methods are called
Source: https://vuejs.org/guide/essentials/list.html#array-change-detection
One possible solution would be, to push your resulting elements to the reactive array, therefore triggering it's mutators.
// ...
created (){
getUsersWithPoints().then((users) => {
users.forEach(user => {
this.items.push(user)
})
console.log('Users:', this.items)
})
},
// ...
Object Mutators
Depending on the size of your users-array (> 10k) this approach could potentially lag/very slow on some devices. Another approach would be, to use the reactive mutators of an object by updating an objects prop. Please note, that I haven't used that with Vue 3 options API yet, so this could be faulty:
// ...
data() {
return {
user: {
items: [],
loaded: false, // can be used for some loading animation
},
}
},
created (){
getUsersWithPoints().then((users) => {
this.user.items = users
this.user.loaded = true
console.log('Users:', this.user.items)
})
},
// ...
Composition API
As stated before and propably lead to the confusion of Valentin Rapp is the useage of the options API in Vue 3. While this should be fully supported by Vue, with version 3 the composition API was implemented that could, besides other things, fix this problem for you:
// Vue 3 Component with Composition API — not a 1:1 approach!
// ...
setup() {
const items = ref([])
getUsersWithPoints().then((users) => {
users.forEach(user => {
this.items.push(user)
})
})
return {
items,
}
},
// ...
VS Code Saving Bug
This "bug" is more likely a confirmation for the problem stated above: mutators. Yet your given description is way to little to give you some detailed information here.
We don't know what your whole toolchain between "storing in vscode" to "bringing the new code to the browser" looks like. How does your code get transpiled? How is the file beeing watched? How is the hot reloading implemented?
Yet in general I think this will work like so:
File gets stored within your editor
Your toolchains watcher detects a changed file hash for one js file
Your toolchain transpiles your code
Your hot reload service will reload and inject the new code
In order to sync your current data within your browser instance and the default data within the hot-reloaded component will be replaced correctly with usage of vues mutators.
Your component therefor will detect the changes correctly. Your v-for get's rerendered and the data will be displayed correctly
But as I said, this is pure speculation and is purly depending on your setup.
So I tried multiple things to fix it:
Switching to Composition API
Using Array Mutators instead of replacing the Array
Using async - await instead of then(..)
Unfortunately, none of that fixed the problem. So I looked more deeply into the getUsersWithPoints function to find what I think was the mistake:
Using forEach with an async function.
Using Promise.all instead fixed the issue. Here is the code:
async function getUsersWithPoints(){
let usersExtended = new Array();
const usersQuery = query(collection(db, `groups/${wgKey}/users`));
const usersSnap = await getDocs(usersQuery);
await Promise.all(usersSnap.docs.map(async(user) => {
const tasksQuery = query(collection(db, `${user.ref.path}/tasks`));
const tasks = await getDocs(tasksQuery);
let points = 0;
tasks.forEach((task)=>{
points+=task.data().points;
});
usersExtended.push({
username: user.data().name,
points: points,
id: user.id
});
}));
return usersExtended;
}
Using array mutators seemed like a logical thing to do, although the docs say:
When working with non-mutating methods, we should replace the old array with the new one
...
You might think this will cause Vue to throw away the existing DOM and re-render the entire list - luckily, that is not the case. Vue implements some smart heuristics to maximize DOM element reuse, so replacing an array with another array containing overlapping objects is a very efficient operation.
source: https://vuejs.org/guide/essentials/list.html#array-change-detection
So as far as I understand, replacing the array will not re-render the entire DOM, but vue uses "smart heuristics" to render the replacing array very efficiently. So now that I fixed the data call I tried replacing and using Array Mutators and both worked.
In your created lifecycle hook you overwrite the reactive data array. This will not trigger an update as described here:
https://vuejs.org/guide/essentials/list.html#array-change-detection
You will need to push the data to the array. To modify and clear it use splice.
In Vue 3 you will not have to deal with this anymore.

Vue child component not displaying dynamic data on first page load

Given the code below, my child component alerts trigger before any of the code in the Parent mounted function.
As a result it appears the child has already finished initialization before the data is ready and therefor won't display the data until it is reloaded.
The data itself comes back fine from the API as the raw JSON displays inside the v-card in the layout.
My question is how can I make sure the data requested in the Parent is ready BEFORE the child component loads? Anything I have found focuses on static data passed in using props, but it seems this completely fails when the data must be fetched first.
Inside the mounted() of the Parent I have this code which is retrieves the data.
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray).then(() => {
console.log('DATA ...') // fires after the log in Notes component
this.checkAttendanceForPreviousTwoWeeks().then(()=>{
this.getCurrentParticipants().then((results) => {
this.currentP = results
this.notesArr = this.notes // see getter below
})
The getter that retrieves the data in the parent
get notes() {
const newNotes = eventsModule.getNotes
return newNotes
}
My component in the parent template:
<v-card light elevation="">
{{ notes }} // Raw JSON displays correctly here
// Passing the dynamic data to the component via prop
<Notes v-if="notes.length" :notesArr="notes"/>
</v-card>
The Child component:
...
// Pickingn up prop passed to child
#Prop({ type: Array, required: true })
notesArr!: object[]
constructor()
{
super();
alert(`Notes : ${this.notesArr}`) // nothing here
this.getNotes(this.notesArr)
}
async getNotes(eventNotes){
// THIS ALERT FIRES BEFORE PROMISES IN PARENT ARE COMPLETED
alert(`Notes.getNotes CALL.. ${eventNotes}`) // eventNotes = undefined
this.eventChanges = await eventNotes.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
...
I am relatively new to Vue so forgive me if I am overlooking something basic. I have been trying to fix it for a couple of days now and can't figure it out so any help is much appreciated!
If you are new to Vue, I really recommend reading the entire documentation of it and the tools you are using - vue-class-component (which is Vue plugin adding API for declaring Vue components as classes)
Caveats of Class Component - Always use lifecycle hooks instead of constructor
So instead of using constructor() you should move your code to created() lifecycle hook
This should be enough to fix your code in this case BUT only because the usage of the Notes component is guarded by v-if="notes.length" in the Parent - the component will get created only after notes is not empty array
This is not enough in many cases!
created() lifecycle hook (and data() function/hook) is executed only once for each component. The code inside is one time initialization. So when/if parent component changes the content of notesArr prop (sometimes in the future), the eventChanges will not get updated. Even if you know that parent will never update the prop, note that for performance reasons Vue tend to reuse existing component instances when possible when rendering lists with v-for or switching between components of the same type with v-if/v-else - instead of destroying existing and creating new components, Vue just updates the props. App suddenly looks broken for no reason...
This is a mistake many unexperienced users do. You can find many questions here on SO like "my component is not reactive" or "how to force my component re-render" with many answers suggesting using :key hack or using a watcher ....which sometimes work but is almost always much more complicated then the right solution
Right solution is to write your components (if you can - sometimes it is not possible) as pure components (article is for React but the principles still apply). Very important tool for achieving this in Vue are computed propeties
So instead of introducing eventChanges data property (which might or might not be reactive - this is not clear from your code), you should make it computed property which is using notesArr prop directly:
get eventChanges() {
return this.notesArr.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
Now whenever notesArr prop is changed by the parent, eventChanges is updated and the component will re-render
Notes:
You are overusing async. Your getNotes function does not execute any asynchronous code so just remove it.
also do not mix async and then - it is confusing
Either:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray)
await this.checkAttendanceForPreviousTwoWeeks()
const results = await this.getCurrentParticipants()
this.currentP = results
this.notesArr = this.notes
or:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
Promise.all(promisesArray)
.then(() => this.checkAttendanceForPreviousTwoWeeks())
.then(() => this.getCurrentParticipants())
.then((results) => {
this.currentP = results
this.notesArr = this.notes
})
Great learning resource

How to render user list in React-Firebase

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>
);
}

Is it possible to use references as react component's prop or state?

I want to create react table component which values are derived from single array object. Is it possible to control the component from view side? My goal is that every user using this component in their web browsers share the same data via singleton view object.
Program modeling is like below.
Database - there are single database in server which contain extinct and independent values.
DataView - there are singleton View class which reflects Database's table and additional dependent data like (sum, average)
Table - I'll build react component which looks like table. And it will show View's data with supporting sorting, filtering, editing and deleting row(s) feature (and more). Also it dose not have actual data, only have reference of data from View(Via shallow copy -- This is my question, is it possible?)
My intentions are,
- When user changes value from table, it is queried to DB by View, and if succeed, View will refer updated data and change it's value to new value and notify to Table to redraw it's contents. -- I mean redraw, not updating value and redraw.
- When values in View are changed with DB interaction by user request, there are no need to update component's value cause the components actually dose not have values, only have references to values (Like C's pointer). So only View should do is just say to Component to redraw it's contents.
I heard that React's component prop should be immutable. (Otherwise, state is mutable) My goal is storing references to component's real value to it's props so that there are no additional operation for reflecting View's data into Table.
It is concept problems, and I wonder if it is possible. Since javascript dose not support pointer officially(Am I right?), I'm not sure if it is possible.
View class is like below,
const db_pool = require('instantiated-singleton-db-pool-interface')
class DataView {
constructor() {
this.sessions = ['user1', 'user2'] // Managing current user who see the table
this.data = [ // This is View's data
{id:1, name:'James', phone:'12345678', bank:2000, cash:300, total:2300,..},
{id:2, name:'Michael', phone:'56785678', bank:2500, cash:250, total:2300,..},
{id:3, name:'Tyson', phone:'23455432', bank:2000, cash:50, total:2300,..}
] // Note that 'total' is not in db, it is calculated --`dependent data`
}
notifySessionToUpdate(ids) {
// ids : list of data id need to be updated
this.sessions.forEach((session) => {
session.onNotifiedUpdateRow(ids) // Call each sessions's
})
}
requestUpdateRow(row, changed_value) {
// I didn't write async, exception related code in this function for simple to see.
update_result = db_pool.update('UPDATE myTable set bank=2500 where id=1')
if (update_result === 'fail') return; // Do Nothing
select_result = db_pool.select('SELECT * from myTable where id=1') // Retrieve updated single data which object scheme is identical with this.data's data
for (k in Object.keys(select_result)) {.ASSIGN_TO_row_IF_VALUE_ARE_DIFFERENT.} // I'm not sure if it is possible in shallow copy way either.
calc.reCalculateRow(row) // Return nothing just recalculate dependant value in this.data which is updated right above.
// Notify to session
this.notifySessionToUpdate([1]) // Each component will update table if user are singing id=1's data if not seeing, it will not. [1] means id:1 data.
return // Success
}
... // other View features
}
Regarding session part, I'm checking how to implement sessionizing(?) the each user and it's component who is communicating with server. So I cannot provide further codes about that. Sorry. I'm considering implementing another shallow copied UserView between React Component Table and DataView(And also I think it helps to do something with user contents infos like sorting preference and etc...)
Regarding DB code, it is class which nest it's pool and query interface.
My problem is that I'm not familiar with javascript. So I'm not sure shallow copy is actually implementable in all cases which I confront with.
I need to think about,
1. Dose javascript fully support shallowcopy in consistent way? I mean like pointer, guarantee check value is reference or not.
2. Dose react's component can be used like this way? Whether using props or state Can this be fullfilled?
Actually, I strongly it is not possible to do that. But I want to check your opinions. Seems it is so C language-like way of thinking.
Redraw mean re-render. You can expose setState() or dispatch() functions from Table component and call them on View level using refs:
function View() {
const ref = useRef();
const onDbResponse = data => ref.current.update(data);
return (
<Table ref={ ref } />
);
}
const Table = React.forwardRef((props, ref) => {
const [ data, setData ] = useState([]);
useImperativeHandler(ref, {
update: setData
});
...
});
Anyway i don't think it's a good practice to update like that. Why can't you just put your data in some global context and use there?
const Context = React.createContext({ value: null, query: () => {} });
const Provider = ({ children }) => {
const [ value, setValue ] = useState();
const query = useCallback(async (request) => {
setValue(await DB.request(request));
}, [ DB ]);
const context = { value, query };
return <Context.Provider value={ context }>{ children }</Context.Provider>;
}
const useDB = () => useContext(Context);
const View = () => {
const { request } = useDB();
request(...);
}
const Table = () => {
const { value } = useDB();
...
}

Filtering a list of names with ReactJS using data from RelayJS without calling GrpahQL

I'm stuck in wondering if I can filter a list of names which I receive from Relay and graphql-java server without the need of making calls, without making any changes in my GrpahQL schema and only using ReactJS for this purpose.
---MobX as a state management library can be a decision but I should first store all the Relay result.
Caveat emptor: I'm newish at Relay as well & struggling with these same concepts. But, given the relative dearth of accessible information on Relay, I thought it'd be helpful to try and layout the key concepts here. My understanding could be wrong, so I'd love it if anyone who found a mistake in my code/reasoning would comment/edit.
Filtering took awhile for me to 'click' as well. It depends on where you keep the data you'll use to filter, but let's assume the name field lives on your Users Type, and the query is something like this:
viewer {
allUsers {
edges {
node {
name
}
}
}
}
And let's say your top-level NameList component looked like this:
class NameList extends Component {
render() {
return (
<div>
{this.props.users.edges
.map(u => {
<NameItem name={u.node} />
})
}
</div>
)
}
}
Relay.createContainer(NameList, {
initialVariables: { first: 10 },
fragments: {
users: () => Relay.QL`
fragment on Viewer {
allUsers(first: $first) {
edges {
node {
${NameItem.getFragment('user')}
}
}
}
}
`
}
})
And your NameItem setup was simply:
class NameItem extends Component {
render() {
return (
<div>
Name: {this.props.user.name}
</div>
)
}
}
Relay.createContainer(NameItem, {
initialVariables: {},
fragments: {
user: () => Relay.QL`
fragment on User {
name
}
`
}
})
Consider the generalizable pattern here:
The List Component
A List component takes a fragment on the top-level Type in the query--in this case, Viewer, from a Relay container.
List also inserts a fragment on behalf of its Item child at the level of the User Type.
In other words, it captures an array of User objects it's supposed to pass down to the Item component.
If this wasn't Relay, and instead was, say, Redux, this component might simply pass state.users to the Item component. You can do that because, at some point, you've manually extracted all your Users from your own back-end and loaded them into Redux. But since Relay does the hard thinking for you, it needs a teensy bit more information than Redux.
The Item Component
This is even more simple. It expects an entity of type User and renders the name. Besides syntax, the functionality here isn't much different from a similar component in a Redux setup.
So really, without the complexity of Relay on top, all you have is an array of items that you're rendering. In vanilla React, you'd simply filter the array prior to (or during) your call to .map() in render().
However, with Relay, the fragment handed to the child is opaque to the parent--i.e., the List is handing a blind package to the Item, so it can't make a decision on whether or not to pass it down based on the fragment's content.
The solution in this contrived example is pretty simple: just peel-off the name field at the parent and child level. Remember: Relay is about components telling GraphQL what data they need. Your List component needs whatever fields it intends on filtering on--no more, no less.
If we modify the above List container:
...
users: () => Relay.QL`
fragment on Viewer {
allUsers(first: $first) {
edges {
node {
name
${NameItem.getFragment('user')}
}
}
}
}
`
And then we update our render function:
<div>
{this.props.users.edges
.map(u => {
if (u.node.name == "Harvey") {
<NameItem name={u.node} />
}
})
}
</div>
Then we've achieved basic filtering without needing mobx, more server trips, etc.

Categories