React Context: Provide instance of an Api Manager - reactjs

I want to keep a single instance of a third party api manager class in my react app.
I'm trying to achieve this using using the React Context Api like this:
Api Context:
import * as React from "react";
import { ApiManager } from "../ApiManager";
const defaultValue = new ApiManager()
const ApiContext = React.createContext(defaultValue);
const ApiProvider = ApiContext.Provider;
const ApiConsumer = ApiContext.Consumer;
const withApi = (enhanced) => {
return (
<ApiConsumer>
{api => enhanced }
</ApiConsumer>
)
}
export default ApiContext;
export {ApiContext, ApiProvider, ApiConsumer, withApi};
in my client I would do something like this:
const api = new ApiManager({
host: apiHost,
baseUrl: apiBaseUrl,
key: apKey,
sessionToken: persistedSessionToken
});
ReactDOM.hydrate(
<Provider store={store}>
<PersistGate loading={<h1>loading...</h1>} persistor={persistor}>
<BrowserRouter>
<ApiProvider value={api}>
<Main />
</ApiProvider>>
</BrowserRouter>
</PersistGate>
</Provider>, document.querySelector('#app')
);
I have 3 questions:
Does this even make sense?
How would I set the sessionToken if it gets renewed by the user?
How do I rehydrate the api instance after a page reload using redux-persist?

If you just want to use one instance of ApiManager then I don't think you need Context API. Just create an instance and export/import it in files where you need it.
You can wrap that third party api manager to add some methods you need like setSessionToken to set/update sessionToken when app loads or when user receives/refreshes it.
Why bother with redux-presist in this case (even if you decide to use Context API) if Redux is not involved. If you want to store sessionToken so store it in localStorage or any other storage you want, it has simple API to set/retrieve values, you don't need to use a library for it.

Related

Using Redux dev tools with NextJS: how to find out what's going on in store when Redux is being called server side?

We have a NextJS application using next-redux-wrapper and Redux thunks. We have a page that works fine when we load the page via a local link, that is, it's rendered locally, but when we reload the page, thus rendering it on the server, our store remains (partially) empty: certain fields are never filled.
I'm using Redux dev tools to follow the actions, but all I ever see when I reload the page in the list of actions is ##init. When I put log statements in I see -- in the server-side console -- that my reducer is being called with valid values. However that field remains empty in the store, as shown by RDT in the browser.
The Redux actions are not showing up in the browser Redux Dev Tools console because they are happening on the server.
The store is set up as next-redux-wrapper instructs
// _app.ts
import withRedux from 'next-redux-wrapper';
import initStore from '../redux/store';
const makeStore = initialState => {
return initStore(initialState);
};
const MyApp = ({ Component, pageProps, apollo, store }: Props) => {
return (
<ApolloProvider client={apollo}>
<Provider store={store}>
<Sidebar />
<Component {...pageProps} />
</Provider>
</ApolloProvider>
);
};
MyApp.getInitialProps = async appContext => {
const { Component, ctx } = appContext;
const appProps = await App.getInitialProps(appContext);
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {};
const allProps = {
...appProps,
...pageProps
};
return { ...allProps };
};
export default withRedux(makeStore)(withApollo(MyApp));
How can I figure out what's happening in my Redux store if I can't use Redux Dev Tools to see? What I'd like to do is find out exactly where and when the values that are being passed to the reducers are overwritten with a blank value.
The answer turned out to be that I was dispatching a thunk within my thunk server side, and I suppose that the result didn't come back in time to make it to the store transfer from NextJS server to the client. When I made it a direct, async, call within my thunk, all worked fine.

Using React-Redux: How to Access Variables in the Store, within Nested Components

I am using React-Redux, but I am not able to figure out how to access a variable in the Redux store inside of my nested components.
How can I share a variable between components, using React-Redux?
For example:
I have an 'index.js' file and 30 nested components. Managing these components becomes difficult after a while.
I have a 'C1.js' component. Let's just say I wrote this code in it.
function Reducer(state = 'example' , action) {
return state;
}
const store = createStore(Reducer)
index.js file:
<Provider store = {store}>
<App/>, document.getElementById('root')
</Provider>
How do I pass the 'store' variable to the 'C1.js' component to the index.js file?
Thanks...
You need to use something called "Connect" to connect your various components to the provider.
In the file that contains your C1.js component:
import {connect} from 'react-redux'
const MyComponent = () => {
let someData = props.someData
return(
//all of your JSX for your component here
)
}
const mapState = state => {
return {
someData: state.someData
}
}
export default connect(mapState)(MyComponent)
In the code above, notice the mapStateFunction. Connect is hooking that up with the Provider, and the state that is on the Provider. So that is where you are able to link whatever properties are on your Provider (React-Redux) state with this particular data.
Now, in your component, you will now have prop.someData
-
In the index file, you have your Provider in the wrong place, you need to change your code to this:
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
See, the difference there? The is the React Element (and all of its children that you are asking React to render to the DOM). It is the first parameter of the ReactDOM.render function.
The second parameter to the ReactDom.render function is the element in the DOM where you want it to put all of your React elements.
You did not configure well redux and react. You need to go over the doc of redux to setup correctly. Should get working after that.

