How to extract Firebase data to variable after setState() with react? - javascript

First time using react and firebase real-time database. I'm having a hard time extracting the data and inserting it into const items. From my understanding, the firebase code is asynchronous, so the code will execute after the page is loaded up.
After checking the console numerous times, I see the data loads into const tasks array. However, the data inside tasks[] doesn't display when I put it inside the items array(title, subtitle, etc). I've looked at multiple solutions on this and none of them have answered my issue.
I thought it was because of the items being a const, but after changing it to var, the issue persisted. Any sort of guidance would be appreciated here.
One small note: using await doesn't seem to work for me whatsoever. I could be mixing things up due to my lack of knowledge of firebase + react.
const Timelines = () => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
const ref = firebase.database().ref();
const listener = ref.on('value', snapshot => {
const fetchedTasks = [];
snapshot.forEach(childSnapshot => {
const key = childSnapshot.key; //using await here doesn't work. Return 'await is a reserved word error'
const data = childSnapshot.val(); //using await here doesn't work. Return 'await is a reserved word error'
fetchedTasks.push({ id: key, ...data });
});
setTasks(fetchedTasks);
});
return () => ref.off('value', listener);
}, [firebase.database()]);
const items = [{
title: "Header" + tasks,
cardTitle: "Title",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: ["Paragrah"],
media: {
type: "IMAGE",
source: {
url: "./images/image1.jpeg"
}
}
},
{
title: "Title",
cardTitle: "Title1",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: "Paragraph 1",
media: {
type: "IMAGE",
source: {
url: "./images/img-2.jpg"
}
}
},
]
return (
<div className='timeline' style={{ width: "auto", height: "auto" }}>
<Chrono items={items}
slideShow
mode="VERTICAL_ALTERNATING"
theme={{
primary: "#7e8083",
secondary: "white",
cardBgColor: "#00467f",
cardForeColor: "white",
titleColor: "#00467f"
}}
useReadMore="true" />
</div>
)
};
export default Timelines;

Your problem may be rerendering problem. So you can try loading state. Check my useEffect and return.
const Timelines = () => {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(false); //<----
useEffect(() => {
setLoading(true) //<-----
const ref = firebase.database().ref();
const listener = ref.on('value', snapshot => {
const fetchedTasks = [];
snapshot.forEach(childSnapshot => {
const key = childSnapshot.key; //using await here doesn't work. Return 'await is a reserved word error'
const data = childSnapshot.val(); //using await here doesn't work. Return 'await is a reserved word error'
fetchedTasks.push({ id: key, ...data });
});
setTasks(fetchedTasks);
setLoading(false) //<---
});
return () => ref.off('value', listener);
}, [firebase.database()]);
const items = [{
title: "Header" + tasks,
cardTitle: "Title",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: ["Paragrah"],
media: {
type: "IMAGE",
source: {
url: "./images/image1.jpeg"
}
}
},
{
title: "Title",
cardTitle: "Title1",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: "Paragraph 1",
media: {
type: "IMAGE",
source: {
url: "./images/img-2.jpg"
}
}
},
]
return !loading && (
<div className='timeline' style={{ width: "auto", height: "auto" }}>
<Chrono items={items}
slideShow
mode="VERTICAL_ALTERNATING"
theme={{
primary: "#7e8083",
secondary: "white",
cardBgColor: "#00467f",
cardForeColor: "white",
titleColor: "#00467f"
}}
useReadMore="true" />
</div>
)
};
export default Timelines;
Thank you

What Gökhan Geyik stated as a solution is correct. But I had to adjust my code a little since my Firebase db structure was different. The problem was, rerendering!
useEffect(() => {
setLoading(true)
const ref = firebase.database().ref('Overview/events');
const listener = ref.on('value', snapshot => {
const fetchedTasks = [];
const data = snapshot.val();
fetchedTasks.push(data);
setTasks(fetchedTasks);
setLoading(false)
});
return () => ref.off('value', listener);
}, [firebase.database()]);
return !loading && (
....
)
This worked like a charm. Much kudos to Gökhan Geyik for the right direction!

