React Native Context with Array - react-native

I am new to react native and trying to create React Native context which will store array of objects. Context looks something like this:
import React, {useState, useCallback} from 'react';
export const NotificationContext = React.createContext({
notifications: [],
updateNotifications: () => {},
});
export default function NotificationContextProvider({children}) {
const [notifications, setNotifications] = useState([]);
const updateNotifications = n => {
notifications.push(n);
setNotifications(notifications);
};
const contextValue = {
notifications,
updateNotifications: useCallback(n => updateNotifications(n), []),
};
return (
<NotificationContext.Provider value={contextValue}>
{children}
</NotificationContext.Provider>
);
}
Now when I am trying to access the context, I am not getting the updated array value as desired.
var context = useContext(NotificationContext);
useEffect(() => {
(async () => {
console.log('Before', context);
console.log('Notification value', context.notifications);
context.updateNotifications([1]);
console.log('After', context);
})();
}, []);

I think you should pass dependencies variable context in useEffect

Related

Context API dispatch not called with onEffect while using expo-splash-screen

When I am trying to use the dispatch function recieved with the useContext hook I cannot get the change the content of the data inside the context. It looks like as if the call wasn't even made, when I try to log something inside the conext's reducer it doesn't react. When I try to call it from other components, it works just fine.
Sorry if it's not clean enough, I'm not too used to ask around here, if there's anything else to clarify please tell me, and I'll add the necessary info, I just don't know at the moment what could help.
import { QueryClient, QueryClientProvider } from "react-query";
import LoginPage from "./src/pages/LoginPage";
import { UserDataContext, UserDataProvider } from "./src/contexts/UserData";
import { useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
import { useContext } from "react";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import { useCallback } from "react";
import { UserData } from "./src/interfaces";
SplashScreen.preventAutoHideAsync();
const queryClient = new QueryClient();
export default function App() {
const [appReady, setAppReady] = useState<boolean>(false);
const { loggedInUser, dispatch } = useContext(UserDataContext);
useEffect(() => {
async function prepare() {
AsyncStorage.getItem("userData")
.then((result) => {
if (result !== null) {
console.log(loggedInUser);
const resultUser: UserData = JSON.parse(result);
dispatch({
type: "SET_LOGGED_IN_USER",
payload: resultUser,
});
new Promise((resolve) => setTimeout(resolve, 2000));
}
})
.catch((e) => console.log(e))
.finally(() => setAppReady(true));
}
if (!appReady) {
prepare();
}
}, []);
const onLayoutRootView = useCallback(async () => {
if (appReady) {
await SplashScreen.hideAsync();
}
}, [appReady]);
if (!appReady) {
return null;
}
return (
<>
<UserDataProvider>
<QueryClientProvider client={queryClient}>
<LoginPage onLayout={onLayoutRootView} />
</QueryClientProvider>
</UserDataProvider>
</>
);
}
I'm thinking I use the context hook too early on, when I check the type of the dispatch function here it says it's [Function dispatch], and where it works it's [Function bound dispatchReducerAction].
I think the problem might come from me trying to call useContext before the contextprovider could render, but even when I put the block with using the dispatch action in the onLayoutRootView part, it didn't work.

How to fetch data from Amplify's GraphQL API and store it in a React's context variable

I am using React native and I have a context variable post, it has an attribute called name and I have defined a function called onChange to set it.
import React, { useState } from "react";
const PostContext = React.createContext({
content: "",
onChange: (newPostContent: string) => {},
});
export const PostContextProvider = ({ children }) => {
const [content, setContent] = useState("");
const postChangeHandler = (newPostContent: string) => {
setContent(newPostContent);
};
return (
<PostContext.Provider
value={{ content, onChange: postChangeHandler }}
>
{children}
</PostContext.Provider>
);
};
export default PostContext;
Now I have a page on which I want to fetch a post from Amplify's GraphQL API and set its content to my context variable, so I can use it on other pages.
import React, { useEffect, useContext } from "react";
import { API, graphqlOperations} from "aws-amplify";
import PostContext from "./context/post-context";
const post = useContext(PostContext);
const fetchPost = async () => {
const {data: {getPost: { postContent },},} = await API.graphql(
graphqlOperation(`
query GetPost {
getPost(id: "${some post Id}") {
content
}
}
`)
);
post.onChange(postContent)
}
useEffect(()=>{
fetchPost()
}, [])
useEffect(()=>{
console.log(post.content)
}, [post])
What I expect is that in the async function, the execution is blocked until postContent (because of the await and then it's value is assigned to the context variable, or its update is schedualed (that's why I have also included a useEffect to console.log the value of post.content. But it is not updated and its value remains an empty screen. Can somebody help me with this? I am learning React native how this work, so a detailed answer that lets me know what I am doing wrong is appreciated.

How to write Animated.Value.addListener in functional components?

I know in class components we use addListener in this way:
const Animated.Value= new Animated.Value(0);
Animated.Value.addListener((value)=>this.value=value;)
I wondering how should i convert Animated.Value.addListener in functional components?and second question: should i put addListener in useEffect hook?
In my case, I did as below
import React, { useRef, useEffect } from "react";
const AnimationBox = (props) => {
const pan: any = useRef(new Animated.ValueXY(props.pan)).current;
useEffect(() => {
pan.addListener((value) => {
console.log(value);
});
return () => {
pan.removeAllListeners();
};
}, []):
}

React native: useSelector redux is empty

I am new and i want to using react native to create android application so after creating project i installed redux and redux thunk and do every config that redux wants to work .
I create a action file :
export const GETSURVEYOR = 'GETSURVEYOR';
const URL = "http://192.168.1.6:3000/";
export const fetchSurveyor = () => {
return async dispatch => {
const controller = new AbortController();
const timeout = setTimeout(
() => { controller.abort(); },
10000,
);
const response = await fetch(`${URL}GetSurveyorList`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}),
signal: controller.signal
});
clearTimeout(timeout);
const resData = await response.json();
dispatch({
type: GETSURVEYOR,
surveyorList: resData.SurveyorList
});
}
}
after that i create reducer to handle this data :
import {GETSURVEYOR} from '../actions/surveyor'
const initialState = {
surveyorList: []
}
export default (state = initialState, action) => {
switch (action.type) {
case GETSURVEYOR:
return {
...state,
surveyorList: action.surveyorList
};
Now i am using by useSelector, useDispatch from 'react-redux .
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
await dispatch(surveyorActions.fetchSurveyor());
console.log('run use Callback');
console.log('returned :', survayers );
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, [dispatch]);
return [loadSurvayer, surveyorCount];
}
When for first time this paged is rendered , of course that survayers is empty but after fetch data in action and set state to reducer , survayers nut to be an empty.
But i get empty still ? I am sure data is fetched from services but i got empty from survayers ?
LOG Running "RNAuditMngm" with {"rootTag":1}
LOG run use Callback
LOG returned : []
LOG run use Callback
LOG returned : []
if i change my useEffect code to this:
useEffect(() => {
loadSurvayer();
}, [dispatch,survayers]);
I fall to loop !!!! How could i change code without loop?
I think everything works fine, but you're not using the console.log in the right place. When you run the loadSurvayer the survayers is empty. It is empty even the second time because you are not passing it as a dependency in the useEffect hook. And like you said, if you pass it as a dependency, then it causes an infinite loop, and that's right because whenever the survayers change, that function will be called again and so on.
So, here's what you have to do:
Remove the dispatch dependency from your useEffect hook.
Change the console.log's outside of the loadSurvayer function.
Remove the await from the dispatch call because it is synchronous.
Here's how to modify your code to work the right way:
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
return [loadSurvayer, surveyorCount];
}
Improvement bonus and suggestion: remove the surveyorCount state variable because you don't actually need it as you can return the count directly.
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
// Remove the `surveyorCount`
//const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
//return [loadSurvayer, surveyorCount];
return [loadSurvayer, survayers.length]; // <-- Use `survayers.length` instead of `surveyorCount`
}
In useSelector shouldn't you read surveyerList like this state.surveyorList ?. your state doesn't have any object named surveyor but you are currently reading like state.surveyor.surveyorList

