After embedding kepler.gl into my application, how can I add the feature of adding the data from url? By default, I am able to add csv file into the modal of kepler.
from the kepler.gl dashboard initial page use the 'Load map uising url' and point your csv there. Reference screenshot
I did it this way but i actually trying to get it work with multiple data:
const reducers = combineReducers({
keplerGl: keplerGlReducer,
});
const store = createStore(reducers, {}, applyMiddleware(taskMiddleware));
const mapboxApiAccessToken = "YOUR-API-KEY";
export default function Keplerglcomp() {
return (
<Provider store={store}>
<Map/>
</Provider>
);
}
function Map() {
const dispatch = useDispatch();
const {data} = useSwr("datacap", async () => {
const response = await fetch(
"https://placeholder.com/data.json"
);
const data = await response.json();
return data;
})
React.useEffect(() => {
if (data) {
dispatch(
addDataToMap({
datasets: {
info: {
label: "Datalabel-1",
id: "Dataid-1"
},
data
},
option: {
centerMap: true,
readOnly: true
},
config: {}
}),
);
}
}, [dispatch, data]);
return (
<div>
<AutoSizer>
{({height, width}) => (
<KeplerGl
id="magic"
mapboxApiAccessToken={mapboxApiAccessToken}
width={window.innerWidth}
height={window.innerHeight}
/>
)}
</AutoSizer>
</div>
);
}
Related
I want to enable persistance for react-native application.
Following tutorial on https://garden.bradwoods.io/notes/javascript/state-management/xstate/global-state#rehydratestate
I can't use asynchronous code inside xstate's hook useInterpret
Original code (which uses localStorage instead of AsyncStorage) doesn't have that issue since localStorage is synchronous.
import AsyncStorage from '#react-native-async-storage/async-storage';
import { createMachine } from 'xstate';
import { createContext } from 'react';
import { InterpreterFrom } from 'xstate';
import { useInterpret } from '#xstate/react';
export const promiseMachine = createMachine({
id: 'promise',
initial: 'pending',
states: {
pending: {
on: {
RESOLVE: { target: 'resolved' },
REJECT: { target: 'rejected' },
},
},
resolved: {},
rejected: {},
},
tsTypes: {} as import('./useGlobalMachine.typegen').Typegen0,
schema: {
events: {} as { type: 'RESOLVE' } | { type: 'REJECT' },
},
predictableActionArguments: true,
});
export const GlobalStateContext = createContext({
promiseService: {} as InterpreterFrom<typeof promiseMachine>,
});
const PERSISTANCE_KEY = 'test_key';
export const GlobalStateProvider = (props) => {
const rehydrateState = async () => {
return (
JSON.parse(await AsyncStorage.getItem(PERSISTANCE_KEY)) ||
(promiseMachine.initialState as unknown as typeof promiseMachine)
);
};
const promiseService = useInterpret(
promiseMachine,
{
state: await rehydrateState(), // ERROR: 'await' expressions are only allowed within async functions and at the top levels of modules.
},
(state) => AsyncStorage.setItem(PERSISTANCE_KEY, JSON.stringify(state))
);
return (
<GlobalStateContext.Provider value={{ promiseService }}>
{props.children}
</GlobalStateContext.Provider>
);
};
I tried to use .then syntax to initialize after execution of async function but it caused issue with conditional rendering of hooks.
I had the same use case recently and from what I found there is no native way for xState to handle the async request. What is usually recommended is to introduce a generic wrapper component that takes the state from the AsyncStorage and pass it a prop to where it is needed.
In your App.tsx you can do something like:
const [promiseMachineState, setPromiseMachineState] = useState<string | null>(null);
useEffect(() => {
async function getPromiseMachineState() {
const state = await AsyncStorage.getItem("test_key");
setPromiseMachineState(state);
}
getAppMachineState();
}, []);
return (
promiseMachineState && (
<AppProvider promiseMachineState={promiseMachineState}>
...
</AppProvider>
)
)
And then in your global context you can just consume the passed state:
export const GlobalStateProvider = (props) => {
const promiseService = useInterpret(
promiseMachine,
{
state: JSON.parse(props.promiseMachineState)
},
(state) => AsyncStorage.setItem(PERSISTANCE_KEY, JSON.stringify(state))
);
return (
<GlobalStateContext.Provider value={{ promiseService }}>
{props.children}
</GlobalStateContext.Provider>
);
};
I have a react native project that uses react-navigation. I have these two screens that are part of a stack navigator. I want to call all API related functions in App.js or the stack navigator rather than directly on a screen. I would also like to use data in the two screens. How can I do this?
App.js
import fetchData from './Data';
export default function App() {
const [data, setData] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
setData1WeekCases(await fetchData());
};
fetchAPI();
}, [setData1WeekCases]);
}
Data.tsx
export const fetchData = async () => {
try {
const {
data: { countries },
} = await axios.get("https://covid19.mathdro.id/api/countries");
return countries.map((country) => country.name);
} catch (error) {
console.log(error);
}
};
StackNavigator.tsx
const AppStack = createNativeStackNavigator();
const MainStackNavigator = () => {
return (
<AppStack.Navigator>
<AppStack.Screen
name="HomeScreen"
component={HomeScreen}
options={{
title: "Home",
}}
/>
<AppStack.Screen
name="DataScreen"
component={DataScreen}
options={{
headerBackTitle: "Summary",
title: "Data",
}}
/>
<AppStack.Navigator>
)
}
First of all to access data globally like in your case between screens, you have to use state management tool like Redux or Context. You can find many tutorials for this on youtube if you can't figure it out using the docs.
Secondly if you want to do all the fetching in a separate file then you can create an axios instance in a separate file like this :
import axios from "axios";
import AsyncStorage from "#react-native-async-storage/async-storage";
const axiosClient = axios.create();
axiosClient.defaults.baseURL = "API_URL";
axiosClient.defaults.headers = {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
};
//All request will wait 2 seconds before timeout
axiosClient.defaults.timeout = 2000;
axiosClient.defaults.withCredentials = true;
export default axiosClient;
axiosClient.interceptors.request.use(
async config => {
const token = await AsyncStorage.getItem('token');
if (token) {
config.headers.Authtoken = JSON.parse(token);
}
return config;
},
error => {
return console.log(error);
},
);
export function getRequest(URL) {
return axiosClient
.get(URL)
.then((response) => response)
.catch((err) => err);
}
export function postRequest(URL, payload) {
//! step for x-www-form-urlencoded data
const params = new URLSearchParams(payload);
return axiosClient.post(URL, params).then((response) => response);
}
export function patchRequest(URL, payload) {
return axiosClient.patch(URL, payload).then((response) => response);
}
export function deleteRequest(URL) {
return axiosClient.delete(URL).then((response) => response);
}
Fetch data from the api then change the state using redux to get the response in every screen.
I use FlatList with useState.
const [state, setState] = useState(route);
<FlatList
keyboardDismissMode={true}
showsVerticalScrollIndicator={false}
data={state}
keyExtractor={(comment) => "" + comment.id}
renderItem={renderComment}
/>
When I change the datㅁ which is contained in state, I want to re-run Flatlist with new data.
So after I mutate my data, I try to rerun useQuery first in order to change state. I put refetch module here.
1)
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
If I put button, this onValid function will executed.
<ConfirmButton onPress={handleSubmit(onValid)}>
onValid function changes data and after all finished, as you can see I put refetch().
=> all this process is for that if I add comment and press confirm button, UI (flatlist) should be changed.
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};
But when I console.log data after all, it doesnt' contain added data..
what is the problem here?
If you need more explanation, I can answer in real time.
please help me.
add full code
export default function Comments({ route }) {
const { data: userData } = useMe();
const { register, handleSubmit, setValue, getValues } = useForm();
const [state, setState] = useState(route);
const [update, setUpdate] = useState(false);
const navigation = useNavigation();
useEffect(() => {
setState(route?.params?.comments);
}, [state, route]);
const renderComment = ({ item: comments }) => {
return <CommentRow comments={comments} photoId={route?.params?.photoId} />;
};
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
const createCommentUpdate = (cache, result) => {
const { comments } = getValues();
const {
data: {
createComment: { ok, id, error },
},
} = result;
if (ok) {
const newComment = {
__typename: "Comment",
createdAt: Date.now() + "",
id,
isMine: true,
payload: comments,
user: {
__typename: "User",
avatar: userData?.me?.avatar,
username: userData?.me?.username,
},
};
const newCacheComment = cache.writeFragment({
data: newComment,
fragment: gql`
fragment BSName on Comment {
id
createdAt
isMine
payload
user {
username
avatar
}
}
`,
});
cache.modify({
id: `Photo:${route?.params?.photoId}`,
fields: {
comments(prev) {
return [...prev, newCacheComment];
},
commentNumber(prev) {
return prev + 1;
},
},
});
}
};
const [createCommentMutation] = useMutation(CREATE_COMMENT_MUTATION, {
update: createCommentUpdate,
});
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};
I am trying to store my app data on Google Drive and iCloud based on user device.I don't want to use Async Storage ,redux state neither I want to store data on my server cloud i.e. AWS. Basically I like the way how WhatsApp takes data backup on google drive for android devices and iCloud for IOS devices.This way I want to store my encrypted data's private keys on drive or iCloud so that if user changes his device I can get these keys from drive or iCloud and proceed for decryption mechanism.I found https://github.com/manicakes/react-native-icloudstore which serves my purpose for iCloud.But I haven't found anything on same line for google drive.Can you please suggest me better approach for above requirement?
For Google Drive Implementation You can Check this Package : react-native-google-drive-api-wrapper . You Have To Use Google SignIn With this package In order Get the Access Token , Install This Package Also react-native-google-signin/google-signin .
A Quick Example :
import { GoogleSignin } from "#react-native-google-signin/google-signin";
import {
GDrive,
ListQueryBuilder,
MimeTypes
} from "#robinbobin/react-native-google-drive-api-wrapper";
import React, {
useCallback,
useEffect,
useState
} from "react";
import {
AppRegistry,
Button,
SafeAreaView,
StyleSheet
} from "react-native";
import { name } from './app.json';
function App() {
const [gdrive] = useState(() => new GDrive());
const [ui, setUi] = useState();
const invoker = useCallback(async cb => {
try {
return await cb();
} catch (error) {
console.log(error);
}
}, []);
const createBinaryFile = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.newMultipartUploader()
.setData([1, 2, 3, 4, 5], MimeTypes.BINARY)
.setRequestBody({
name: "bin",
//parents: ["folder_id"]
})
.execute()
)));
}, [invoker]);
const createIfNotExists = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.createIfNotExists(
{
q: new ListQueryBuilder()
.e("name", "condition_folder")
.and()
.e("mimeType", MimeTypes.FOLDER)
.and()
.in("root", "parents")
},
gdrive.files.newMetadataOnlyUploader()
.setRequestBody({
name: "condition_folder",
mimeType: MimeTypes.FOLDER,
parents: ["root"]
})
)
)));
}, [invoker]);
const createFolder = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.newMetadataOnlyUploader()
.setRequestBody({
name: "Folder",
mimeType: MimeTypes.FOLDER,
parents: ["root"]
})
.execute()
)));
}, [invoker]);
const createTextFile = useCallback(async () => {
console.log(await invoker(async () => {
return (await gdrive.files.newMultipartUploader()
.setData("cm9iaW4=", MimeTypes.TEXT)
.setIsBase64(true)
.setRequestBody({
name: "base64 text",
})
.execute()).id;
}));
}, [invoker]);
const emptyTrash = useCallback(async () => {
if (await invoker(async () => {
await gdrive.files.emptyTrash();
return true;
}))
{
console.log("Trash emptied");
};
}, [invoker]);
const getWebViewLink = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.getMetadata(
"some_id", {
fields: "webViewLink"
}
)
)));
}, [invoker]);
const readFiles = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.getText("text_file_id")
)));
console.log(await invoker(async () => (
await gdrive.files.getBinary("bin_file_id", null, "1-1")
)))
}, [invoker]);
useEffect(() => {
GoogleSignin.configure({
scopes: [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.appfolder"
]});
(async () => {
if (await invoker(async () => {
await GoogleSignin.signIn();
gdrive.accessToken = (await GoogleSignin.getTokens()).accessToken;
gdrive.files.fetchCoercesTypes = true;
gdrive.files.fetchRejectsOnHttpErrors = true;
gdrive.files.fetchTimeout = 1500;
return true;
}))
{
setUi([
["create bin file", createBinaryFile],
["create folder", createFolder],
["create if not exists", createIfNotExists],
["create text file", createTextFile],
["empty trash", emptyTrash],
["get webViewLink", getWebViewLink],
["read files", readFiles]
].map(([title, onPress], index) => (
<Button
key={index}
onPress={onPress}
title={title}
/>
)));
}
})();
}, [
createBinaryFile,
createFolder,
createIfNotExists,
createTextFile,
emptyTrash,
getWebViewLink,
readFiles,
invoker
]);
return (
<SafeAreaView
style={styles.container}
>
{ui}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: "cyan",
flex: 1,
justifyContent: "space-around",
padding: 25
}
});
I'm trying to start a loading spinner when an image uploads as part of a form, and stop it when a reference to the image is saved in Firebase.
This function in my Actions.js file returns the input from a given form field:
export const formUpdate = ({ prop, value }) => {
alert('update')
return {
type: FORM_UPDATE,
payload: { prop, value }
};
};
With Connect, I use formUpdate to store values for different form fields in my Form.js component - this works fine.
I use a separate function in Actions.js to handle the image upload, and once uploaded, I call this function to save a reference in Firebase:
export const saveImageReference = (downloadUrl, sessionId) => {
const { currentUser } = firebase.auth();
firebase
.database()
.ref(`users/${currentUser.uid}/images`)
.push({
imageId: sessionId,
imageUrl: downloadUrl
})
.then(formUpdate({ prop: 'loading', value: false }));
};
I'm trying to get my form to show a loading spinner during upload. To do this, I'm using formUpdate at the end of saveImageReference to dispatch a loading prop. However, this isn't working.
formUpdate executes as part of the .then() block - I see the alert to confirm this - but no data makes it to the Form component.
I've also tried using a different prop (for example 'name') to see if it updates the form field, but nothing happens.
I have redux-thunk working properly - I use a similar approach to show a spinner in my login form - but this action doesn't seem to want to play ball.
If it helps, here's mapStateToProps from my Form component:
const { name, location, loading } = state.testForm;
return {
loading,
name,
activity
};
};
export default connect(
mapStateToProps,
{ formUpdate, uploadImage }
)(Form);
Update
Here's the uploadImage code based on azundo's answer. This doesn't execute:
export const uploadImage = (
uri,
mime = 'application/octet-stream'
) => dispatch => {
const { Blob } = RNFetchBlob.polyfill;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
const { currentUser } = firebase.auth();
console.log('Starting upload action...');
return new Promise((resolve, reject) => {
console.log('in promise 1');
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
const sessionId = new Date().getTime();
// create a reference in firebase storage for the file
let uploadBlob = null;
const imageRef = firebase
.storage()
.ref(`user/${currentUser.uid}/images`)
.child(`image_${sessionId}`);
// encode data with base64 before upload
RNFetchBlob.fs
.readFile(uploadUri, 'base64')
.then(data => {
console.log('Encoding image...');
return RNFetchBlob.polyfill.Blob.build(data, {
type: `${mime};BASE64`
});
})
// put blob into storage reference
.then(blob => {
uploadBlob = blob;
console.log('uploading...');
return imageRef.put(blob, { contentType: mime });
})
.then(() => {
console.log('Getting download URL...');
uploadBlob.close();
return imageRef.getDownloadURL();
})
.then(url => {
console.log('Saving reference...');
// setLoading();
resolve(url);
saveImageReference(url, sessionId);
})
.then(() => {
dispatch(formUpdate({ prop: 'loading', value: false }));
})
.catch(error => {
reject(error);
});
});
};
Based on what you've described, the formUpdate call in saveImageReference is not actually dispatching your action, it's just calling the bare formUpdate function which simply returns the plain action object. You'll need to find some way to actually get that action dispatched.
Assuming uploadImage is a redux-thunk action I would recommend keeping the knowledge of the action dispatch out of the saveImageReference function and instead dispatch from uploadImage:
export const saveImageReference = (downloadUrl, sessionId) => {
const { currentUser } = firebase.auth();
// note that we are now returning the firebase promise here
return firebase
.database()
.ref(`users/${currentUser.uid}/images`)
.push({
imageId: sessionId,
imageUrl: downloadUrl
});
};
const uploadImage = (arg1, arg2) => dispatch => {
// other upload code here prior to calling the firebase function...
saveImageReference(downloadUrl, sessionId).then(() => {
dispatch(formUpdate({prop: 'loading', value: false}));
});
})
If you trying to render loader, when you await for an async operation. You could use suspense.
This would be a better option.
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
After much investigation, I solved the issue. I moved saveImageReference() from Actions.js to my component:
addImage = () => {
ImagePicker.showImagePicker(response => {
if (!response.didCancel) {
// shows modal with a form for user to select an image and add metadata
this.setState({ showModal: true });
// set the loading spinner in the form to show image is uploading
this.props.formUpdate({ prop: 'loading', value: true });
// takes the selected image and invokes uploadImage
uploadImage(response.uri).then(url => {
// once image is uploaded, generate sessionId in the component, and invoke saveImageReference
const sessionId = new Date().getTime();
this.props.saveImageReference(url, sessionId);
});
}
});
};
The uploadImage() action creator resolves with the URL of a successfully uploaded image, which saveImageReference() uses to create the reference.
Once that reference is saved, saveImageReference() dispatches a dedicated action to set loading to false. Here's the contents of Actions.js:
export const uploadImage = (uri, mime = 'application/octet-stream') => {
const { Blob } = RNFetchBlob.polyfill;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
const { currentUser } = firebase.auth();
console.log('Starting upload action...');
return new Promise((resolve, reject) => {
console.log('in promise');
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
const sessionId = new Date().getTime();
// create a reference in firebase storage for the file
let uploadBlob = null;
const imageRef = firebase
.storage()
.ref(`user/${currentUser.uid}/images`)
.child(`image_${sessionId}`);
// encode data with base64 before upload
RNFetchBlob.fs
.readFile(uploadUri, 'base64')
.then(data => {
console.log('Encoding image...');
return RNFetchBlob.polyfill.Blob.build(data, {
type: `${mime};BASE64`
});
})
// put blob into storage reference
.then(blob => {
uploadBlob = blob;
console.log('uploading...');
return imageRef.put(blob, { contentType: mime });
})
.then(() => {
console.log('Getting download URL...');
uploadBlob.close();
return imageRef.getDownloadURL();
})
.then(url => {
resolve(url, sessionId);
})
.catch(error => {
reject(error);
});
});
};
export const saveImageReference = (downloadUrl, sessionId) => {
const { currentUser } = firebase.auth();
console.log('Saving reference!');
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/images`)
.push({
imageId: sessionId,
imageUrl: downloadUrl
})
.then(ref => {
console.log(ref.key);
dispatch(imageUploadComplete());
});
};
};
const imageUploadComplete = () => {
return dispatch => {
return dispatch({
type: IMAGE_CREATE,
payload: false
});
};
};
No matter what I tried, I couldn't dispatch another action from within saveImageReference() - introducing return dispatch would freeze the flow, and without it I'd get dispatch is not defined.
Invoking this at a component level, using this.props.saveImageReference() solved the issue.