React & Firebase - Appending component with Firebase onSnapshot - javascript

Currently, I'm using a button with an onClick function to append a JSX component to the UI every time it's been pressed. I'm also using a useEffect with some firebase database logic which is an onSnapshot to get live reads on the data to display the information onto the UI.
The problem is when a user refreshes the page the onClick function that appends the JSX is deleted from the state and doesn't display the firebase data onto the UI. How would I go about sorting this problem out?
Code:
import React, { useEffect, useState } from "react";
import DashboardBody from "./DashboardComponents/DashboardBody";
import db from "../../firebase";
function Test() {
const [exercise, setExercise] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => {
db.collection("users").onSnapshot((snapshot) => {
setExercise(
snapshot.docs.map((doc) => ({
id: doc.id,
exercise: doc.data().exercise,
}))
);
});
return;
}, []);
return (
<div>
{[...Array(count)].map((count, index) => (
<DashboardBody key={index} exercise={exercise} />
))}
<div>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
</div>
);
}
export default Test;

The count variable is stored in the component's state and is incremented whenever the button is clicked. When the page reloads (or the component unmounts and mounts again), the state variable is lost. To preserve the state, you would have to store it in the database (or in the browser's storage) so that you can fetch it when the component mounts again.

Related

Why only first time React function component getting props

