How do I convert this class component to a functional component?
What I am trying to achieve is to subscribe and unsubscribe from firebase using useEffect()
class PostsProvider extends Component {
state = { posts: [] }
unsubscribeFromFirestore = null;
componentDidMount = () => {
this.unsubscribeFromFirestore = firestore
.collection('posts')
.onSnapshot(snapshot => {
const posts = snapshot.docs.map(collectIdAndDocs);
this.setState({ posts });
});
}
componentWillUnmount = () => {
this.unsubscribeFromFirestore();
}
This is how I'd convert your component. You'd useState() to create your posts state and then a useEffect is pretty straightforward to move. The main thing you'd want to make sure of is that your dependency array is correct for it so it doesn't subscribe and unsubscribe too often (or not often enough).
function PostsProvider(){
const [posts,setPosts] = useState([]);
useEffect(() => {
const unsubscribeFromFirestore = firestore
.collection('posts')
.onSnapshot(snapshot => {
const posts = snapshot.docs.map(collectIdAndDocs);
setPosts(posts);
});
return () => {
unsubscribeFromFirestore();
}
}, [])
}
Related
I created a hook to manipulate users data and one function is listener for users collection.
In hook I created subscriber function and inside that hook I unsubscribed from it using useEffect.
My question is is this good thing or maybe unsubscriber should be inside screen component?
Does my approach has cons?
export function useUser {
let subscriber = () => {};
React.useEffect(() => {
return () => {
subscriber();
};
}, []);
const listenUsersCollection = () => {
subscriber = firestore().collection('users').onSnapshot(res => {...})
}
}
In screen component I have:
...
const {listenUsersCollection} = useUser();
React.useEffect(() => {
listenUsersCollection();
}, []);
What if I, by mistake, call the listenUsersCollection twice or more? Rare scenario, but your subscriber will be lost and not unsubscribed.
But generally speaking - there is no need to run this useEffect with listenUsersCollection outside of the hook. You should move it away from the screen component. Component will be cleaner and less chances to get an error. Also, easier to reuse the hook.
I prefer exporting the actual loaded user data from hooks like that, without anything else.
Example, using firebase 9 modular SDK:
import { useEffect, useMemo, useState } from "react";
import { onSnapshot, collection, query } from "firebase/firestore";
import { db } from "../firebase";
const col = collection(db, "users");
export function useUsersData(queryParams) {
const [usersData, setUsersData] = useState(undefined);
const _q = useMemo(() => {
return query(col, ...(queryParams || []));
}, [queryParams])
useEffect(() => {
const unsubscribe = onSnapshot(_q, (snapshot) => {
// Or use another mapping function, classes, anything.
const users = snapshot.docs.map(x => ({
id: x.id,
...x.data()
}))
setUsersData(users);
});
return () => unsubscribe();
}, [_q]);
return usersData;
}
Usage:
// No params passed, load all the collection
const allUsers = useUsersData();
// If you want to pass a parameter that is not
// a primitive or a string
// memoize it!!!
const usersFilter = useMemo(() => {
return [
where("firstName", "==", "test"),
limit(3)
];
}, []);
const usersFiltered = useUsersData(usersFilter);
As you can see, all the loading and cleaning-up logic is inside the hook, and the component that uses this hook is as clear as possible.
I have a functional component (App.js) where I want to fetch some initial data using useEffect.
useEffect(() => {
const init = async () => {
const posts = await getPosts(0, 3);
const newArticles = await getArticles(posts);
setArticles(() => [...articles, ...newArticles]);
};
init();
}, []);
then I want to pass the result to a child
<ArticleList articles={articles}></ArticleList>
but in the Article component I get an empty array when I try to console.log the props.
useEffect(() => {
console.log(props.articles);
setArticles(() => props.articles);
}, [props.articles]);
How can I solve this issue?
class Dashboard extends Component {
constructor(props) {
super(props)
this.state = {
assetList: [],
assetList1: [];
}
}
componentDidMount = async () => {
const web3 = window.web3
const LandData=Land.networks[networkId]
if (LandData) {
const landList = new web3.eth.Contract(Land.abi, LandData.address)
this.setState({ landList })
}
}
...
}
In this code the state for landlist is not defines in constructor but setState is used. If I have to convert the code to a function component, what will be the equivalent code?
In React class components, there existed a single state object and you could update it with any properties you needed. State in React function components functions a little differently.
React function components use the useState hook to explicitly declare a state variable and updater function.
You can use a single state, and in this case the functionality would be pretty similar, keeping in mind though that unlike the this.setState of class components, the useState
Example:
const Dashboard = () => {
const [state, setState] = React.useState({
assetList: [],
assetList1: []
});
useEffect(() => {
const web3 = window.web3;
const LandData = Land.networks[networkId];
if (LandData) {
const landList = new web3.eth.Contract(Land.abi, LandData.address);
setState(prevState => ({
...prevState,
landList,
}));
}
}, []);
return (
...
);
};
With the useState hook, however, you aren't limited to a single state object, you can declare as many state variables necessary for your code to function properly. In fact it is recommended to split your state out into the discrete chunks of related state.
Example:
const Dashboard = () => {
const [assetLists, setAssetLists] = React.useState({
assetList: [],
assetList1: []
});
const [landList, setLandList] = React.useState([]);
useEffect(() => {
const web3 = window.web3;
const LandData = Land.networks[networkId];
if (LandData) {
const landList = new web3.eth.Contract(Land.abi, LandData.address);
setLandList(landList);
}
}, []);
return (
...
);
};
const Dashboard = () => {
const [assetList, setAssetList] = useState([])
const [assetList1, setAssetList1] = useState([])
useEffect(() => {
const web3 = window.web3
const LandData = Land.networks[networkId]
if (LandData) {
const landList = new web3.eth.Contract(Land.abi, LandData.address)
setAssetList(landList)
}
}, [])
I have theorical question about custom hooks and use effect when redux is involved.
Let`s assume I have this code:
//MyComponent.ts
import * as React from 'react';
import { connect } from 'react-redux';
const MyComponentBase = ({fetchData, data}) => {
React.useEffect(() => {
fetchData();
}, [fetchData]);
return <div>{data?.name}</data>
}
const mapStateToProps= state => {
return {
data: dataSelectors.data(state)
}
}
const mapDispatchToProps= {
fetchData: dataActions.fetchData
}
export const MyComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponentBase);
This works as expected, when the component renders it does an async request to the server to fetch the data (using redux-thunk). It initializes the state in the reduces, and rerender the component.
However we are in the middle of a migration to move this code to hooks. Se we refactor this code a little bit:
//MyHook.ts
import { useDispatch, useSelector } from 'react-redux';
import {fetchDataAction} from './actions.ts';
const dataState = (state) => state.data;
export const useDataSelectors = () => {
return useSelector(dataState);
}
export const useDataActions = () => {
const dispatch = useDispatch();
return {
fetchData: () => dispatch(fetchDataAction)
};
};
//MyComponent.ts
export const MyComponent = () => {
const data = useDataSelectors()>
const {fetchData} = useDataActions();
React.useEffect(() => {
fetchData()
}, [fetchData]);
return <div>{data?.name}</data>
}
With this change the component enters in an infite loop. When it renders for the first time, it fetches data. When the data arrives, it updates the store and rerender the component. However in this rerender, the useEffect says that the reference for fetchData has changed, and does the fetch again, causing an infinite loop.
But I don't understand why the reference it's different, that hooks are defined outside the scope of the component, they are not removed from the dom or whateverm so their references should keep the same on each render cycle. Any ideas?
useDataActions is a hook, but it is returning a new object instance all the time
return {
fetchData: () => dispatch(fetchDataAction)
};
Even though fetchData is most likely the same object, you are wrapping it in a new object.
You could useState or useMemo to handle this.
export const useDataActions = () => {
const dispatch = useDispatch();
const [dataActions, setDataActions] = useState({})
useEffect(() => {
setDataActions({
fetchData: () => dispatch(fetchDataAction)
})
}, [dispatch]);
return dataActions;
};
first of all if you want the problem goes away you have a few options:
make your fetchData function memoized using useCallback hook
don't use fetchData in your useEffect dependencies because you don't want it. you only need to call fetchData when the component mounts.
so here is the above changes:
1
export const useDataActions = () => {
const dispatch = useDispatch();
const fetchData = useCallback(() => dispatch(fetchDataAction), []);
return {
fetchData
};
};
the 2nd approach is:
export const MyComponent = () => {
const data = useDataSelectors()>
const {fetchData} = useDataActions();
React.useEffect(() => {
fetchData()
}, []);
return <div>{data?.name}</data>
}
The way I cache data in Class component is like below :
1. make async API call in componentDidMount
2. get API response and dispatch data through redux
3. use response data by mapping state to prop
What I want to do is caching data right after you get API response with mapped redux state value
inside of useEffect in function component
(
It works on class component. but I'm wondering how should I make it work in function component)
export class MyClassComponent extends React.Component {
private readonly _myCachedData = {
'data1': {}
}
public async componentDidMount() {
await this.loadAsyncData();
}
private loadAsyncData() {
const { makeAPICall } = this.props
await makeAPICall();
return this._myCachedData.data1 = this.props.data1FromReduxConnect;
}
}
export const mapStateTopProps = (state) => {
const { data1FromReduxConnect } = state;
return data1FromReduxConnect;
}
...
What I have tried :
export const MyFunctionComponent = props => {
const { data1FromReduxConnect } = props;
const myCachedData = React.useRef();
const loadAsyncData = () => {
const { makeAPICall } = this.props
await makeAPICall();
return myCachedData.current = data1FromReduxConnect;
}
React.useEffect(()=> {
await loadAsyncData();
})
}
export const mapStateTopProps = (state) => {
const { data1FromReduxConnect } = state;
return data1FromReduxConnect;
}
I was only able to get the previous value ofdata1FromReduxConnect unlike class component did get updated value this.props.data1FromReduxConnect after API call
Not sure if I should just keep class component for it, or is there a way to deal with this issue!
Thanks!!
I don't think that is the right way to use the useRef hook. Similar to React's class components' createRef(), it is actually used to access the DOM in functional components.
If the HTTP request happens only once when MyFunctionComponent is initialised, we can use [] as the second argument in the useEffect hook which will cause this effect to be run only once. In addition, we will need to make use of useState hook to keep track of the component's state which is to be updated with the values from the redux store.
export const MyFunctionComponent = props => {
const { data1FromReduxConnect } = props;
const [ myData, setMyData ] = useState();
const loadAsyncData = async() => {
const { makeAPICall } = this.props
await makeAPICall();
}
useEffect(()=> {
async function getData() {
await loadAsyncData();
}
getData();
// do the rest to get and store data from redux
setMyData(data1FromReduxConnect);
}, [])
}