Related

How to implement server side search filter in redux tool kit using query builder RTK?

I want to apply server side search filter by text using redux toolkit.
I have two query builder methods in place. One for fetching all items and second for fetching only filtered data.
Query builder for fetching all items is
getAllBlogs: builder.query<BlogType[], void>({
queryFn: async () => {
const collectionRef = collection(Firestore, BLOG_COLLECTION)
const q = query(collectionRef, limit(1000))
const resp = await getDocs(q)
return {
data: resp.docs.map((doc) => doc.data() as BlogType),
}
},
providesTags: (result) => {
const tags: { type: 'Blogs'; id: string }[] = [
{ type: 'Blogs', id: 'LIST' },
]
if (result) {
result.forEach(({ id }) => {
tags.push({
type: 'Blogs',
id,
})
})
}
return tags
},
}),
This works fine and I'm getting the whole list through useGetAllBlogsQuery data.
Query builder for fetching filtered data is here: (Partially completed)
getBlogsByTitle: builder.query<BlogType[], string>({
queryFn: async (title) => {
const collectionRef = collection(Firestore, BLOG_COLLECTION)
const q = query(
collectionRef,
where('searchIndex', 'array-contains', title),
limit(1000),
)
const resp = await getDocs(q)
return {
data: resp.docs.map((doc) => doc.data() as BlogType), // Correct data
}
},
// I'm trying to only push the resultant items in state. This is not working
providesTags: (result) => {
const tags: { type: 'Blogs'; id: string }[] = []
if (result) {
result.forEach(({ id }) => {
tags.push({
type: 'Blogs',
id,
})
})
}
return tags
},
}),
I have react component looks like this where I'm calling these queries.
const Blogs: NextPage = () => {
const { data: blogs } = blogsApi.useGetAllBlogsQuery()
const [getBlogsByTitle] = blogsApi.useLazyGetBlogsByTitleQuery()
const debounced = useDebouncedCallback(async (value) => {
const { data } = await getBlogsByTitle(value)
console.log(data) // Correct data
}, 500)
return (
<div>
<InputText
onChange={(e) => debounced(e.target.value)}
/>
</div>
)}
The above code has two functionalities.
Fetch all the items on initial load.
Filter when debounced function is being called.
What I want is when getBlogsByTitle is called it will auto update the same state blogs in redux and we don't have to do much.
We are getting correct response in getBlogsByTitle but this query is not updating state with only its filtered response.
I'm new to redux-toolkit. Can someone help me out here where am I doing wrong ?

React - State is not updating when it is supposed to, why is react doing this? (not retaining)

Hi so I'm trying to grab some json from an api and then populate a table, pretty simple stuff.
What's happening is that I can see the "tableData" state being updated as each new row comes in, I'm also logging every time "tableData" is updated, yet maybe .5 seconds after its all done my "tableData" is empty again (check console screenshots)
const [bigChartData, setbigChartData] = React.useState("data1");
const [tableData, setTableData] = React.useState([]);
const setBgChartData = (name) => {
setbigChartData(name);
};
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.forEach(item => {
setTableData(oldData => [...oldData, {
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
}]);
});
})
.catch(err => console.log(err));
setTimeout(function () {
console.log(tableData);
}, 3000);
}
useEffect(() => {
getData();
}, []);
useEffect(() => {
console.log("Table data updated:");
console.log(tableData);
}, [tableData]);
I think you should not iterate through each row inside getData() method instead try following code
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.map(item => {
return{
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
};
});
setTableData(data)
}).catch(err => console.log(err));
}
or if you have already some data in tableData then
setTableData([...tableData, data])

useCallback function is not a function while using unit test Jest

