1

Hello I am new to react native and cant figure out this problem please help me I am working in a project .In my project I want all the tabs and tab content to be autogenerated dynamically from my firebase database

Data Structure

I need that all these nodes (Living room, kitchen, bedroom, etc.) to be the names of the tab and all tabs should show its own content directly from firebase like Living room tab will show app_1 and app_2 similarly Kitchen and bedroom will also autogenerate these directly from database

Like this image:

Output required

const HorScrollView = () => {
  const [homeId, setHomeId] = useState(0);
  const [roomList, setRoomList] =useState(["Loading Rooms...."]);

  const homeidProvider = () => {
    const user = auth().currentUser;
    return new Promise((resolve,reject) => {
      
      database().ref(`/USERS/${user.uid}/home_id`).once('value').then(snapshot => {
        resolve(snapshot.val());
      });
      
    });
  };

  const roomListProvider = ()=>{
    return new Promise((resolve,reject) => {
      
      database().ref(`/HOMES/${homeId}/rooms`).once('value').then(snapshot => {
        resolve(snapshot.val());
      });
      
    });

  }
  

  const callMe = async () => {

    let home_id = await homeidProvider();
    setHomeId(home_id);

    let roomdata = await roomListProvider();  
    setRoomList((Object.keys(roomdata)).reverse());
 
    
    }
    callMe();
  
  return (
    <View style={styles.scrollViewContainer} >
    <ScrollView horizontal>
     {roomList.map((roomlist) => (
              <Pressable key={roomlist}>
                <Text style={styles.scrollViewText} >{roomlist}
                </Text>
                </Pressable>

     ))}
     </ScrollView>
     </View>
  );
};
Dharmaraj
  • 47,845
  • 8
  • 52
  • 84

2 Answers2

0

There's a UI library called antd which can help you do this. Use a <Table/> tag for this, then as props pass the value of the columns attribute to be the titles that are coming through, and then the dataSource attribute will be the data under those specific columns. Read the documentation for more.

Odasaku
  • 177
  • 6
  • 17
0

While you can use the <Tabs> component from antd to achieve what you want, your current code has some bugs regarding how it handles user state and the asynchronous calls.

Taking a look at these lines:

const homeidProvider = () => {
  const user = auth().currentUser;
  return new Promise((resolve, reject) => {
    database()
      .ref(`/USERS/${user.uid}/home_id`)
      .once('value')
      .then(snapshot => {
        resolve(snapshot.val());
      });
    });
  };

Here there are two main problems:

  • You make use of auth().currentUser but this isn't guaranteed to contain the user object that you expect as it may still be resolving with the server (where it will be null) or the user may be signed out (also null).
  • You incorrectly chain the promise by wrapping it in a Promise constructor (known as the Promise constructor anti-pattern) where the errors of the original promise will never reach the reject handler leading to crashes.

To fix the user state problem, you should make use of onAuthStateChanged and look out for when the user signs in/out/etc.

function useCurrentUser() {
  const [user, setUser] = useState(() => auth().currentUser || undefined);
  const userLoading = user === undefined;
  
  useEffect(() => auth().onAuthStateChanged(setUser), []);
  
  // returns [firebase.auth.User | null, boolean]
  return [user || null, userLoading];
}

// in your component
const [user, userLoading] = useCurrentUser();

To fix the PCAPs, you'd use:

const homeidProvider = () => {
  return database()
    .ref(`/USERS/${user.uid}/home_id`)
    .once('value')
    .then(snapshot => snapshot.val());
};

const roomListProvider = () => {
  return database()
    .ref(`/HOMES/${homeId}/rooms`)
    .once('value')
    .then(snapshot => snapshot.val());
}

Because these functions don't depend on state changes, you should place them outside your component and pass the relevant arguments into them.

Next, these lines should be inside a useEffect call where error handling and unmounting the component should be handled as appropriate:

const callMe = async () => {
  let home_id = await homeidProvider();
  setHomeId(home_id);

  let roomdata = await roomListProvider();  
  setRoomList((Object.keys(roomdata)).reverse());    
}
callMe();

should be swapped out with:

