WiX navigation Access redux store from registered component - reactjs

I use WiX navigation with redux.
https://wix.github.io/react-native-navigation/#/usage
In app.jsx file i register all my screens with redux store :
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
registerScreens(store, Provider);
In registerSceens.js i bind the redux store :
export default (store, Provider) => {
Navigation.registerComponent('aApp.FooComponent', () => FooComponent, store,
Provider);
}
So in FooComponent, how can i easily access to the redux store ?
export default class FooComponent extends Component {
constructor(props) {
super(props);
console.log(store); //i need REDUX store
}

You need to connect your component, then you can get the store from the context, something like this:
class FooComponent extends Component {
static contextTypes = {
store: object, // <--- allow this component to access the store
};
componentWillMount(props) {
const { store } = this.context; // <-- Get it!!
console.log(store); //i need REDUX store
}
}
export default connect()(FooComponent);
Once you have defined the contextTypes, you can access the store from this.context.
While you can do this... the question is... why would you do something like that? connect already allows you to access the state and bind actions, that's usually all you need. As a rule of thumb, is always a good idea to keep our components as dumb as possible.

You need connect your FooComponent to redux using react-redux. After that you can access anything via props.

Related

How to get component own props from decorator function

My component needs to use properties that I'm passing when I creating the component (OwnProps), plus it needs to use props from MobX Store. I know how to connect component that is using only Mobx Store or only OwnProps. But I can't find a way to have both. Here is an example I wrote. Help me, please.
// Main App.tsx inside Router
// Properties I passed here I'll call own props.
<Container
{...match.params}
id={match.id}
/>
// Container.tsx. Container logic and types are here.
// Own props
import { observer, inject } from 'mobx-react'
export interface ContainerOwnProps {
id: number,
}
// Own props with connected MobX store
type ContainerProps = ContainerOwnProps & {
store: ContainerStore
}
// In this decorator, I want to inject my MobX store and save my types.
// And this is doesn't work, cause I don't know how to pass ownProps argument when I export it below
const connector = (Component: React.FC<ContainerProps>, ownProps: ContainerOwnProps) =>
inject((state: Stores) : ContainerProps => { // in Stores object I desided to place all my components stores
return {
...ownProps,
store: state.ContainerStore
}
})(observer(Component));
// Main react component. Which uses Own props and props from store
const Container: React.FC<ContainerProps> =
(props: ContainerProps) => {
return (
<p id={props.id}>props.store.text</p>
);
}
// [Error] It would be nice to know how to put "ownProps" here
export default connector(Container, ownProps)
So, I researched it a bit. And I found out that the "inject" pattern is outdated. I end up using "mobx-react-lite" with hooks and react context. It's easy and corresponds to react best practice. Also, you can write your own hook to connect the store. The code looks something like this:
const storeContext = createContext(mobxStore)
const useStore = () => useContext(storeContext)
const store = useStore()

Get Redux Store In Presentational Component

This is a tagalong of this question here. In contrast to that question, I don't need my presentational component to be a class. Is there a way to retrieve Redux's store without using a class and the corresponding super() method?
container.js
function mapStateToProps(state) {
return {
a: state.a
};
}
function mapDispatchToProps(dispatch) {
return {
setA: a => dispatch(setA(a)),
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(container);
presentational.js
function b({ a }) {
return {
console.log(a) // returns undefined
}
}
Does the dispatch work the same way?
Yes you can access the store from anywhere you like. You just need to export it from the file where you create it.
import { configureStore } from '...';
export const store = configureStore(...);
Import the store in your presentational.js file
import {store} from '...'
// And now you can access the current state or perform a dispatch:
store.getState() // current state
store.dispatch()
EDIT
My previous answer was wrong apologies for that, actually functional components (the one without class) can also access redux state and they can do that using connect from react-redux the very same way class components do
Reason for previous wrong answer
I once long ago tried to use connect with functional components and it didn't work for some weird reasons but when I converted the functional component to class component it worked without making changes to any other logic so I concluded that only class components can access redux state.
But I was wrong as I tested my case in this sandbox link https://codesandbox.io/s/38yw3l6nom (please look out for sample component in containers folder)
Previous wrong answer (please don't read if you are looking only for the correct solution)
No, connect from 'react-redux' modules only works on class components. Also, super is a method called in a constructor and hence they can only be called in class. You can refer this link here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes.
If you want any data stored in redux in your presentational component you'll have to pass it through a container component which will have access to the redux store. Please read more about here https://redux.js.org/recipes/writing-tests#components.
If you want to use connect on a presentational component then you'll have to use composition. recompose to achieve that.
import {compose} from 'recompose';
const presentationalComponent = props => {
return (
<div>{//Your content here}</div>
);
}
function mapStateToProps(state) {
return {
a: state.a
};
}
function mapDispatchToProps(dispatch) {
return {
setA: a => dispatch(setA(a)),
};
}
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
)
)(presentationalComponent);
You'll not have to make container component this way.

How to connect component (file) to redux store

I need to create component/file/class whatever and connect it to redux store.
I don't need this component to be rendered.
It's just a helper component which can contain methods that return value from store.
I tried to create a file with:
export function getKeyFromReduxStore(key:string) {...
but this is just a file that export function and I can't (or don't know) how to connect it to redux store.
All my components are connected with store throught:
<RouterWithRedux>
<Scene key="root">...
but as I said this is no scene it's just helper component that I want to reuse through whole app.
How can I make such a component and connect it to redux?
Redux store has a method called getState which gets the state of the store. You can import the store you have created in the file where redux store is required.
// in the file where you created your store
import { createStore } from 'redux';
export const myStore = createStore(
// ... some middleware or reducer etc
)
// in your component/file/class
import { myStore } from '../path/to/store'
export function getKeyFromReduxStore(key:string) {
return (myStore.getState())[key];
}
Alternatively, you can pass in the store to getKeyFromReduxStore function and call it in react component where store would be available. For instance in the mapStateToProps function:
// component/file/class
export function getKeyFromReduxStore(store, key) {
return store[key];
}
// react component with access to store
import { getKeyFromReduxStore } from '../path/to/file';
class MyKlass extends Component {
// ... your logic
render() {
const val = this.props.getKey(someVal);
}
}
const mapStateToProps = (state) => ({
getKey: (key) => getKeyFromReduxStore(store, key),
})
export default connect(mapStateToProps)(MyKlass);

Subscribe the state - Redux

I'm trying to display some data which will always be updated but when I add some new data to store, the new data is not seen on the screen as I didn't know about subscribe to store method. But I don't know where to use it and how to use it. I couldn't find any suitable example for my project.
First possibility to use as I did search on it (use it like mapStateToProps);
const mapStateToProps = (state) => {
return {
dashboardsList: state.header.dashboardsList,
templatesList: state.header.templatesList
}
}
DashboardDropdown.propTypes = {
dashboardsList: PropTypes.array,
templatesList: PropTypes.array
};
export default connect(mapStateToProps, null)(DashboardDropdown);
Let's say I want to subscribe to state.header.templatesList, how can I write it?
Or should I subscribe the state.header.templatesList in the app-store.js?
This is my store class;
const RootReducer = (state = {}, action) => {
return {
[HeaderModule.constants.NAME]: HeaderModule.reducer(
state[HeaderModule.constants.NAME],
action
),
[AuthModule.constants.NAME]: AuthModule.reducer(
state[AuthModule.constants.NAME],
action
),
[DashboardViewModule.constants.NAME]: DashboardViewModule.reducer(
state[DashboardViewModule.constants.NAME],
action,
),
[TemplateViewModule.constants.NAME]: TemplateViewModule.reducer(
state[TemplateViewModule.constants.NAME],
action,
),
[WidgetContainerModule.constants.NAME]: WidgetContainerModule.reducer(
state[WidgetContainerModule.constants.NAME],
action
)
}
}
const Store = createStore(RootReducer, applyMiddleware(thunk, logger()));
export default Store;
If I should subsribe it here, how can I again write it?
Thanks a lot!
I think I can help you with this - you'll have to add some code to your components that will map the Redux state to that component's props.
First, install react-redux - $ npm install --save react-redux, if you haven't yet.
Something like:
MyComponent.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => ({
state
});
class MyComponent extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
console.log(this.props.state)
}
render(){
return(
<div>Hello</div>
)
}
}
export default connect(mapStateToProps, undefined)(MyComponent);
When this loads up, you'll see that console.log(this.props.state) will refer to the Redux state, because we have mapped the state (as in the Redux state) to the props of the component. When Redux updates, that should 'subscribe' the component to those changes.
If DashboardDropdown (the default export of that file) is rendered on the DOM as of now, then you are now subscribed to the store. Whenever the global state (store) changes, every mapStateToProps in every ConnectedComponent will be invoked giving the component (DashboardDropdown) the new props.

Nested components testing with Enzyme inside of React & Redux

I have a component SampleComponent that mounts another "connected component" (i.e. container). When I try to test SampleComponent by mounting (since I need the componentDidMount), I get the error:
Invariant Violation: Could not find "store" in either the context or
props of "Connect(ContainerComponent)". Either wrap the root component
in a , or explicitly pass "store" as a prop to
"Connect(ContainerComponent)".
What's the best way of testing this?
Enzyme's mount takes optional parameters. The two that are necessary for what you need are
options.context: (Object [optional]): Context to be passed into the component
options.childContextTypes: (Object [optional]): Merged contextTypes for all children of the wrapper
You would mount SampleComponent with an options object like so:
const store = {
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ... whatever state you need to pass in ... })
}
const options = {
context: { store },
childContextTypes: { store: React.PropTypes.object.isRequired }
}
const _wrapper = mount(<SampleComponent {...defaultProps} />, options)
Now your SampleComponent will pass the context you provided down to the connected component.
What I essentially did was bring in my redux store (and Provider) and wrapped it in a utility component as follows:
export const CustomProvider = ({ children }) => {
return (
<Provider store={store}>
{children}
</Provider>
);
};
then, I mount the SampleComponent and run tests against it:
it('contains <ChildComponent/> Component', () => {
const wrapper = mount(
<CustomProvider>
<SampleComponent {...defaultProps} />
</CustomProvider>
);
expect(wrapper.find(ChildComponent)).to.have.length(1);
});
Option 1)
You can wrap the container component with React-Redux's Provider component within your test. So with this approach, you actually reference the store, pass it to the Provider, and compose your component under test inside. The advantage of this approach is you can actually create a custom store for the test. This approach is useful if you want to test the Redux-related portions of your component.
Option 2)
Maybe you don't care about testing the Redux-related pieces. If you're merely interested in testing the component's rendering and local state-related behaviors, you can simply add a named export for the unconnected plain version of your component. And just to clarify when you add the "export" keyword to your class basically you are saying that now the class could be imported in 2 ways either with curly braces {} or not. example:
export class MyComponent extends React.Component{ render(){ ... }}
...
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
later on your test file:
import MyComponent from 'your-path/MyComponent'; // it needs a store because you use "default export" with connect
import {MyComponent} from 'your-path/MyComponent'; // don't need store because you use "export" on top of your class.
I hope helps anyone out there.
There is also the option to use redux-mock-store.
A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.
The mock store provides the necessary methods on the store object which are required for Redux.
You can specify optional middlewares and your app specific initial state.
import configureStore from 'redux-mock-store'
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {}
const store = mockStore(initialState)
const wrapper = mount(<SampleComponent store={store}/>)
You can use name export to solve this problem:
You should have:
class SampleComponent extends React.Component{
...
render(){
<div></div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SampleComponent)
You can add a export before class:
export class SampleComponent extends React.Component{
and import this component with no redux store:
import { SampleComponent } from 'your-path/SampleComponent';
With this solution you don't need to import store to your test files.
in an attempt to make the use of decorator syntax more testable I made this:
https://www.npmjs.com/package/babel-plugin-undecorate
input:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
output:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
export class __undecorated__AnyOldClass {
method() {
console.log('hello');
}
}
Hopefully this can provide a solid Option 3!

Resources