How to pass the value of useState to BackHandler.addEventListener

I'm using React Hooks and when I create an event listener for android back press handler, the state inside the callback function handler is empty!
In class components it works fine!
'use strict';
import React, { useState, useEffect } from 'react';
import { BackHandler } from 'react-native';
import TextInput from '../../../../components/TextInput';
export default function Foo() {
const [comment, setComment] = useState('');
useEffect(() => {
const handler = BackHandler.addEventListener(
'hardwareBackPress',
handleValidateClose
);
return () => handler.remove();
}, []);
const handleValidateClose = () => {
/* Here is empty */
console.log(comment);
};
return <TextInput onChangeText={setComment} value={comment} />;
}
The value should be the useState changed
handleValidateClose should be on your dependency array.
You can use your function outside the useEffect but should use with useCallback.
const handleValidateClose = useCallback(() => {
console.log(comment);
return true;
}, [comment]);
useEffect(() => {
const handler = BackHandler.addEventListener(
'hardwareBackPress',
handleValidateClose,
);
return () => handler.remove();
}, [handleValidateClose]);
You can also move the definition to inside useEffect, and add a comment as a dependency.
useEffect(() => {
const handleValidateClose = () => {
console.log(comment);
return true;
};
const handler = BackHandler.addEventListener(
'hardwareBackPress',
handleValidateClose,
);
return () => handler.remove();
}, [comment]);
To clean things up, create a useBackHandler.
export default function useBackHandler(handler) {
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', handler);
return () => {
BackHandler.removeEventListener('hardwareBackPress', handler);
};
});
}
And use it like this:
const handleValidateClose = () => {
console.log(comment);
return true;
};
useBackHandler(handleValidateClose);
Please config your project to use the eslint-plugin-react-hooks. That's a common pitfalls that the plugin would help you with.