React throw an error when we try to update the state on an unmounted component.So When I test react component for that I am getting errors on the first render only.
I made a component that enable child component based on click. And child component have button which updates state after some settimeout which throw react warning
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Which is perfectly valid. But to overcome this I am passing enable props from the parent component based on that there is a condition just before setTimeout in the child component. So why does it throw an error the first time only?
To reproduce
Click on the child component button which is false and click on parent component button enable which unmount the child component.
**My question is why react throw an error on the first time only ? And why it is working fine on second time **
Parent component
import { useState } from "react";
import "./styles.css";
import { Test } from "./Test";
export default function App() {
const [state, setstate] = useState(true);
const changeState = () => {
setstate(!state);
};
return (
<div className="App">
<button onClick={changeState}>enable </button>
{state && <Test enable={state} />}
</div>
);
}
Child Component
import React, { useState } from "react";
export const Test = (props) => {
const [state, setstate] = useState(false);
const fetchData = () => {
setstate(!state);
if (props.enable) {
setTimeout(() => {
setstate(false);
}, 1000);
}
};
return (
<>
<button onClick={fetchData}> {`${state}`}</button>
</>
);
};
Codesandbox link to test
Nice track, Just you are missing a minor point, when you write a state thats needed some time to execute and the same time we can visit the flow again and again base on any action, then we need to clear old subscription before go to new one...
For example, in your code here, you update state flow, but the state flow is register a new subscription every time we visit a component with valid props and click on button, so that, prev execution may still work when you trigger new event, so simply, what we need to do unmounted old subscription and we can do that by this for your case:
import React, { useState, useEffect } from "react";
export const Test = (props) => {
const [state, setstate] = useState(false);
useEffect(() => {
if (props.enable) {
const timer = setTimeout(() => {
setstate(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [state, props.enable]);
const fetchData = () => {
setstate((prev) => !prev);
};
return (
<>
<button onClick={fetchData}> {`${state}`}</button>
</>
);
};
Look at code above, simply we add code need to cleanup in effect which its look to my state and prop, now when I click on button, the effect will trigger, if we do that again, the clearTime will work for prev subscription and then add new one and so on...
Notes:
In your case we can remove function and use setState direct on your button.
Prefer to use useCallBack in your function like const fetchData = useCAllback...
You can use setstate((prev) => !prev); its will be work as snapshot, and its usefull when you depends on old value..but may it not needed in some cases too, but just to know about this feature.
Update 1:
What is Subscription:
You can say the subscription represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription, in another word, you can say yes, any async task or any job will be invoke to react life-cycle state and its needed to observe changes, then you talk about subscribe, like API or time out or time interval and so on, any of these action thats need to clear prev subscribe (stop observer - unsubscribe) to prevent any memory leek and clear memory to keep state flow safe and prevent unneeded reredner.

How to render stateHook result from the function call in ReactJS

I'm trying to get the result of stateHook and render it properly
//import React from 'react';
import React, {useState,useEffect} from 'react';
import {DashboardLayout} from '../components/Layout';
const ProjectsPage = () => {
function GetCount() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
return (
<DashboardLayout>
<h2>Projects Page {GetCount}</h2>
</DashboardLayout>
)
}
export default ProjectsPage;
While rendering a function call in ReactJS it throws this error
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
at h2
at div
at section
at main
at div
at div
at main
at div
at BodyWrapper (http://localhost:3000/static/js/main.chunk.js:210:3)
at DashboardLayout (http://localhost:3000/static/js/main.chunk.js:338:3)
at ProjectsPage
at Route (http://localhost:3000/static/js/vendors~main.chunk.js:39794:29)
at Switch (http://localhost:3000/static/js/vendors~main.chunk.js:39996:29)
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:39429:30)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:39049:35)
at Routes
at App
And the UI remains blank,
The problem the error message is referring to is here:
<h2>Projects Page {GetCount}</h2>
GetCount is a function (specifically, a component function). You're using it (not calling it) there. You want to use the component, like so:
<h2>Projects Page <GetCount/></h2>
I'd also suggest adding the missing dependency on the useEffect hook:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
// ^^^^^^^
You don't absolutely need it if count is the only state item and the component has no properties, but it's best practice to list the effect's dependencies so it isn't called too often.
You are trying to render:
<DashboardLayout>
<h2>Projects Page {GetCount}</h2>
</DashboardLayout>
However, GetCount is a function, hence:
Functions are not valid as a React child.
What you should do is:
<DashboardLayout>
<h2>Projects Page</h2>
<GetCount />
</DashboardLayout>

In React how can I append values to functional state when it is a dependency of useEffect without triggering another API call?

I have a todo list app which users can read and save items to. Here, Todo is a functional component that queries an API for the users current items on their list using the useEffect() hook. When a successful response is received the data is added to the component's state using useState() and rendered as part of the ItemList component.
When a user submits the form within the AddItemForm component a call back is fired that updates the state of newItem, a dependency of useEffect, which triggers another call to the API and a re-render of the component.
Logically, everything above works. However, it seems wrong to make an extra request to the API simply to receive the data that is already available but I can't find the correct pattern that would allow me to push the item available in useCallback to the items array without causing useEffect to loop infinitely yet still update the ItemList component.
Is there away for my app to push new date from the form submission to items array whilst updating the view and only calling the API once when the page loads?
function Todo() {
const [items, setItems] = useState();
const [newItem, setNewItem] = useState();
useEffect(() => {
fetch('https://example.com/items').then(
(response) => {
setItems(response.data.items);
}, (error) => {
console.log(error);
},
);
}, [newItem]);
const updateItemList = useCallback((item) => {
setNewItem(item);
});
return (
<>
<AddItemForm callback={updateItemList} />
<ItemList items={items} />
</>
);
}
function ItemList(props) {
const { items } = props;
return (
<div>
{ items
&& items.map((item) => <p>{item.description}</p>)}
</div>
);
}
Call API only on start by removing newItem from useEffect(...,[]).
Then add item to the items by destructuring in setItems:
const updateItemList = (item) => {
setItems([...items, item]);
}

useEffect hook not triggered when state is updated somewhere else

I`m having some problems trying to listen to state changes in this application. Basically I was expecting a useEffect hook to be fired after some state changed, but nothing at all is happening.
This is what I got
index.jsx
// this is a simplification.
// I actually have a react-router-dom's Router wrapping everything
// and App is a Switch with multiple Route components
ReactDOM.render(
<Provider>
<App>
</Provider>
, document.getElementById('root'));
useSession.jsx
export const useSession = () => {
const [session, setSession] = useState(null)
const login = useCallback(() => {
// do something
setSession(newSession)
})
return {
session,
setSession,
login
}
}
Provider.jsx
const { session } = useSession();
useEffect(() => {
console.log('Print this')
}, [session])
// more code ...
App.jsx
export function Login() {
const { login } = useSession();
return <button type="button" onClick={() => { login() }}>Login</button>
}
Well I have this Parent component Provider watching the session state, but when it is updated the useEffect of the provider is never called.
The useEffect is fired only if the setSession is called in the same hook/method. For example, if I import the setSession in the Provider and use it there, the useEffect will be fired; Or if I add a useEffect in the useSession method, it is gonna be fired when login updates the state.
The callback of useEffect is called but only once, when the component is mounted, but not when the state is changed.
How can I achieve this behavior? Having the Provider's useEffect fired whenever session is updated?
Thanks in advance!
I think this is just a bit of misunderstanding of how custom hooks work.Every instance of the component has its own state. Let me just show a simple example illustrating this.
function App () {
return (
<div>
<ComponentA/>
<ComponentB/>
<ComponentC/>
<ComponentD/>
</div>
)
}
function useCounter() {
const [counter, setCounter] = React.useState(0);
function increment() {
setCounter(counter+1)
}
return {
increment, counter, setCounter
}
}
function ComponentA() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button A</button>
ComponentA Counter: {counter}
</div>
)
}
function ComponentB() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button B</button>
ComponentB Counter: {counter}
</div>
)
}
function ComponentC() {
const { counter }= useCounter();
return (
<div>
ComponentC Counter: {counter}
</div>
)
}
function ComponentD() {
const [toggle, setToggle] = React.UseState(false);
const { counter }= useCounter();
React.useEffect(() => {
setInterval(()=>{
setToggle(prev => !prev);
}, 1000)
})
return (
<div>
ComponentD Counter: {counter}
</div>
)
}
From the above code if you can see that incrementing count by clicking Button Awill not affect the count instance of ComponentB.This is because every instance of the component has its own state. You can also see that clicking either buttons won't trigger ComponentC to rerender since they don't share the same instance. Even if i trigger rerender every one second like in Component D thus invoking useCounter the counter in ComponentD remains 0.
Solution
However there are multiple ways of making components share/listen to same state changes
You can shift all your state i.e [session state] to the Provider component and make it visible to other components by passing it via props.
You can move state to a global container Redux or simply use Context Api + UseReducer Hook here is an example
But since you are dealing with auth and session management, I suggest you persist the session state in local storage or session storage, and retrieve it whenever you need it. Hope that helped

When and why to useEffect

This may seem like a weird question, but I do not really see many use cases for useEffect in React (I am currently working on a several thousand-lines React codebase, and never used it once), and I think that there may be something I do not fully grasp.
If you are writing a functional component, what difference does it make to put your "effect" code in a useEffect hook vs. simply executing it in the body of the functional component (which is also executed on every render) ?
A typical use case would be fetching data when mounting a component : I see two approaches to this, one with useEffect and one without :
// without useEffect
const MyComponent = () => {
[data, setData] = useState();
if (!data) fetchDataFromAPI().then(res => setData(res));
return(
{data ? <div>{data}</div> : <div>Loading...</div>}
)
}
// with useEffect
const MyComponent = () => {
[data, setData] = useState();
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
return(
{data ? <div>{data}</div> : <div>Loading...</div>}
)
}
Is there an advantage (performance-wise or other) to useEffect in such usecases ?
I. Cleanup
What if your component gets destroyed before the fetch is completed? You get an error.
useEffect gives you an easy way to cleanup in handler's return value.
II. Reactions to prop change.
What if you have a userId passed in a props that you use to fetch data. Without useEffect you'll have to duplicate userId in the state to be able to tell if it changed so that you can fetch the new data.
The thing is, useEffect is not executed on every render.
To see this more clearly, let's suppose that your component MyComponent is being rendered by a parent component (let's call it ParentComponent) and it receives a prop from that parent component that can change from a user action.
ParentComponent
const ParentComponent = () => {
const [ counter, setCounter ] = useState(0);
const onButtonClicked = () => setCounter(counter + 1);
return (
<>
<button onClick={onButtonClicked}>Click me!</button>
<MyComponent counter={counter} />
</>
);
}
And your MyComponent (slightly modified to read and use counter prop):
const MyComponent = ({ counter }) => {
[data, setData] = useState();
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
return(
<div>
<div>{counter}</div>
{data ? <div>{data}</div> : <div>Loading...</div>}
</div>
)
}
Now, when the component MyComponent is mounted for the first time, the fetch operation will be performed. If later the user clicks on the button and the counter is increased, the useEffect will not be executed (but the MyComponent function will be called in order to update due to counter having changed)!
If you don't use useEffect, when the user clicks on the button, the fetch operation will be executed again, since the counter prop has changed and the render method of MyComponent is executed.
useEffect is handling the side effect of the problem. useEffect is the combination of componentDidMount and componentDidUpdate. every initial render and whenever props updated it will be executed.
For an exmaple:
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
Another example:
let's assume you have multiple state variables, the component will re-render for every state values change. But We may need to run useEffect in a specific scenario, rather than executing it for each state change.
function SimpleUseEffect() {
let [userCount, setUserCount] = useState(0);
let [simpleCount, setSimpleCount] = useState(0);
useEffect(() => {
alert("Component User Count Updated...");
}, [userCount]);
useEffect(() => {
alert("Component Simple Count Updated");
}, [simpleCount]);
return (
<div>
<b>User Count: {userCount}</b>
<b>Simple Count: {simpleCount}</b>
<input type="button" onClick={() => setUserCount(userCount + 1}} value="Add Employee" />
<input type="button" onClick={() => setSimpleCount(simpleCount + 1}} value="Update Simple Count" />
</div>
)
}
In the above code whenever your props request changed, fetchDataFromAPI executes and updated the response data. If you don't use useEffect, You need to automatically handle all type of side effects.
Making asynchronous API calls for data
Setting a subscription to an observable
Manually updating the DOM element
Updating global variables from inside a function
for more details see this blog https://medium.com/better-programming/https-medium-com-mayank-gupta-6-88-react-useeffect-hooks-in-action-2da971cfe83f

Categories