In react-admin get access to redux store - javascript

My question is related to react-admin repo.
I want to dispatch an action, outside of scope of a component, in order to do that, I've read that I need to get access to the actual redux store itself, and dispatch on in directly,
so I know that the Admin component has an initialState prop, but it only accepts default state object, not the store. So I can't make a store and pass it in.
My question is:
How do I access redux store of an Admin component?
How can I dispatch an action outside of a component, when using Admin as my main app component?
my current app entry looks like this:
<AppLayoutDirection>
<Admin
title="My App"
locale="en"
dataProvider={dataProvider}
authProvider={authProvider}
i18nProvider={i18nProvider}
theme={themeProvider}
customSagas={customSagas}
appLayout={AppLayout}
>
{DynamicResource}
</Admin>
</AppLayoutDirection>

When you say that you need to dispatch an action outside the scope of a component, I suppose that it's in reaction to another action that was dispatched in the past.
In that case, that's what react-admin calls a side effect. React-admin handles side effects using redux-saga. Here is how to create a custom saga:
// in src/bitcoinSaga.js
import { put, takeEvery } from 'redux-saga/effects';
import { showNotification } from 'react-admin';
export default function* bitcoinSaga() {
yield takeEvery('BITCOIN_RATE_RECEIVED', function* () {
yield put(showNotification('Bitcoin rate updated'));
})
}
Register this saga in the <Admin> component as follows:
// in src/App.js
import React from 'react';
import { Admin } from 'react-admin';
import bitcoinSaga from './bitcoinSaga';
const App = () => (
<Admin customSagas={[ bitcoinSaga ]} dataProvider={simpleRestProvider('http://path.to.my.api')}>
...
</Admin>
);
export default App;
This is documented in the react-admin documentation, in the <Admin> chapter.

You could also simply use custom reducers if the computation is no async
// in src/App.js
import React from 'react';
import { Admin } from 'react-admin';
import reducers from './reducers';
const App = () => (
<Admin customReducers={customReducers} dataProvider={simpleRestProvider('http://path.to.my.api')}>
...
</Admin>
);
export default App;

Related

Pass react-redux store and dispatch functions via props?

The following React component is given:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { store, StoreState } from "../../redux/actions";
import { setBackgroundAction } from "../../redux/title.actions";
import "./Loader.scss";
interface ReduxProps {
bgClass: string;
}
interface Props extends ReduxProps {
bgChange?: boolean;
}
export default function Loader(props: Props) {
const [bgClassOld, setBgClassOld] = useState<string>("");
const dispatch = useDispatch();
useEffect(() => {
const { bgChange, bgClass } = props;
if (bgChange) {
setBgClassOld(bgClass);
dispatch(setBackgroundAction("bg-white"));
dispatch(setBackgroundAction(bgClassOld));
}
});
return (
<div className="d-flex">
<div className="loader">
<img src="/loadscreen.gif" />
</div>
</div>
);
}
// function mapping(state: StoreState): ReduxProps {
// return {
// bgClass: state.title.backgroundClass,
// };
// }
This is more a theoretical question to see how to actually do the following change:
The component Loader will be imported from another npm package (shared components).
My problem is that I have a redux state in the current implementation included (changed it from Class to Functional component, so thats mapping() is still in there).
As I only import the component in my "main" client, I will not have the whole redux setup in place. So I think I need to pass the store and the dispatch functions via props.
So should I create a prop store for my component, where I pass the redux store when I import the shared component?
Do I also create two props for each dispatch functions?
Does is make sense or would there be a better approach?
You generally shouldn't import the Redux store directly into components. The hooks allow your component to access whatever Redux store has been injected into the component tree by a <Provider>.
You also don't need to pass dispatch as a prop. Any component can call useDispatch(), and dispatch actions to whatever Redux store is actually being used.
If I understand your question, you're planning on importing this component into an existing app, and it sounds like that app is already configured to use (React-)Redux with a <Provider> at the top. If that's the case, then you don't have to do anything else special to make this work. Just call the React-Redux hooks in any of your components.

Cannot access redux store in actions/api service (without connect() func)

