React native context api not passing value properly, null is not an object (evaluating 'user.uid') - javascript

I want to retrieve a field value of a document in Users collection by referencing it via the where condition from Firestore. I use the context api to pass the user object of the logged in user in my app. I get this error that user.uid is null. I can't spot where the mistake is. I have added the relevant piece of code.
EditProfile.js
const EditProfile = () => {
const { user } = React.useContext(AuthContext);
const [name, setName] = React.useState();
React.useEffect(() => {
const userid = user.uid;
const name = getFieldValue("Users", userid);
setName(name);
}, []);
};
export default EditProfile;
passing and getting value via context
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = React.useState(null);
return (
<AuthContext.Provider
value={{
user,
setUser,
}}
>
{children}
</AuthContext.Provider>
);
};
const AppStack = () => {
return (
<AuthProvider>
<BottomTab.Navigator>
<BottomTab.Screen
name="ProfileStack"
component={ProfileStack}
/>
</BottomTab.Navigator>
</AuthProvider>
);
};
export default AppStack;
ProfileStack.js
export const ProfileStack = ({ navigation }) => {
return (
<Stack.Navigator>
<Stack.Screen
name="Profile"
component={Profile}
/>
<Stack.Screen
name="EditProfile"
component={EditProfile}
/>
</Stack.Navigator>
);
};
getFieldValue function
export const getFieldValue = (collection, userid) => {
firestore()
.collection(collection)
.where("userid", "==", userid)
.get()
.then((querySnapshot) => {
if (querySnapshot.size === 0) {
return "";
}
if (querySnapshot.size === 1) {
const { name } = querySnapshot[0].data();
return name;
}
})
.catch((e) => console.log(e));
};
Routing file
const Routes = () => {
// Set an initializing state whilst Firebase connects
const [initializing, setInitializing] = React.useState(true);
const { user, setUser } = React.useContext(AuthContext);
// Handle user state changes
const onAuthStateChanged = (user) => {
setUser(user);
if (initializing) setInitializing(false);
};
React.useEffect(() => {
RNBootSplash.hide();
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
if (initializing) return null;
return (
<NavigationContainer>
{user ? <AppStack /> : <AuthStack />}
</NavigationContainer>
);
};
export default Routes;

Related

Firebase Auth - null is not an object

After logging in, I'm automatically moving to Tabs screen.
But Tabs.js shows me this error: null is not an object (evaluation 'currentUser.uid').
And on every other screen that in Tabs shows this error.
Code:
const databaseUserName = firestore()
.collection('Users')
.doc(currentUser.uid)
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
setName(documentSnapshot.data().name);
setGroupID(documentSnapshot.data().group);
}
});
Routes.js:
const Routes = () => {
// const [user, setUser] = useState();
const { user, setUser } = useContext(AuthContext);
const [initializing, setInitializing] = useState(true);
React.useEffect(() => {
StatusBar.setBackgroundColor('#FF573300');
StatusBar.setTranslucent(true)
}, []);
const onAuthStateChanged = (user) => {
setUser(user);
if (initializing) setInitializing(false);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
if (initializing) return null;
return (
<NavigationContainer>
{user ? <AppStack /> : <AuthStack />}
</NavigationContainer>
);
}
I'm initializing the user, I really don't know how it can be null

React Navigation does not update screen upon login

I have a simple application, and I want that on the first launch it should open a setup screen. After the user has finished the setup, and pressed the button then the values are stored with AsyncStorage. Then the React Navigation should react to this and push the user to the normal flow (Home screen). I have done everything, but my problem is that the user is not automatically pushed to the Home screen. The user has to restart the application in order to continue. This is my code:
App.js
const Stack = createStackNavigator();
function App() {
const [isSet, setIsSet] = useState(true);
async function checkSetup() {
const myValue = await AsyncStorage.getItem('#myValue');
if (myValue === null) {
setIsSet(false);
} else {
setIsSet(true);
}
}
useEffect(() => {
checkSetup();
}, []);
return (
<View style={{flex: 1, backgroundColor: '#272D2E'}}>
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
{isSet ? (
<>
<Stack.Screen name="Start" component={Start} />
<Stack.Screen name="Screen2" component={DocumentScan} />
<Stack.Screen name="Screen3" component={MailPhone} />
</>
) : (
<>
<Stack.Screen name="Setup" component={Setup} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
</View>
);
}
export default App;
And in my Setup.js
function Setup({}) {
const navigation = useNavigation();
const [myValue, setMyValue] = useState('');;
const setStorage = async (name, value) => {
try {
await AsyncStorage.setItem(name, value);
} catch (error) {
console.log(error);
}
};
const goToNextStep = useCallback(async () => {
await setStorage('#myValue', myValue);
}, [myValue);
The value myValue is set from a TextInput. Does anyone know how to fix this?
Because checkSetup is not a hook and React does not track states of AsyncStorage.
To make AsyncStorage value as a React state, you can use custom hooks like the following:
const useAsyncStorage = (key) => {
const [value, setValue] = useState(null);
const set = useCallback(async (val) => {
await AsyncStorage.setItem(key, val);
setValue(val);
}, []);
const load = useCallback(async () => {
setValue(await AsyncStorage.getItem(key));
}, [key]);
useEffect(() => {
load();
}, [load]);
return [
value,
set
];
};
Then you can use this hook in your components:
App.js
const [myValue] = useAsyncStorage('#myValue');
useEffect(() => {
setIsSet(myValue !== null);
}, [myValue]);
Setup.js
const [, setMyValue] = useAsyncStorage('#myValue');
const goToNextStep = useCallback(async () => {
setMyValue(myValue);
}, [myValue, setMyValue]);
After set storage you can write following snippet.
history.pushState(state, title, url)

useState won't update the state when I set it in react

I need to render a component that has a route using react router. the first component has a button that when clicked needs to render another component that has state passed in from the first component. The page redirects but doesn't load. All of the data from the first component I want is passed in but it wont set state when I use setProfile(p). All the other console.log()s in the member component show all the data I expect but it won't set the state with this data.
import {useLocation} from "react-router-dom";
const Member = (props)=> {
const [user, setUser] = useState({});
const [profile, setProfile] = useState({});
const [user, setUser] = useState({});
const { state } = useLocation();
const [profile, setProfile] = useState({});
const dispatch = useDispatch();
const [list, setList] = useState([]);
const [posts, setPosts] = useState([]);
const [snInstance, setsnInstance] = useState({});
// run effect when user state updates
useEffect(() => {
const doEffects = async () => {
try {
// const p = await incidentsInstance.usersProfile(state.user, { from: accounts[0] });
// const a = await snInstance.getUsersPosts(state.user, { from: accounts[0] });
if (state && state.user) {
setUser(state.user);
}
const accounts = await MyWeb3.getInstance().getAccounts();
setAccounts(accounts);
console.log(accounts)
const incidents = MyWeb3.getInstance().getContract(Incidents)
const incidentsInstance = await MyWeb3.getInstance().deployContract(incidents);
const sn = MyWeb3.getInstance().getContract(SocialNet)
const snInstance = await MyWeb3.getInstance().deployContract(sn);
setsnInstance(snInstance);
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
const a = await snInstance.getUsersPosts(pro, { from: accounts[0] });
console.log(a)
console.log(p)
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile, state]);
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div class="container">
<a target="_blank">Name : {profile.name}</a>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
{p.message}
</tr>})}
</div>
)
}
export default Member;
This is the parent component I want to redirect from
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
I have this component working when I don't have a dynamic route that needs data passing in from the parent component It's redirecting from.
My routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
This is the reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
It doesn't make any sense that you would dispatch({ type: 'ADD_MEMBER', response: member }) with a member object that came from the publisher property of a post. That info is already in your state. You probably need to be normalizing your state better so that you can select it where you need it.
You want to use the Link component from react-router-dom to navigate to a member's profile page. Your Route should render the correct profile based on an id or username property in the URL. Don't pass through the data when you redirect, just go to the correct URL. On that Member page you can get the user from the state by looking up the id.
In Posts:
<Link to={`/member/${p.publisher.id}`}><button>Profile</button></Link>
In Routes:
<Route path="/member/:id" component={Member} />
In Member:
const Member = () => {
const { id } = useParams();
const profile = useSelector((state) =>
state.socialNetworkContract.members.find((user) => user.id === id)
);
const dispatch = useDispatch();
useEffect(() => {
const doEffects = async () => {
if ( ! profile ) {
dispatch(loadUser(id));
}
};
doEffects();
}, [dispatch, profile, id]);

React Native setState/useState of an Object

I am new to React Native and don't quite understand the concept of initial states of an object and updating the state when I have more than one property to set.
the error (edit #2):
Objects are not valid as a React child (found: object with keys {userRole}). If you meant to render a collection of children, use an array instead.
App.js
const initialLoginState = {
userRole: null,
userId: null,
};
const [user, setUser] = useState(initialLoginState);
const [isReady, setIsReady] = useState(false);
const restoreUser = async () => {
const user = await authStorage.getUser();
if (user) setUser(user);
};
if (!isReady) {
return (
<AppLoading
startAsync={restoreUser}
onFinish={() => setIsReady(true)}
onError={console.warn}
/>
);
}
//render
return (
<AuthContext.Provider value={{ user, setUser }}>
<NavigationContainer>
{user.userRole ? <ViewTest /> : <AuthNavigator />}
</NavigationContainer>
</AuthContext.Provider>
);
useAuth which updates the user when I received the data:
const logIn = (data, authToken) => {
setUser((prevState) => ({
userRole: {
...prevState.userId,
userRole: data.USERROLE,
},
}));
authStorage.storeToken(data.USERID);
};
You don't need prevState in functional component. user is the prevState before you set new state
const logIn = (data, authToken) => {
setUser({...user, userRole: data.USERROLE});
authStorage.storeToken(data.USERID);
};
Objects are not valid as a React child (found: object with keys {userRole}). If you meant to render a collection of children, use an array instead.
<AuthContext.Provider value={{ user, setUser }}> // <---- the problem is here
<NavigationContainer>
{user.userRole ? <ViewTest /> : <AuthNavigator />}
</NavigationContainer>
</AuthContext.Provider>
I'm not sure what AuthContext.Provider is, but it's trying to render the object(User) as html react elements, make sure you know what sort of data the value prop of that component takes.
I was able to get the right answer with the help of #P.hunter, #Erdenezaya and #Federkun.
The problem was in the state init and setUser().
App.js
const initialLoginState = {
userRole: null,
userId: null,
};
const [user, setUser] = useState({
initialLoginState,
});
const [isReady, setIsReady] = useState(false);
const restoreUser = async () => {
const user = await authStorage.getUser();
if (user) setUser(user);
};
if (!isReady) {
return (
<AppLoading
startAsync={restoreUser}
onFinish={() => setIsReady(true)}
onError={console.warn}
/>
);
}
//syntax error was found in {user.userRole}
return (
<AuthContext.Provider value={{ user, setUser }}>
<NavigationContainer>
{user.userRole ? <ViewTest /> : <AuthNavigator />}
</NavigationContainer>
</AuthContext.Provider>
);
Context functionality for setting the user had to be done like this:
export default useAuth = () => {
const { user, setUser } = useContext(AuthContext);
const logIn = (data, authToken) => {
setUser({ ...user, userRole: data.USERROLE });
authStorage.storeToken(data.USERID);
};
const logOut = () => {
setUser({ ...user, userRole: null });
authStorage.removeToken();
};
return { user, logIn, logOut };
};
Thank you all for your help!

React Native: Saving array via AsyncStorage and retrieving it

I am trying to get an array of objects from my Redux-Store state called user and save it to async storage and use useState with the response to set the state before I retrieve it and view it with the FlatList however I am getting an error along the lines of Warning: Can't perform a React state update on an unmounted component. The user details is being set to the redux store in another component and then being retrieved from the current component I am displaying. Please could I get your help . I would really appreciate it. Thank you in advance!!!
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
const [getUser, setGetUser] = useState()
const saveUserAsync = async () => {
await AsyncStorage.setItem('user', JSON.stringify(user))
}
saveUserAsync()
AsyncStorage.getItem('user').then(response => {
setGetUser(response)
})
return (
<FlatList
data={getUser}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser
You can use useEffect hook to solve this problem.
IS_MOUNTED variable will track if component is mounted or not.
let IS_MOUNTED = false; // global value
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
const [getUser, setGetUser] = useState()
const saveUserAsync = async () => {
await AsyncStorage.setItem('user', JSON.stringify(user))
}
AsyncStorage.getItem('user').then(response => {
if(IS_MOUNTED)
{
setGetUser(JSON.parse(response));
}
});
useEffect(() => {
IS_MOUNTED = true;
saveUserAsync();
return (() => {
IS_MOUNTED = false;
})
},[])
return (
<FlatList
data={getUser}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser
import { useEffect } from "react"
let isMount = true
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
// const [getUser, setGetUser] = useState()
// useEffect(() => {
// const saveUserAsync = async () => {
// await AsyncStorage.setItem('user', JSON.stringify(user))
// const response = await AsyncStorage.getItem('user')
// if (isMount)
// setGetUser(JSON.parse(response))
// }
// saveUserAsync()
// }, [user])
// useEffect(() => {
// isMount = true
// return () => {
// isMount = false
// }
// }, [])
return (
<FlatList
// data={getUser}
data={user}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser

Categories

Resources