ApolloConsumer VS import client

Why should I use ApolloConsumer instead of importing directly the client in my module ?
From the doc I should do somthing like :
// Module A.js initiate client
const client = new ApolloClient({
// init cache, links, typeDefs...
});
export default client;
// Module index.jsx
import client from 'A';
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root'));
// Any other component not using Query or Mutation
const Other = () => (
<ApolloConsumer>
{
client => {
// use client
}
}
</ApolloConsumer>);
But why not just import client without ApolloConsumer ?
// Module OtherBis
import client from 'A';
const AltOther () => {
// do something with client
return <div></div>;
};
Probably for the same reasons why you shouldn't import store directly:
Similarly, while you can reference your store instance by importing it directly, this is not a recommended pattern in Redux. If you create a store instance and export it from a module, it will become a singleton. This means it will be harder to isolate a Redux app as a component of a larger app, if this is ever necessary, or to enable server rendering, because on the server you want to create separate store instances for every request.
In my opinion, ApolloConsumer component is created for support JSX just like react-relay for relay. It is not necessary. Actually I have never used the ApolloConsumer. Moreover there are hooks(eg. useQuery, useMutation, useSubscription) which can do everything. They just make a tool. Whether use it or not is up to you.

React router consume my url request to back-end API

I'm building an SPA react-redux app at client-side, asp.net core back-end API.
Everythings run perfectly with IISExpress when debugging.
But when I deploy to IIS as a web application nested default website with the alias "mysubdomain". Everything still runs ok except export function.
(1st case): Open browser, enter download API link: http://localhost/mysubdomain/api/v1/export?filterparam. A save dialogue open. That was my expectation.
(2nd case: normal case): open my site (homepage): http://localhost/mysubdomain then click export, a new window open by the link:
http://localhost/mysubdomain/api/v1/export?filterparam.
I was expecting that savefile popup opening similar to (1st case) but NO. Browse return my components/Layout rendered.
I don't know what happening with react router/route? Or I make something wrong? I guess that react-router just consume my URL-request then render my Component, instead of call to my back-end API.
My export function inside redux store:
export: (filterParams) => async (dispatch, getState) => {
const url = `api/v1/export?${filterParams}`;
window.open(url,'_blank');
}
Back-end API:
[HttpGet]
[Route("download")]
public async Task<IActionResult> Download(DailyDischargeUrlQuery urlQuery)
{
var stream = await _dailyDischargeRepository.ExportAsCsvStream(urlQuery.DischargeDate, urlQuery.MRN, urlQuery.GetCompanies(), urlQuery.GetOrders());
return File(stream, "text/csv", "dailydischarge.csv");
}
index.js
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const history = createBrowserHistory({ basename: baseUrl });
const initialState = window.initialReduxState;
const store = configureStore(history, initialState);
const rootElement = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
rootElement);
registerServiceWorker();
App.js
import 'core-js';
import React from 'react';
import {Route, Router} from 'react-router';
import Layout from './components/Layout';
import Home from './components/Home';
export default () => (
<Layout>
<Route exact path='/' component={Home}/>
</Layout>
);
The problem has been resolved!
That because of service worker built-in create-react-app.
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.

How can I inject props into a Provider component?

I have an entry point file that exports a Provider wrapping a component. For the life of me I can't get props into it. Any ideas?
entry point file that I call sometime in my app. It is a seperate file, and lives outside my app. Thus, the wrapping of a Provider.
const App2 =
<Provider store={myOtherStore}>
<Application />
</Provider>
;
export default App2;
I tried this and some other ways.. but no luck. The entry component and any other component tied to Application does not see any new props other than "actions and state"
const App2 = (props) =>
return (
<Provider store={myOtherStore}>
<Application data={props} />
</Provider>
)
;
export default App2;
basically, in my working file I'd call that above like so:
const newApp = React.cloneElement(newApplication, {
productID
});
Edit: In the Application component (the one the Provider surrounds), I am using connect on that and bringing in the actions/state. It is just in this component "Application" I do not have access to the props I am trying to pass in. I just see {actions:{}, state: {}}

Resources