useEffect(() => {
  if (userLoading)  // loading user state, do nothing
    return; 
  if (!user) {      // user is signed out, reset to empty state
    setHomeId(-1);
    setRoomList([]);
    return;
  }
  
  let disposed = false;

  const doAsyncWork = async () => {
    const newHomeId = await getUserHomeId(user.uid);
    const roomsData = await getHomeRoomData(newHomeId);
    
    const newRoomList = [];
    snapshot.forEach(roomSnapshot => {
      const title = roomSnapshot.key;
      const apps = [];
      roomSnapshot.forEach(appSnapshot => {
        apps.push({
          key: appSnapshot.key,
          ...appSnapshot.val()
        });
      });
      newRoomList.push({
        key: title,
        title,
        apps
      });
    });

    if (disposed) // component unmounted? don't update state
      return;

    setHomeId(newHomeId);
    setRoomList(newRoomList);
  }

  doAsyncWork()
    .catch(err => {
      if (disposed) // component unmounted? silently ignore
        return;

      // TODO: Handle error better than this
      console.error("Failed!", err);
    });

  return () => disposed = true;
}, [user, userLoading]); // rerun only if user state changes

You should also track the status of your component:

Status Meaning
"loading" data is loading
"error" something went wrong
"signed-out" no user logged in
"ready" data is ready for display

Rolling this together:

import { Tabs, Spin, Alert, Card } from 'antd';
const { TabPane } = Tabs;
const { Meta } = Card;

function useUser() {
  const [user, setUser] = useState(() => auth().currentUser || undefined);
  const userLoading = user === undefined;
  
  useEffect(() => auth().onAuthStateChanged(setUser), []);
  
  return [user || null, userLoading];
}

const getUserHomeId = (uid) => {
  return database()
    .ref(`/USERS/${uid}/home_id`)
    .once('value')
    .then(snapshot => snapshot.val());
};

const getHomeRoomData = (homeId) => {
  return database()
    .ref(`/HOMES/${homeId}/rooms`)
    .once('value')
    .then(snapshot => snapshot.val());
}

const RoomView = () => {
  const [homeId, setHomeId] = useState(0);
  const [status, setStatus] = useState("loading")
  const [roomList, setRoomList] = useState([]);
  
  const [user, userLoading] = useUser();
  
  useEffect(() => {
    if (userLoading)  // loading user state, do nothing
      return; 
    if (!user) {      // user is signed out, reset to empty state
      setHomeId(-1);
      setRoomList([]);
      setStatus("signed-out");
      return;
    }
    
    let disposed = false;
    setStatus("loading");

    const doAsyncWork = async () => {
      const newHomeId = await getUserHomeId(user.uid);
      const roomsData = await getHomeRoomData(newHomeId);
      
      const newRoomList = [];
      snapshot.forEach(roomSnapshot => {
        const title = roomSnapshot.key;
        const apps = [];
        roomSnapshot.forEach(appSnapshot => {
          apps.push({
            key: appSnapshot.key,
            ...appSnapshot.val()
          });
        });
        newRoomList.push({
          key: title,
          title,
          apps
        });
      });

      if (disposed) // component unmounted? don't update state
        return;

      setHomeId(newHomeId);
      setRoomList(newRoomList);
      setStatus("ready");
    }

    doAsyncWork()
      .catch(err => {
        if (disposed) // component unmounted? silently ignore
          return;

        // TODO: Handle error better than this
        console.error("Failed!", err);
        setStatus("error");
      });

    return () => disposed = true;
  }, [user, userLoading]); // rerun only if user state changes
  
  switch (status) {
    case "loading":
      return <Spin tip="Loading rooms..." />
    case "error":
      return <Alert
        message="Error"
        description="An unknown error has occurred"
        type="error"
      />
    case "signed-out":
      return <Alert
        message="Error"
        description="User is signed out"
        type="error"
      />
  }
  
  if (roomList.length === 0) {
    return <Alert
      message="No rooms found"
      description="You haven't created any rooms yet"
      type="info"
    />
  }
  
  return (
    <Tabs defaultActiveKey={roomList[0].key}>
      {
        roomList.map(room => {
          let tabContent;
          if (room.apps.length == 0) {
            tabContent = "No apps found in this room";
          } else {
            tabContent = room.apps.map(app => {
              <Card style={{ width: 300, marginTop: 16 }} key={app.key}>
                <Meta
                  avatar={
                    <Avatar src="https://via.placeholder.com/300x300?text=Icon" />
                  }
                  title={app.name}
                  description={app.description}
                />
              </Card>
            });
          }
        
          return <TabPane tab={room.title} key={room.key}>
            {tabContent}
          </TabPane>
        })
      }
    </Tabs>
  );
};
samthecodingman
  • 23,122
  • 4
  • 30
  • 54