Please check my code below i have to navigate to EmployeeCreate after login success .I use this in dispatch(NavigationActions.navigate({ routeName: 'EmployeeCreate' })); in my AuthActions.js. but not working.Previously i used 'react-native-router-flux' instead of react-navigation. Iam new to react-native and couldn't find the issue.
App.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import firebase from 'firebase';
import ReduxThunk from 'redux-thunk';
import reducers from './reducers';
import Router from './Router';
class App extends Component {
componentWillMount() {
const config = {
apiKey: "###",
authDomain: "###",
databaseURL: "###",
projectId: "###",
storageBucket: "###,
messagingSenderId: "0000000"
};
firebase.initializeApp(config);
}
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<Router />
</Provider>
);
}
}
export default App;
Router.js
import React from 'react';
import { StackNavigator} from 'react-navigation';
import LoginForm from './components/LoginForm';
import EmployeeList from './components/EmployeeList';
import EmployeeCreate from './components/EmployeeCreate';
import EmployeeEdit from './components/EmployeeEdit';
const RouterComponent = StackNavigator({
LoginForm : {screen : LoginForm},
EmployeeCreate : {screen :EmployeeCreate},
EmployeeEdit:{screen:EmployeeEdit},
},
{
headerMode : 'none',
navigationOptions:{
headerVisible : false,
}
}
)
export default RouterComponent;
AuthActions.js
import firebase from 'firebase';
import { NavigationActions } from 'react-navigation'
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER
} from './types';
export const emailChanged = (text) => {
return {
type: EMAIL_CHANGED,
payload: text
};
};
export const passwordChanged = (text) => {
return {
type: PASSWORD_CHANGED,
payload: text
};
};
export const loginUser = ({ email, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
firebase.auth().signInWithEmailAndPassword(email, password)
.then(user => loginUserSuccess(dispatch, user))
.catch((error) => {
console.log(error);
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(user => loginUserSuccess(dispatch, user))
.catch(() => loginUserFail(dispatch));
});
};
};
const loginUserFail = (dispatch) => {
dispatch({ type: LOGIN_USER_FAIL });
};
const loginUserSuccess = (dispatch, user) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user
});
dispatch(NavigationActions.navigate({ routeName: 'EmployeeCreate' }));
};
AuthReducer.js
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER
} from '../actions/types';
const INITIAL_STATE = {
email: '',
password: '',
user: null,
error: '',
loading: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EMAIL_CHANGED:
return { ...state, email: action.payload };
case PASSWORD_CHANGED:
return { ...state, password: action.payload };
case LOGIN_USER:
return { ...state, loading: true, error: '' };
case LOGIN_USER_SUCCESS:
return { ...state, ...INITIAL_STATE, user: action.payload };
case LOGIN_USER_FAIL:
return { ...state, error: 'Authentication Failed.', password: '', loading: false };
default:
return state;
}
};
You are using dispatch method of Redux. You should be using the dispatch method of React-navigation instead. You can do one of these:
1) Do this.props.navigation.dispatch(NavigationActions.navigate({ routeName: 'EmployeeCreate' }))
2) Or integrate React-navigation with Redux ( recommended for your case especially ) and use Redux's default dispatch. I have created a barebones repo just for react-navigation + Redux
React navigation with Redux flow is changed little bit in newer versions of React Navigation.
You can checkout the best way to do it from 3.x in their documentation itself.
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
Related
I'm trying to practise createAsyncThunk with reduxjs/tookit. When I first fetch the data from the api it works and I can render the data. However, when I refresh the page I get "TypeError: Cannot read properties of undefined (reading 'memes')" error and can't get it worked anymore. I looked up for some info and thought passing dispatch as useEffect dependency would help but it didn't. When I open Redux Devtools extension => diff = I can clearly see that I fetched the data, promise fulfilled and everything is fine. I try to log allMemes to console and it shows an empty object.
store.js
import { configureStore } from "#reduxjs/toolkit";
import memeSlice from "./features/getAllMemes/displayAllMemesSlice";
const store = configureStore({
reducer:{
memes:memeSlice
}
});
export default store;
DisplaySlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const loadAllMemes = createAsyncThunk("getMemes/loadAllMemes", async () => {
try {
const response = await fetch("https://api.imgflip.com/get_memes");
const data = await response.json();
return data;
}
catch (error) {
console.log(error)
}
})
export const memeSlice = createSlice({
name:"getMemes",
initialState: {
isLoading:false,
hasError:false,
allMemes:{},
},
extraReducers: {
[loadAllMemes.pending]:(state, action) => {
state.isLoading = true;
},
[loadAllMemes.fulfilled]:(state, action) => {
state.allMemes = action.payload;
state.isLoading = false;
},
[loadAllMemes.rejected]:(state, action) => {
state.hasError = true;
}
}
})
export default memeSlice.reducer;
export const selectAllMemes = state => state.memes.allMemes;
displayAllMemes.js
import React , {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadAllMemes, selectAllMemes } from './displayAllMemesSlice';
export default function DisplayAllMemes() {
const allMemes = useSelector(selectAllMemes)
const dispatch = useDispatch()
useEffect(() => {
dispatch(loadAllMemes())
console.log(allMemes)
}, [dispatch])
return (
<div>
{allMemes.data.memes.map(item => (
<h1>{item.id}</h1>
))}
</div>
)
}
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const loadAllMemes = createAsyncThunk("memes/getAllMemes", async () => {
try {
const response = await fetch("https://api.imgflip.com/get_memes");
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
export const memeSlice = createSlice({
name: "memes",
initialState: {
isLoading: false,
hasError: false,
allMemes: {},
},
extraReducers: {
[loadAllMemes.pending]: (state, action) => {
state.isLoading = true;
},
[loadAllMemes.fulfilled]: (state, action) => {
const { data, success } = action.payload;
state.allMemes = data;
state.isLoading = false;
},
[loadAllMemes.rejected]: (state, action) => {
state.hasError = true;
},
},
});
export const {} = memeSlice.actions;
export default memeSlice.reducer;
-- root reducer
import { combineReducers } from "redux";
import memes_slice from "./memes_slice";
const reducer = combineReducers({
memes: memes_slice,
});
export default reducer;
-- Component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { loadAllMemes } from "../../app/memes_slice";
function Memes() {
const { allMemes } = useSelector((state) => state.memes);
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadAllMemes());
}, [dispatch]);
console.log(allMemes.memes);
return <div>Memes</div>;
}
export default Memes;
this my updated version of intergrating redux and NextJS. Just to elobarate what I have done so far...
STEP 1. I've created a store.js file to set up my global store in reference to github's explanation from nextJS developers.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { customerListReducer } from './customerReducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
customerList: customerListReducer,
});
const reducer = (state, action) => {
console.log('Just Displaying the Store', state);
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.count) nextState.count = state.count; // preserve count value on client side navigation
return nextState;
} else {
return combinedReducer(state, action);
}
};
// create a makeStore function
const store = () =>
createStore(
reducer,
bindMiddleware([thunkMiddleware])
);
// export an assembled wrapper
export const wrapper = createWrapper(store);
STEP 2: Imported the wrapper above in my _app file to make the wrapper available across all pages in my application
import Nav from '../components/Nav';
import {wrapper} from '../reducers/store';
function MyApp({ Component, pageProps }) {
return (
<>
<Nav />
<Component {...pageProps} />
</>
);
}
export default wrapper.withRedux(MyApp);
STEP 3: CONFIGURATIONS
A) My Action that calls external API
import axios from 'axios';
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
export const listCustomers = () => async (dispatch) => {
try {
dispatch({
type: CUSTOMER_LIST_REQUEST,
});
const { data } = await axios.get(
'https://byronochara.tech/gassystem/api/v1/customers'
);
const result = data.results;
dispatch({
type: CUSTOMER_LIST_SUCCESS,
payload: result,
});
} catch (error) {
dispatch({
type: CUSTOMER_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
B)My Action Reducer
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
import { HYDRATE } from 'next-redux-wrapper';
export const customerListReducer = (state = { customers: [] }, action) => {
switch (action.type) {
case HYDRATE:
return { loading: true, customers: [] };
case CUSTOMER_LIST_REQUEST:
return { loading: true, customers: [] };
case CUSTOMER_LIST_SUCCESS:
return {
loading: false,
customers: action.payload,
};
case CUSTOMER_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
C)The finally bringing it all together in my index.js page to display the results:
import React, { useEffect } from 'react';
import Head from 'next/head';
import { useSelector} from 'react-redux';
import { listCustomers } from './../actions/customerActions';
import { wrapper } from '../reducers/store';
import styles from '../styles/Home.module.css';
const Home = () => {
//Select the loaded customers' list from central state
const customerList = useSelector((state) => {
console.log(state);
return state.customerList;
});
const { loading, error, customers } = customerList;
//displaying the customers data from the external API
console.log('Fetched Customers Data', customers);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{/* {loading && <h6>Loading...</h6>} */}
{/* {error && <h6>Error Occured...</h6>} */}
{/* {customers.map((customer) => (
<h3>{customer.customerName}</h3>
))} */}
{/* <ArticleList customers={customers} /> */}
</div>
);
};
// getStaticProp at build time
// getServerSideProp at every request slower
// getStaticPath to dynamically generate paths based on the data we are fetching
export const getStaticProps = wrapper.getServerSideProps(async ({ store }) => {
// console.log('STORE', store);
store.dispatch(listCustomers());
});
export default Home;
COMMENT ON THE PROBLEM I'M FACING FROM THE ABOVE CODE: once everything has been set up if you follow the code above, the code seems to run well the store is successfully created when I log the result on the console ``{ customerList: { loading: true, customers: [] } }. But then I guess this is the result from the HYDRATE action type since it will always be dispatch since am using getStaticProps``` that creates a new store instance in the server.
MAIN QUIZ: My challenge is how do I bypass the HYDRATED action and reconcile the server side state with the client side store and persist it and at least to finally be able to view the list from the external API. Thanks in advance. :)
I totally recommend you to use reduxjs/toolkit. It's very simple , less code, no wrappers, clean. And no matter your project on nextjs or created via CRA. Also you dont need to configure redux-thunk and redux-devtools cause they are enabled by default. Read documentation for more information ( how to persist state without any npm package and so on )
Here is a little example.
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import userSlice from './user.slice.js';
//reducers
const rootReducer = combineReducers({
user: userSlice
});
const store = configureStore({
reducer: rootReducer,
});
export default store;
Wrap with Provider (in your case _app.js)
<Provider store={store}>
<Component {...pageProps} />
</Provider>
user.slice.js ( action + reducer )
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
id: '',
email: '',
roles: []
};
// export async action
export const signIn = createAsyncThunk('user/signIn', async (data) => {
try {
const payload = await api.auth.signin(data).then((res) => res.data);
// do some stuff if you want
return payload ;
} catch (err) {
console.log(err.response);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
removeUser(state, payload) {
//cant be an async method
return initialState;
},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, { payload }) => {
// payload from the async method above (asyncThunk)
return payload;
});
},
},
});
// export actions
export const { removeUser } = userSlice.actions;
// export reducer
export default userSlice.reducer;
Thats it. Last step to call actions from any component e.g.
import { useDispatch, useSelector } from 'react-redux';
import { signIn, removeUser } from '../actions/userSlice';
// in function component
// call hooks
const dispatch = useDispatch();
// read the store
const { user } = useSelector((state) => state);
// dispatch any action , example below
dispatch(signIn(userCredentials));
// or
dispatch(removeUser());
I has an Issue with setting Redux with NextJS and this is my final answer after some insight from mirik999 too.
A. my store.
import { configureStore } from '#reduxjs/toolkit';
//importing the slice file with sliced reducers
import customerListReducer from '../slices/customerSlice';
// const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));
const store = configureStore({
reducer: {
customerList: customerListReducer,
},
});
export default store;
B. The store is provided in my app component
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Nav />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
C. The Slice file that automatically creates action creators and the reducer
import { createSlice } from '#reduxjs/toolkit';
//creating and action that calls API from a REST API backend
export const customersFetchedList = createAsyncThunk(
'customersList/customersListSuccess',
async () => {
try {
const { data } = await axios.get(
'https://example.com/api/your/endpoint'
);
const result = data.results;
//the payload
const payload = result;
return payload;
} catch (error) {
console.log(error.response);
const payload =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return payload;
}
}
);
const initialState = {
loading: true,
customers: [],
error: false,
};
const customerListSlice = createSlice({
name: 'customersList',
initialState,
reducers: {
//reducer functions we've provided
customersRequest(state, action) {
if (state.loading == true) {
return state;
}
},
},
extraReducers: (builder) => {
initialState,
builder.addCase(customersFetchedList.fulfilled, (state, action) => {
state.loading = false;
state.customers = action.payload;
state.error = false;
return state;
});
},
});
export const {
customersRequest,
customersLoadingError,
} = customerListSlice.actions;
export default customerListSlice.reducer;
D. Then finally fired this action above in my component using the useEffect()
import React, { useEffect } from 'react';
import Head from 'next/head';
const Home = () => {
//method to fire the action
const dispatch = useDispatch();
//Select the loaded customers' list from central state
const customerList = useSelector((state) => state);
// const { loading, error, customers } = customerList;
useEffect(() => {
dispatch(listCustomers());
}, []);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{loading && <h6>Loading...</h6>}
{error && <h6>Error Occured...</h6>}
{customers.map((customer) => (
<h3>{customer.customerName}</h3>
))}
</div>
);
};
Thanks so much for your contribution. :)
It does not redirect after successfully logged in.
getting a console error TypeError: Cannot read property 'push' of undefine
Here my code.
I'm creating SPA in vue3 with Laravel 8.
import { ref } from "vue";
import { useRoute } from "vue-router";
export default {
setup() {
const form = ref(
{
email: "hudson.vilma#example.net",
password: "password",
isLoading: false,
},
);
const user = ref("");
function login() {
axios.get('/sanctum/csrf-cookie').then(response => {
axios.post('/login', this.form).then(response => {
this.$router.push('/dashboard')
// useRoute.push('/dashboard');
// this.$router.push({ name: "Dashboard" });
}).catch(error => console.log(error)); // credentials didn't match
});
}
return { form, login, user , useRoute};
},
};
</script>
in app.js instant of vue &
require('./bootstrap');
import { createApp } from "vue";
import App from "./view/App.vue";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");
Try to use useRouter instead of useRoute and instantiate it like const router =useRouter() in setup function:
import { ref } from "vue";
import { useRouter } from "vue-router";
export default {
setup() {
const router =useRouter()
const form = ref(
{
email: "hudson.vilma#example.net",
password: "password",
isLoading: false,
},
);
const user = ref("");
function login() {
axios.get('/sanctum/csrf-cookie').then(response => {
axios.post('/login', this.form).then(response => {
router.push('/dashboard')
}).catch(error => console.log(error)); // credentials didn't match
});
}
return { form, login, user ,};
},
};
</script>
Note that this couldn't be used in composition API.
You are using this.$router.push('/dashboard') in setup(). This cannot be used in setup(). Instead you can use...
router.push('/dashboard')
I am trying to configure subscriptions with Apollo 2 and NEXT.js. I can get the client to connect to the server and they are working in the GraphQL playground, so the bad configuration must be in the withData file, or the component that handles the subscription.
When inspecting the socket connection on the network panel in chrome, the subscription payload does not get added as a frame, like it does in the GraphQL playground.
withData:
import { ApolloLink, Observable } from 'apollo-link';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { onError } from 'apollo-link-error';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log({ graphQLErrors });
}
if (networkError) {
console.log('Logout user');
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
createPersistedQueryLink().concat(
new BatchHttpLink({
uri: GRAPHQL_ENDPOINT,
credentials: 'include'
}),
process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: null
)
]),
cache
});
}
export default withApollo(createClient);
Subscription component:
import { CONVERSATION_QUERY } from '../../constants/queries';
import { CONVERSATION_SUBSCRIPTION } from '../../constants/subscriptions';
import PropTypes from 'prop-types';
import { Query } from 'react-apollo';
const Conversation = props => (
<Query
{...props}
query={CONVERSATION_QUERY}
variables={{ input: { _id: props._id } }}
>
{(payload) => {
const more = () => payload.subscribeToMore({
document: CONVERSATION_SUBSCRIPTION,
variables: { input: { conversation: props._id } },
updateQuery: (prev, { subscriptionData }) => {
console.log({ subscriptionData });
if (!subscriptionData.data.messageSent) return prev;
const data = subscriptionData;
console.log({ data });
return Object.assign({}, prev, {});
},
onError(error) {
console.log(error);
},
onSubscriptionData: (data) => {
console.log('onSubscriptionData ', data);
}
});
return props.children({ ...payload, more });
}}
</Query>
);
Conversation.propTypes = {
children: PropTypes.func.isRequired
};
export default Conversation;
The subscription that has been tested in the GraphQL playground:
import gql from 'graphql-tag';
export const CONVERSATION_SUBSCRIPTION = gql`
subscription messageSent($input: messageSentInput) {
messageSent(input: $input) {
_id
users {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
messages {
_id
body
createdAt
read
sender {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
}
}
}
`;
The more function is then executed in componentDidMount:
componentDidMount() {
this.props.subscribeToMore();
}
The result in the console from the log in updateQuery is:
{"data":{"messageSent":null}}
I hadn't configured my withData file properly. You need to use split from the apollo-link package to let Apollo determine if the request should be handled with http or ws. Here is my working configuration file.
import { ApolloLink, Observable } from 'apollo-link';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { getMainDefinition } from 'apollo-utilities';
import { onError } from 'apollo-link-error';
import { split } from 'apollo-link';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const httpLink = new BatchHttpLink({
uri: GRAPHQL_ENDPOINT
});
// Make sure the wsLink is only created on the browser. The server doesn't have a native implemention for websockets
const wsLink = process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: () => {
console.log('SSR');
};
// Let Apollo figure out if the request is over ws or http
const terminatingLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return (
kind === 'OperationDefinition'
&& operation === 'subscription'
&& process.browser
);
},
wsLink,
httpLink
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.error({ graphQLErrors });
}
if (networkError) {
console.error({ networkError});
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
// Push the links into the Apollo client
createPersistedQueryLink().concat(
// New config
terminatingLink
// Old config
// new BatchHttpLink({
// uri: GRAPHQL_ENDPOINT,
// credentials: 'include'
// })
)
]),
cache
});
}
export default withApollo(createClient);
Im using Apollo Client, Graphcool and React. I have a working login form but I need the UI to update when the user is logged in, and I need this to happen in different components.
It seems apollo-link-state is the solution for this. My code below seems to work but Im getting this error:
Missing field CurrentUserIsLoggedIn in {} in writeToStore.js
My Apollo Client setup:
import React from 'react';
import ReactDOM from 'react-dom';
// Apollo
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { withClientState } from 'apollo-link-state';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
// Components
import LoginTest from './components/App/LoginTest';
const wsLink = new WebSocketLink({
uri: `wss://subscriptions.graph.cool/v1/XXX`,
options: {
reconnect: true,
},
});
// __SIMPLE_API_ENDPOINT__ looks like: 'https://api.graph.cool/simple/v1/__SERVICE_ID__'
const httpLink = new HttpLink({
uri: 'https://api.graph.cool/simple/v1/XXX',
});
// auth
const middlewareAuthLink = new ApolloLink((operation, forward) => {
const token = localStorage.getItem('auth-token');
const authorizationHeader = token ? `Bearer ${token}` : null;
operation.setContext({
headers: {
authorization: authorizationHeader,
},
});
return forward(operation);
});
const cache = new InMemoryCache();
const defaultState = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: false,
},
};
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
CurrentUserIsLoggedIn: (_, args) => {
const data = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: args.value,
},
};
cache.writeData({ data });
},
},
},
});
const client = new ApolloClient({
cache,
link: ApolloLink.from([
stateLink,
middlewareAuthLink,
split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
),
]),
});
ReactDOM.render(
<ApolloProvider client={client}>
<LoginTest />
</ApolloProvider>,
document.getElementById('root'),
);
LoginTest.js:
import React from 'react';
import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
import App from './App';
const LoginTest = props => {
if (props.LoginServerQuery.loading) return <p>Loading...</p>;
// If the server tells us the user is logged in
if (props.LoginServerQuery.loggedInUser) {
// Then set the local logged in state to true
props.CurrentUserIsLoggedInMutation({
variables: {
value: true,
},
});
}
return <App />;
};
const CurrentUserIsLoggedInMutation = gql`
mutation CurrentUserIsLoggedInMutation($value: Boolean) {
CurrentUserIsLoggedIn(value: $value) #client {
value
}
}
`;
const LoginServerQuery = gql`
query LoginServerQuery {
loggedInUser {
id
}
}
`;
const LoginTestQuery = compose(
graphql(LoginServerQuery, { name: 'LoginServerQuery' }),
graphql(CurrentUserIsLoggedInMutation, {
name: 'CurrentUserIsLoggedInMutation',
}),
)(LoginTest);
export default LoginTestQuery;
At the moment, apollo-link-state requires you to return any result in your resolver function. It can be null too. This might be changed in the future.
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
CurrentUserIsLoggedIn: (_, args) => {
const data = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: args.value,
},
};
cache.writeData({ data });
return data;
},
},
},
try adding a return statement in your mutation. Similar problem occured here with different function: apollo-link-state cache.writedata results in Missing field warning