This is my test code snippet but it throws an exception, TypeError: componentInstance.loadLoanApplication is not a function :
it('should render the SubmittedLoan', () => {
const loanData = {
data: {
id: 1,
};
const div = document.createElement('div');
const wrapper = mount(
<AppProviders>
<MemoryRouter initialEntries={['/review/153']}>
<SubmittedLoan
match={{ params: { loanId: 1, step: 1 } }}
history={{
location: { state: { from: 'register' } },
push() {},
}}
/>
</MemoryRouter>
</AppProviders>,
div,
);
const componentInstance = wrapper
.find(SubmittedLoan)
.children()
.first()
.children()
.first()
.instance();
const loanApplication = {
id: 1,
steps_data: [
{ slug: 'step_1', title: 'Step 1' },
{ slug: 'step_2', title: 'Step 2' },
],
status: ApiCaptiq.STATUS_SUBMITTED,
};
expect(wrapper.find(SubmittedLoan).length).toBe(1);
componentInstance.loadLoanApplication(1, 1);
componentInstance.onLoadLoanApplication(loanData);
componentInstance.onLoadFail();
componentInstance.setState({
formData: [{ item: 'value' }, { item2: 'value2' }],
activeStep: 1,
loanApplication,
});
componentInstance.handleSnackbarClose(new Event('click'), '');
componentInstance.setState({ activeStep: 3 });
});
Then my Component which uses memo is as follows :
export const SubmittedLoan = memo(() => {
const [loanApplication, setLoanApplication] = useState<LoanApplication | null>(null);
const [message, setMessage] = useState({
message: '',
open: false,
messageType: '',
});
const authContext = useContext(AuthContext);
const customerContext = useCustomerData();
const params = useParams();
const history = useHistory();
const classes = useStyles();
const { loanId } = params;
const onLoadFail = useCallback(() => {
setMessage({
message: 'Die verfügbaren Darlehensarten können nicht aufgelistet werden',
open: true,
messageType: 'error',
});
}, []);
const onLoadLoanApplication = useCallback(
(response: AxiosResponse) => {
setTemplateSettings(response, authContext);
if (
response.data.status === ApiCaptiq.STATUS_STARTING ||
response.data.status === ApiCaptiq.STATUS_IN_PROGRESS ||
response.data.status === ApiCaptiq.STATUS_PRE_WAITING
) {
history.push(`/view/${loanId}`);
} else {
setLoanApplication(response.data);
}
},
[loanId, authContext, history],
);
const loadLoanApplication = useCallback(
async (loan_id: number) => {
try {
const response = await request.get(`${ApiCaptiq.LOAN_APPLICATION_URL}${loan_id}/`);
const { fetchCustomerProfile } = customerContext;
await fetchCustomerProfile(response.data.customer_profile_id);
onLoadLoanApplication(response);
} catch (err) {
onLoadFail();
}
},
[customerContext, onLoadLoanApplication, onLoadFail],
);
...
What could be the possible reason for this
The functions you are defining inside the component, are not just available on the component instance. In fact, there is not way to call them. You can test only by mocking the fetch calls they are doing.
If you really need callable functions in your component (you should try to avoid these..), you could use this: https://reactjs.org/docs/hooks-reference.html#useimperativehandle
Perhaps better would be to extract this data loading logic elsewhere and test it separately.

Fetching data with hook takes too long time

I am trying to fetch and map an array with 350 object's elements. I decided to use Hook and useEffect, to re render my dataTable component since mapping is done. Unfortunately, the whole process takes enormous amount of time, and it makes page unresponsive. After 1-2 minutes, table shows up and after few seconds it disappears. After that page is still unresponsive. Could someone explain why it happens, and give me some workaround? I would be grateful.
Code below:
const Employees = (props) => {
const [developers, setDevelopers] = useState([]);
useEffect(() => {
fetchData();
});
const columns = [
{
name: "Emloyee",
selector: "name",
sortable: true,
},
{
name: "Team ",
selector: "team",
sortable: true,
},
{
name: "Email ",
selector: "email",
sortable: true,
},
];
const fetchData = () => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
};
const mapData = (jsonData) => {
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#mail.com",
};
setDevelopers((developers) => [...developers, newDeveloper]);
});
};
return <DataTable title="Employees" columns={columns} data={developers}/>;
};
useEffect without dependency array will run on every render, so in your case, you are stuck in an infinite loop which cause page to become unresponsive
solution:
const fetchData = useCallback(() => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
},[]);
const mapData = useCallback((jsonData) => {
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#kuehne-nagel.com",
};
setDevelopers((developers) => [...developers, newDeveloper]);
});
},[]);
useEffect(() => {
fetchData();
},[fetchData]); // pass dependency array here in useEffect
Thanks to #DrewReese and #SarthakAggarwal , I've got a solution:
const Employees = (props) => {
const [developers, setDevelopers] = useState([]);
const columns = [
{
name: "Emloyee",
selector: "name",
sortable: true,
},
{
name: "Team ",
selector: "team",
sortable: true,
},
{
name: "Email ",
selector: "email",
sortable: true,
},
];
const fetchData = useCallback(() => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
}, []);
const mapData = (jsonData) => {
let table = [];
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#mail.com",
};
table = [...table,newDeveloper];
//setDevelopers((developers) => [...developers, newDeveloper]);
});
setDevelopers((developers) => table);
};
useEffect(() => {
fetchData();
}, [fetchData]);
return <DataTable title="Employees" columns={columns} data={developers}/>;
};
Thanks a lot !