I'm trying to fetch the token from my auth reducer in my app for making subsequent requests for more resources.
The problem is I can't access the store.getState() or store anywhere outside my components. Like actions/api service.
I remember earlier making an app where I was able to without any problems.
Here's a contrived example : https://stackblitz.com/edit/react-redux-app-1wxxab?file=index.js I've made a basic todo app and you can see in actions/index.js that when I console.log(store) I get undefined.
Update: I've updated the example to emphasise the problem, where I can't access it in a separate file api.js
Another Example: https://stackoverflow.com/a/43944684/1356046 they say it works like this but I'm not able to reproduce it.
Anyway to fix this and access the store state? Have tried everything since yesterday. Thanks.
Export store when you created it, then use it eg store.dispatch(action); or create api specific middleware
See What is the best way to access redux store outside a react component? for many examples
Update:
https://stackblitz.com/edit/react-redux-app-1mswrv
store.js:
import { configureStore } from "redux-starter-kit";
import rootReducer from './reducers'
export const store = configureStore({
reducer: rootReducer,
});
index.js:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import { store } from "./store";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
api.js:
import {store} from './store';
// Some promise which dispatches a fetch request after getting the token from the store
export const getSomething = () => {
console.log('store:', store);
return store;
}
Cloned your stackblitz and made the following changes:
In your api file do the following:
import store from './store';
Add a store.js with the content:
import { configureStore } from "redux-starter-kit";
import rootReducer from './reducers'
const store = configureStore({
reducer: rootReducer,
});
export default store;
And changed your index.js to:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import store from './store'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
I would still opt for saving a token in local storage, if the user is logged in and opens another tab the user needs to log in again because the other tab has no access to the token, same when the user goes to another site and then back to your site.
If you are using redux-thunk and passing your api with withExtraArgument() to the thunk middleware passed to createStore() then you can use a lazy callback function to inject selectors for your API token (or whatever else you'd like) to a wrapped API helper function.
Personally, I prefer this approach as it decouples the api helper from redux. The API helper does not have to know about redux at all for this to work. Insread of imports, you simply inject the selector into the API helper, which then calls them when needed (long after the store is initialized).
api.js
// function accepting callback that returns a pretty
// standard post function
export const post = (getHeaders) => (url, body) => {
return fetch(url, {
method: 'POST',
headers: getHeaders(),
...
}
}
store.js
import * as api from './utils/api';
import { selectAuthHeaders } from './features/auth'
const store = createStore(
rootReducer,
initialState,
applyMiddleware(
thunk.withExtraArgument({
post: api.post(() => {
return selectAuthHeaders(store.getState());
}
})
)
)

React native redux export default connect

I do not want to separate components when I am using react-navigation with redux.
How can I make a "const=" rather than make a new file and "export default"
const IntroScreen2 =connect(mapStateToProps,mapDispatchToProps)(IntroScreen2a)
const IntroScreen2 =()=> connect(mapStateToProps,mapDispatchToProps)(IntroScreen2a)
export default connect ...
which one is right?
https://codeshare.io/G79NRk
Do it something like this, define the component in the same file as where you use a default export of connect, passing in the component defined in the file.
These statements should help clear up your misunderstanding(s).
With react navigation, you have screens (components), and you have navigators. Navigators are created with screens (components).
You use react-redux's connect function to connect components to the redux store. You simply wrap a component in a call to connect, and export the return value of that, rather than the component itself.
When you create a navigator, you will need to import the components for your screens.
See the follow three pages, we make a component, export the component connected to the redux store, via react-redux's connect function.
Then we make a router, which exports a single stack navigator from react navigation, which defines a single screen, the component defined (mentioned above).
Then I have given an example of how you'd render that router, for example, inside your App.js.
some-component.js
import React, {Component} from "react";
import {connect} from "react-redux"
// Define the component
class SomeComponent extends Component {
render() {
return null;
}
}
// Map dispatch to props
const mapDispatchToProps = (dispatch) => {
return {};
}
// Map state to props
const mapStateToProps = (state) => {
return {};
};
// Export the component, passed into the connect function from react-redux.
export default connect (mapStateToProps, {}) (SomeComponent);
Then just import this file when defining your navigator with react navigation.
For example
router.js
import SomeComponent from "./some-component.js";
import {createStackNavigator} from "react-navigation";
export default createStackNavigator({
PageOne: {
screen: SomeComponent
}
});
In your App.js (root level)
import React, {Component} from "react";
import Router from "./router.js";
export default class App extends Component {
render () {
return <Router/>;
}
}
Something like that should get you sorted!

Is it bad practice to directly import actions into components?

Is something like this bad practice?
SendInfoButton.js
import React from 'react';
import { sendInfo } from '../actions/index';
export const SendInfoButton = ({currentUser}) => (
<div>
<button onClick={() => sendInfo(currentUser)} />
</div>
)
actions/index.js
import { store } from '../reducers/index';
import { SEND_INFO } from '../constants/index;
export const sendInfo = (currentUser) => store.dispatch({type: SEND_INFO, payload: currentUser})
It seems more efficient to import actions directly into the components this way, as opposed to using mapDispatchToProps and passing down actions to components that won't use them. I'm also more inclined to import actions like this because I already have components with a large number of props and would rather not add to that.
Importing the action creator, like import { sendInfo } from '../actions/index';, is fine - that's how you're supposed to do it.
However, you should then use connect to "bind" the action creators so that they access the correct store instance at runtime and dispatch the action automatically. This can be made shorter by using the "object shorthand" syntax - just pass an object full of action creators as the second argument to connect, like:
export default connect(null, {sendInfo})(SendInfoButton);
Similarly, you shouldn't import the store directly. As #estes said, that locks your code into the same "production" store instance all the time, and makes it harder to test or reuse your code.

Refresh contentComponent in react-navigation

I am using React-Navigation where I am using functionality of custom drawer by using contentComponent of React-Navigation.
const DrawerNavigation = DrawerNavigator({
DrawerStack: { screen: DrawerStack }
}, {
contentComponent: DrawerComponent,
drawerWidth: 300
})
Here DrawerComponent is my custom navigation drawer where I have used custom navigation items like username, profile picture, email address and other menus.
Now whenever user updates their profile I want to refresh my DrawerComponent, I am not able to find any way to do it. Can anybody suggest me a good way to implement this?
Couple of options here, and all are tight to how you want to achieve your state management.
First, one solution would be to have the your user state in the component creating the DrawerNavigator, and pass it down to your custom drawer component. This presents the disadvantage of having to recreate your navigator on state change and create a blink. I do not advice to use this solution but it's worth mentioning as a possibility.
You could also use a React Context, have your user state in a top level component, create a provider passing it the user as the value and make your drawer a consumer of this context. This way, every time the user changes your drawer component would re-render.
What I use personally is Redux to connect my Drawer directly to my global state. It involves a bit of setup but it's worth it in the end. A root component could look like this:
import React from 'react'
import { Provider } from 'react-redux'
export default () => (
<Provider store={store}>
<App />
</Provider>
)
Where store is the result of:
import { createStore, combineReducers } from 'redux'
import reducers from './reducers'
const store = createStore(combineReducers(reducers))
Your reducers are going to be the state of your app, and one would be dedicated to your user data.
Then your Drawer component could be:
import React, { Component } from 'react'
import { View, Text } from 'react-native'
import { connect } from 'react-redux'
#connect(({ user }) => ({ user }))
class Drawer extends Component {
render () {
const { user } = this.props
return (
<View>
<Text>My name is {user.name}</Text>
</View>
)
}
}
export default Drawer
Now, every time you change your user reducer, this Drawer component will re-render.
There is a few things your should know about Redux, so you should probably read up a bit the Getting Started docs.
I know it is a old question now but you can do this by importing the code like
import DrawerView from '../Drawer/Drawer'
contentComponent: DrawerView
then in the DrawerView file
class DrawerView extends Component {
render(){
return(
//Do your stuff here
)
}
}
export default DrawerView;
for more info please visit this link and thank to Kakul Gupta for this https://codeburst.io/custom-drawer-using-react-navigation-80abbab489f7
The easiest way to change menus without using redux is, using createSwitchNavigator.
https://reactnavigation.org/docs/en/auth-flow.html

Categories