Using expo and supabase (no typescript), I am trying to call useAuthStateChange inside a useEffect hook to update useState with the session. From what I understand, this is the most common approach
Logging in and signing up both work, according to Supabase Postgres logs, but I believe useAuthStateChange may be firing upon page load and then not refiring.
Here is my code. Please let me know if I am missing something:
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
});
supabase.auth.onAuthStateChange(({ data: { session } }) => {
setSession(session);
});
}, []);
Have you followed this initialisation recommendation for Expo/React native: https://supabase.com/docs/guides/getting-started/tutorials/with-expo#initialize-a-react-native-app ?
import AsyncStorage from '#react-native-async-storage/async-storage'
import { createClient } from '#supabase/supabase-js'
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: AsyncStorage as any,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
})
Then this should work:
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
})
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
}, [])
Looking at your code above, I think you're accessing the session incorrectly in the onAuthStateChange callback.
Related
I have been trying to use redux and redux-thunk to help get a json file from a api and have been getting a warning stating that action must be a plain object. I am really confused as to where the issue is in the code. i have tried following many other stackoverflow posts and a couple of guides online and have not really got a good grasp of where I am going wrong. I understand that this is a problem with how I am referencing async and dispatch but do not know how to fix it.
This is the function that causes the warning to appear in the simulator
export const fetchData = url => {
console.log("Should enter async dispatch");
return async (dispatch) => {
dispatch(fetchingRequest());
fetch("https://randomuser.me/api/?results=10")
.then(response => {
if (response.ok) {
let json = response.json();
dispatch(fetchingSuccess(json));
console.log("JSON", json);
}
})
.catch(error => {
dispatch(fetchingFailure(error));
console.log("Error", error);
});
};
};
Here is the output in the console
Possible Unhandled Promise Rejection (id: 0):
Error: Actions must be plain objects. Use custom middleware for async actions.
Error: Actions must be plain objects. Use custom middleware for async actions.
Edit: including setup of middleware
I have the middleware setup in the index.js file of my app
index.js
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import { Provider } from "react-redux";
import React, { Components } from "react";
import { createStore, applyMiddleware } from "redux";
import appReducer from "./src/data/redux/reducers/appReducer";
import thunk from "redux-thunk";
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(appReducer);
console.log("Store", store.getState());
const AppContainer = () => (
<Provider store = {store}>
<App />
</Provider>
);
AppRegistry.registerComponent(appName, () => AppContainer);
I learned this implementation of store from a Youtube Tutorial.
Edit 2: Adding in the fetchData call
I call fetchData in a _onPress function like this
_onPress = () => {
const {fetchData} = this.props;
let url = "https://randomuser.me/api/?results=10";
fetchData(url);
console.log("should have fetched");
};
this is how my app has been connected to redux
const mapStateToProps = state => {
return { response: state };
};
const mapStateToDispatch = dispatch => ({
fetchData: url => dispatch(fetchData(url)),
});
export default connect(
mapStateToProps,
mapStateToDispatch
)(SearchScreen);
these are the action in my app
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
I was able to figure out the problem thanks to working through the steps in the comments thanks to Michael Cheng. I ended up finding that the problem was that i had actions with plain objects but they were not returning anything.
The original actions were
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
to this
export const fetchingRequest = () => {
return {
type: FETCHING_REQUEST
}
};
export const fetchingSuccess = json => {
return {
type: FETCHING_SUCCESS,
payload: json
}
};
export const fetchingFailure = error => {
return {
type: FETCHING_FAILURE,
payload: error
};
};
with including the return for each action
I have a file called config-ingest.js in my plugins directory with the following:
import gql from 'graphql-tag'
export default ({ app }, inject) => {
const client = app.apolloProvider.defaultClient;
const apps = gql`
query {
apps: allApps {
key
tree
}
}
`;
client.query({ query: apps }).then(response => {
inject('configStructure', response.data.apps);
});
}
Although the data is successfully retrieved, it doesn't seem to be available in all my pages and components.
If I want to get data from another server that should be available to all my pages, how would I do this? I want to stick this data in it's own file so I can get the data from any component or page when I please.
You should use the Vuex Store for that.
https://nuxtjs.org/guide/vuex-store/
I was able to do the following in store/index.js:
import gql from 'graphql-tag'
export const state = () => ({
apps: []
})
export const mutations = {
SET_APPS (state, apps) {
state.apps = apps
},
}
export const getters = {
getApps (state) {
return state.apps;
},
}
}
export const actions = {
async nuxtServerInit ({ commit }, { app }) {
const client = app.apolloProvider.defaultClient;
const apps = gql`
query {
apps: allApps {
key
label
tree
}
}
`;
await client.query({ query: apps }).then(response => {
commit('SET_APPS', response.data.apps)
});
}
}
I unfortunately can't attach all code or create a gist because the project I'm working on is related to work but I can give enough detail that I think it will work.
I'm trying to mock a call to an action that is stored in a different module but for the life of me I can't figure out how to. I'm able to create a Jest spy on the store.dispatch method but I want to be able to resolve the promise and make sure that the subsequent steps are taken.
The method in the SFC is
doSomething(data) {
this.$store.dispatch('moduleA/moduleDoSomething',{data: data})
.then(() => {
this.$router.push({name: 'RouteName'})
})
.catch(err => {
console.log(err)
alert('There was an error. Please try again.')
})
},
This is what my test looks like:
import { mount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
import Component from '#/components/Component'
import moduleA from '#/store/modules/moduleA'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Vuetify)
describe('Component.vue', () => {
let actions
let store
const $router = []
beforeEach(() => {
actions = {
moduleDoSomething: jest.fn((payload) => {
return Promise.resolve({
status: 200,
})
})
}
store = new Vuex.Store({
state: {},
modules: {
moduleA: {
actions
}
},
})
})
it('does something', () => {
const wrapper = mount(Component, {
store,
localVue,
mocks: {
$router,
},
})
let button = wrapper.find('button that calls doSomething')
button.trigger('click')
expect(actions.moduleDoSomething).toHaveBeenCalled()
expect(wrapper.vm.$router[0].name).toBe('RouteName')
})
})
The following test passes, but I don't want to just test that the action was dispatched; I also want to test things in the "then" block.
it('does something', () => {
const dispatchSpy = jest.spyOn(store, 'dispatch')
const wrapper = mount(Component, {
store,
localVue,
mocks: {
$router,
},
})
let button = wrapper.find('button that calls doSomething')
button.trigger('click')
expect(dispatchSpy).toHaveBeenCalledWith('moduleA/moduleDoSomething',{data: data})
})
})
I managed to solve this problem by simply making the module namespaced in the mocked store.
store = new Vuex.Store({
state: {},
modules: {
moduleA: {
actions,
namespaced: true
}
},
})
I'll delete the question in a little bit
I am having an issue with a plugin I am attempting to use. Note that code works fine after refreshing page however on initial login the mutation it is set to watch (createSession) is not responding correctly. I am not sure if anyone is familiar with the CASL package, but I don't think the issue is there but perhaps something I need to be doing to make the plugin work correctly.
Here is the plugin ability.js
import { Ability } from '#casl/ability'
export const ability = new Ability()
export const abilityPlugin = (store) => {
ability.update(store.state.rules)
const rules = store.subscribe((mutation) => {
switch (mutation.type) {
case 'createSession':
ability.update(mutation.payload.rules)
break
case 'destroySession':
ability.update([{ actions: '', subject: '' }])
break
}
console.log(ability.rules)
})
return rules
}
Here is the store where I am importing
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import { abilityPlugin, ability as appAbility } from './utils/ability'
import storage from './utils/storage'
export const ability = appAbility
Vue.use(Vuex)
axios.defaults.baseURL = 'http://traxit.test/api'
export default new Vuex.Store({
plugins: [
storage({
storedKeys: ['rules', 'token'],
destroyOn: ['destroySession']
}),
abilityPlugin
],
state: {
rules: '',
token: localStorage.getItem('access_token') || null,
sidebarOpen: true,
loading: false,
},
mutations: {
createSession(state, session) {
state.rules = session[0]
state.token = session.access_token
},
}
and I am mutation the createSession with my response data from the initial login action which is to retrieve token and rules here
retrieveToken({ commit }, credentials) {
return new Promise((resolve, reject) => {
axios.post('/login', {
username: credentials.username,
password: credentials.password,
})
.then(response => {
const token = response.data.access_token
localStorage.setItem('access_token', token)
commit('createSession', response.data)
resolve(response)
})
.catch(error => {
console.log(error)
reject(error)
})
})
},
any help would be greatly appreciated!! I have been stuck on this issue for a while..
Once again answering my own question. Lol
So after console loggin my mutation.payload I realized I was trying to access the data incorrecly.
I switched
case 'createSession':
ability.update(mutation.payload.rules)
break
to this
case 'createSession':
ability.update(mutation.payload[0])
break
I am trying to fetch some data from an api using Redux. My code looks like this:
Action:
// Import libraries
import axios from 'axios';
// Import types
import {
GET_ALL_PICKS
} from './types';
export const getAllPicks = ({ token }) => {
const getPicks = (dispatch) => {
axios({
method: 'get',
url: 'http://myapi/',
headers: {
Authorization: `Bearer ${token}`
}
})
.then((response) => {
console.log(response.data); // First log here returns data just fine
dispatch({
type: GET_ALL_PICKS,
payload: response.data
});
})
.catch((error) => {
console.log(error);
});
};
return getPicks;
};
Reducer:
// Import types
import {
GET_ALL_PICKS
} from '../actions/types';
// Set Initial State
const INITIAL_STATE = {
allPicks: {},
loading: false,
error: ''
};
// Make pick reducers
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_ALL_PICKS:
return { ...state, allPicks: action.payload }; // Logging action.payload here returns data just fine
default:
return state;
}
};
Component:
// Import Libraries
import React, { Component } from 'react';
import { Text } from 'react-native';
import { connect } from 'react-redux';
import {
getAllPicks
} from '../actions/picks';
// Make Component
class HomeScreen extends Component {
// Fetch Data
componentWillMount() {
const { token } = this.props;
this.props.getAllPicks({ token });
}
// Test response
componentDidMount() {
console.log(this.props.allPicks); // This log returns empty object, why?!
}
render() {
return (
<Text>Test</Text>
);
}
}
const mapStateToProps = ({ auth, picks }) => {
const { token } = auth;
const { allPicks } = picks;
return {
token,
allPicks
};
};
export default connect(mapStateToProps, { getAllPicks })(HomeScreen);
When I run the app I see the data in the action console.log and if I run a console.log(action.payload) in the reducer I see the data just fine but in component I see an empty array which suggests I'm not hooking up the data in my reducer correctly? Here's a screen shot of the logs:
I have also tried this in my reducer after some Googling:
return Object.assign({}, state, {
allPicks: action.payload
});
but again I got the same result. Can anyone explain to me what I am doing wrong?
You are confusing the component lifecycle and the API lifecycle.
In practice, what's happening is:
componentWillMount
getAllPicks
componentDidMount (at which point, the API didn't return, the picks are empty)
[... wait for the API to return]
then the API returns with the data, but too late
What you need to do then is check for your "picks" state in the render() function, which will be updated each time your state changes (which happens when the API returns), thanks to the connect() function.
You can also check that the picks are updated properly using componentWillUpdate, not componentDidMount which again has nothing to do with the props being updated.