Material-table example doesn't work as intended

In Material-UI's website Table page, there's one specific table that I was interested in using for a project, labeled material-table (https://material-ui.com/components/tables/#material-table). It's got a search bar, a button for adding new rows, and the possibility of editing or deleting existing rows, also through buttons. In the site, everything works perfectly, but the source code provided doesn't, and I'm not sure why.
import React from 'react';
import MaterialTable from 'material-table';
export default function MaterialTableDemo() {
const [state, setState] = React.useState({
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Surname', field: 'surname' },
{ title: 'Birth Year', field: 'birthYear', type: 'numeric' },
{
title: 'Birth Place',
field: 'birthCity',
lookup: { 34: 'İstanbul', 63: 'Şanlıurfa' },
},
],
data: [
{ name: 'Mehmet', surname: 'Baran', birthYear: 1987, birthCity: 63 },
{
name: 'Zerya Betül',
surname: 'Baran',
birthYear: 2017,
birthCity: 34,
},
],
});
return (
<MaterialTable
title="Editable Example"
columns={state.columns}
data={state.data}
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
}),
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
setState(prevState => {
const data = [...prevState.data];
data[data.indexOf(oldData)] = newData;
return { ...prevState, data };
});
}
}, 600);
}),
onRowDelete: oldData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.splice(data.indexOf(oldData), 1);
return { ...prevState, data };
});
}, 600);
}),
}}
/>
);
}
The code above is the one shown on the site. When I try to use it, however, while the Edit button allows me to change the fields of the selected row, the changes are not saved when I use the Save button.
Also, the Delete one seems to always delete the last row of the table, even if it wasn't the one I chose to delete. Adding new rows, however, seems to work normally.
How can I fix this?
const data = [...prevState.data];
data[data.indexOf(oldData)] = newData;
I changed it into:
const foundIndex = oldData.findIndex(x => x.name === newData.name)
data[foundIndex] = newData
return data
It isn't pretty and I don't get all the setTimeouts, but here are working edit and delete on the current demo.js in the material-table on GitHub as of today (the reason I found this question in the first place, because edit and delete don't work on that code either). NOTE: This is scoped to this and does not use prevState style usage of setState, but leaving this here as a working solution for demo.js code.
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
const data = [...this.state.data];
const foundIndex = data.findIndex(x => x.name === newData.name)
data[foundIndex] = newData
this.setState({...this.state, data});
}
}, 600);
}),
onRowDelete: (oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
const data = [...this.state.data];
const foundIndex = data.findIndex(x => x.name === oldData.name)
data.splice(foundIndex, 1);
this.setState({...this.state, data});
}, 1000);
}),

Categories