how react-redux gets the store object of provider? - reactjs

Here is a basic react-redux app (sandbox):
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { useDispatch, Provider, useSelector, useStore } from "react-redux";
const App = () => {
const store = useStore(); // <- how it gets the store object of Provider ?
const state = useSelector(s => s);
return <div>{state}</div>;
};
ReactDOM.render(
<Provider store={createStore((state, action) => 5)}>
<App />
</Provider>,
document.getElementById("root")
);
Now my question is:
How hooks like useStore gets the store object we set in <Provider store={store}> ?
If it was dom, we could use this.closest('.provider').getAttribute('store') to get the store attribute of the provider element in parents. But how we can do it in react?
I'm asking it because i want to understand how react-redux works behind the scenes.
Thanks.

react-redux uses a provider that holds all of the properties it works with. It allows you to pull the internals from the provider through nice APIs such as the hooks API (useStore, useDispatch), or through the connect() HOC API.
To help you visualise it in a simpler way, let's write a "mini" react-redux using React Context API
import React, { createContext, useContext } from 'react';
const InternalProvider = createContext();
/**
* This is the `Provider` you import from `react-redux`.
* It holds all of the things child components will need
*/
const Provider = ({ store, children }) => {
/**
* This `context` object is what's going to be passed down
* through React Context API. You can use `<Consumer>` or
* `useContext` to get this object from any react-redux-internal
* child component. We'll consume it on our `useStore` and
* `useDispatch` hooks
*/
const context = {
getStore: () => store,
getDispatch: (action) => store.dispatch,
};
return (
<InternalProvider value={context}>
{children}
</InternalProvider>
);
}
/**
* These are the hooks you import from `react-redux`.
* It's dead simple, you use `useContext` to pull the `context`
* object, and voila! you have a reference.
*/
const useStore = () => {
const context = useContext(InternalProvider)
const store = context.getStore();
return context;
};
const useDispatch = () => {
const { getDispatch } useContext(InternalProvider);
return getDispatch();
};
/***************************************
* Your redux-aware components
*
* This is how you consume `react-redux` in your app
*/
const MyComponent = () => {
const store = useStore();
const dispatch = useDispatch();
return <>Foo</>
}
const App = () => (
<Provider store={store}>
<MyComponent />
</Provider>
)

Related

React context api catalog lazy load

In my react web app I need to load the whole catalog (metrics) and cache it on client side in order to let all components in my application use it. To store this catalog on client side I want to use react context api and I prefer to load this catalog lazily to avoid long app start.
Here's my solution:
I declare metrics context, exporting only one function getMetrics which returns promise from metrics array. This promise is created only once and is stored as Ref in context provider for future getMetrics executions.
metricsContext.tsx
import * as React from 'react'
import { createContext, useCallback, useRef } from 'react'
import { MetricModel, metricsApi } from '../api/metricsApi'
interface MetricsContext {
getMetrics: () => Promise<MetricModel[]>
}
export const metricsContext = createContext<MetricsContext>({ getMetrics: () => Promise.resolve([]) })
interface Props {
children: React.ReactNode
}
export const MetricsContextProvider = ({ children }: Props) => {
const metricsPromise = useRef<Promise<MetricModel[]>>()
const getMetrics = useCallback(async () => {
if (metricsPromise.current == null) {
metricsPromise.current = metricsApi.getAll()
}
return await metricsPromise.current
}, [])
const { Provider } = metricsContext
return <Provider value={{ getMetrics: getMetrics }}>{children}</Provider>
}
To use this context in the component I need to import context into it and resolve promise by executing useEffect.
SomeComponent.tsx
import * as React from 'react'
import { useContext, useEffect, useState } from 'react'
import { MetricModel } from '../../../api/metricsApi'
import { metricsContext } from '../../../state/metricsContext'
export const SomeComponent = () => {
const cntxt = useContext(metricsContext)
const [metrics, setMetrics] = useState<MetricModel[]>([])
useEffect(() => {
cntxt.getMetrics().then(res => setMetrics(res))
}, [cntxt])
return metrics.map(m => <div key={m.id}>{m.name}</div>)
}
So my questions are:
Is it normal to store Promise in ref?
Is it normal that context provider makes http requests lazily?

console.log(this.props.store) returning undefined in my create react app?

I created a create react app and included redux with card lists and a searchbox that displayed the filtered results, the app was working before I added redux but now it isn't returning any results. When I console.log(this.props.store) it is returning undefined. I would really appreciate it if someone can help me with this. My files are as below:
constants.js
export const CHANGE_SEARCH_FIELD = 'CHANGE_SEARCH_FIELD';
actions.js
import {CHANGE_SEARCH_FIELD} from './constants.js';
export const setSearchField = (text) => ({
type: CHANGE_SEARCH_FIELD,
payload: text
})
reducer.js
import {CHANGE_SEARCH_FIELD} from './constants.js';
const intialState = {
searchField: ''
}
export const searchTeacher = (state=intialState, action={}) => {
switch(action.type) {
case CHANGE_SEARCH_FIELD:
return Object.assign({}, state, { searchField: action.payload });
default:
return state;
}
}
index.js
import ReactDOM from 'react-dom';
import './index.css';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import App from './App.js'; //Our main parent component
import {searchTeacher} from './reducer.js';
import 'tachyons';
import * as serviceWorker from './serviceWorker';
const store = createStore(searchTeacher)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root') );
serviceWorker.unregister();
App.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import CardList from './CardList.js';
import {teacher} from './teacher.js';
import Searchbox from './searchbox.js';
import ErrorBoundry from './ErrorBoundry';
import Scroll from './Scroll.js';
import './App.css';
import {setSearchField} from './actions.js';
const mapStateToProps = state => {
return {
searchField: state.searchField
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSearchChange: (event) => dispatch(setSearchField(event.target.value))
}
}
class App extends Component {
constructor(){
super()
this.state = {
teacher: teacher, //teacher: [],
}
}
render(){
console.log(this.props.store);
const { searchField, onSearchchange } = this.props;
const filteredteacher= teacher.filter(
teacher =>{
return teacher.name.toLowerCase().includes(searchField.toLowerCase());
});
return(
<div className="tc">
<h1 className="f1"> Faculty Members ! </h1>
<Searchbox searchChange={onSearchchange} />
<Scroll>
<ErrorBoundry>
<CardList teacher={filteredteacher} />
</ErrorBoundry>
</Scroll>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
There won't be any props.store, because none of your code is passing down a prop named store to that component.
Components that have been wrapped in connect get props from three sources, combined:
Props passed from the parent component
Props returned from mapState
Props returned from mapDispatch
In this case, mapState is returning {searchField}, and mapDispatch is returning {onSearchChange}, and there's no props from the parent. So, the combined props are {searchField, onSearchChange}.
As a side note, you should use the "object shorthand" form of mapDispatch instead of writing it as a function:
const mapDispatch = {onSearchChange: setSearchField};
You will get two props from redux according to your code,
this.props.searchField
this.props.onSearchChange
connect function of react-redux used to connect react and redux.
mapDispatch is used to dispatch your actions which hold the payload(Second argument of connect function)
mapState is used to get the state of your properties(First argument of connect function)
So in your code, there is not any prop named store, Store is a global redux state which you can get with this method Store.getState() but here is store is redux store which you are passing here const store = createStore(searchTeacher) in your index.js file, This will show whole state of the redux store.
here is how you can get the state of your store.
How do I access store state in React Redux?
You will dispatch an action named onSearchChange like below in your on change method.
this.props.onSearchChange(e)
and redux will return you a value of this after storing in reducer with the name of this.props.searchField.
this.props.store would only be accessible if it was passed down from a parent component (which you are not doing here)
You create your store in index.js but you are not exposing an interface to it.
const store = createStore(searchTeacher);
You can expose these functions from your index.js file to reference the store:
export const getStore = () => store;
export const getState = () => { return store.getState(); };
Then from anywhere else (although not good practice):
import { getStore, getState } from 'index.js';

How do you debug a shallow rendered enzyme test?

I am trying to fix a failing test in my react-redux app. When I dive and dive again into my component, I expect to see the JSX within it. However, I don't see anything.
Here is my component -
const Dashboard = (props) => {
if (props.isSignedIn)
return (
<div className="dashboard">
<h1>Welcome</h1>
</div>
);
return null;
};
const mapStateToProps = (state) => {
return { isSignedIn: state.auth.isSignedIn };
};
export default connect(mapStateToProps, { signIn, signOut })(Dashboard);
Here is my failing test :-
const setup = (initialState = {}) => {
const store = storeFactory(initialState);
const wrapper = shallow(<Dashboard store={store} />).dive().dive();
return wrapper;
};
describe("on render", () => {
describe("the user is signed in", () => {
let wrapper;
beforeEach(() => {
const initialState = { isSignedIn: true };
wrapper = setup(initialState);
});
it("renders the dashboard", () => {
const component = wrapper.find("dashboard");
expect(component.length).toBe(1);
});
});
My store factory :-
export const storeFactory = (initialState) => {
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
console.log(initialState);
return createStoreWithMiddleware(rootReducer, initialState);
};
My test error :-
● the user is signed in › renders the dashboard
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
When I dive one time it looks like this :-
<Dashboard store={{...}} isSignedIn={{...}} signIn={[Function]} signOut={[Function]} />}
but when I try to see the JSX inside of the dashboard component I see nothing?
I'm pretty sure your setup isn't working because you're trying to shallow mount a redux connected component -- which is a higher-order component (HOC) wrapping another component that can't be properly dived into due to enzyme's shallow limitations.
Instead, you have two options:
Option 1.) Recommended: Export the Dashboard component and make assertions against it using shallow or mount (I mostly use mount over shallow specifically to avoid excessive and repetitive .dive() calls).
First export the unconnected component:
export const Dashboard = (props) => {
if (props.isSignedIn)
return (
<div className="dashboard">
<h1>Welcome</h1>
</div>
);
return null;
}
export default connect(...)(Dashboard)
Then in your test, import the Dashboard component (not the default connected export):
import { mount } from "enzyme";
import { Dashboard } from "./Dashboard"; // importing named export "Dashboard"
const initialProps = {
isSignedIn: false
}
const wrapper = mount(<Dashboard { ...initialProps } />); // alternatively, you can use shallow here
// now manipulate the wrapper using wrapper.setProps(...);
or
Option 2.) Not recommended: Wrap the component in a real Provider with a real store and mount the connected HOC:
import { mount } from "enzyme";
import { Provider } from "redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../path/to/reducers";
import types from "../path/to/types";
import Dashboard from "./Dashboard"; // importing connected default export
const store = createStore(rootReducer, undefined, applyMiddleware(thunk));
const initialProps = {
isSignedIn: false
};
const wrapper = mount(
<Provider store={store}>
<Dashboard { ...initialProps } />
</Provider>
);
// now you'll dispatch real actions by type and expect the redux store to change the Dashboard component
For more testing information, please take a look at this answer, which covers a similar example (skip to the bullet points; ignore the MemoryRouter pieces; and, while the example is a bit old, the testing pattern is the same).
The reason I'd recommend option 1 over option 2 is that it's much easier to manage, as you're directly manipulating the component you're testing (not a HOC wrapping your component). Option 2 is only useful if you absolutely need to test the entire workflow of redux to the connected component. I find option 2 to be mostly overkill as you can unit test each piece (actions, reducers, and unconnected components) individually and achieve the same testing coverage. In addition, as I mentioned in this example, I find redux-mock-store to be mostly useful for unit testing redux actions.
On a side note, you can see what enzyme sees by using console.log(wrapper.debug()); in a test!
Example unconnected test:
import React, { createElement } from "react";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { mount } from "enzyme";
import { MemoryRouter } from "react-router-dom";
import { Dashboard } from "./App.js";
configure({ adapter: new Adapter() });
const initialProps = {
isSignedIn: false
};
describe("Unconnected Dashboard Component", () => {
let wrapper;
beforeEach(() => {
/*
This code below may be a bit confusing, but it allows us to use
"wrapper.setProps()" on the root by creating a function that first
creates a new React element with the "initialProps" and then
accepts additional incoming props as "props".
*/
wrapper = mount(
createElement(
props => (
<MemoryRouter initialEntries={["/"]}> // use memory router for testing (as recommended by react-router-dom docs: https://reacttraining.com/react-router/web/guides/testing)
<Dashboard {...props} />
</MemoryRouter>
),
initialProps
)
);
});
it("initially displays nothing when a user is not signed in", () => {
expect(wrapper.find(".dashboard").exists()).toBeFalsy();
});
it("displays the dashboard when a user is signed in", () => {
wrapper.setProps({ isSignedIn: true });
expect(wrapper.find(".dashboard").exists()).toBeTruthy();
});
});
Working example (click on the Tests tab to run tests):
Reuseable HOC wrapper:
utils/HOCWrapper/index.js
import React, { createElement } from "react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom";
import { mount } from "enzyme";
import thunk from "redux-thunk";
import rootReducer from './path/to/reducers';
const middlewares = applyMiddleware([thunk]);
export const store = createStore(rootReducer, null, middlewares);
/**
* Factory function to create a mounted MemoryRouter + Redux Wrapper for a component
* #function HOCWrapper
* #param {node} Component - Component to be mounted
* #param {object} initialProps - Component props specific to this setup.
* #param {object} state - Component initial state for setup.
* #param {array} initialEntries - Initial route entries for MemoryRouter.
* #param {object} options - Optional options for enzyme's "mount"
* #function createElement - Creates a wrapper around passed in component (now we can use wrapper.setProps on root)
* #returns {MountedRouterWrapper}
*/
export const HOCWrapper = (
Component,
initialProps = {},
state = null,
initialEntries = ["/"],
options = {},
) => {
const wrapper = mount(
createElement(
props => (
<Provider store={store}>
<MemoryRouter initialEntries={initialEntries}>
<Component {...props} />
</MemoryRouter>
</Provider>
),
initialProps,
),
options,
);
if (state) wrapper.find(Component).setState(state);
return wrapper;
};
export default HOCWrapper;
To use it, import the HOCWrapper function:
import Component from "./Example";
import HOCWrapper from "./path/to/utils/HOCWrapper";
// if you need access to store, then...
// import HOCWrapper, { store } from "./path/to/utils/HOCWrapper";
const initialProps = { ... };
const initialState = { ... }; // optional (default null)
const initialEntries = [ ... ]; // optional (default "/")
const options = { ... }; // optional (default empty object)
// use the JSDoc provided above for argument breakdowns
const wrapper = HOCWrapper(Component, initialProps, initialState, initialEntries, options); // not all args are required, just the "Component"

Class component with Redux

I am new to React and Redux and as we know, it is best to use class component for those components that have state and the question I would like to ask is that Is it recommended to use functional component for components that have connection and interaction with Redux store since those components that interact with store do not have state locally.
As of version 7.x react-redux now has hooks for functional components
const store = useSelector(store => store)
So that we can use functional component with redux store like class component.
please check below link to get more idea about hooks
https://react-redux.js.org/next/api/hooks
It's perfectly fine to connect functional components to redux store.
Functional components don't have a state is not completely correct with hooks. You can add state to functional component with hooks.
Answering your question, you can connect functional component with redux store like below.
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";
const reducers = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
};
const store = createStore(reducers, 0);
const App = ({ count, handleIncrement, handleDecrement }) => {
return (
<div>
<button onClick={handleIncrement}>+</button>
<h4>{count}</h4>
<button onClick={handleDecrement}>-</button>
</div>
);
};
const mapStateToProps = state => {
return { count: state };
};
const mapDispatchToProps = dispatch => {
return {
handleIncrement: () => {
dispatch({ type: "INCREMENT" });
},
handleDecrement: () => {
dispatch({ type: "DECREMENT" });
}
};
};
const ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById("root")
);
Is it recommended to use functional components for components that have connection and interaction with the Redux store since those components that interact with the store do not have state locally.
Yes, it is recommended to use functional components with redux, and there is a way to have a local state in a functional component.
Why functional components are recommended?
The react ecosystem moves toward the use of hooks which means standardize the functional components.
As stated in docs about uses of hooks or classes:
In the longer term, we expect Hooks to be the primary way people write React components.
How to have a local state in functional components with redux?
Redux introduced redux-hooks API which gives functional components the ability to use local component state and allows to subscribe to the Redux store without wrapping your components with connect().
useSelector
useDispatch
useStore
// Creating a store
const store = createStore(rootReducer)
ReactDOM.render(
<Provider store={store}>
<CounterComponent />
</Provider>,
document.getElementById('root')
)
// CounterComponent.jsx Selector example
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
// Using the store localy with a selector.
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
// CounterComponent.jsx Dispatch Example
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
// Dispatching an action
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
// CounterComponent.jsx Referencing the store example
import React from 'react'
import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
const store = useStore()
// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update if the store state changes
return <div>{store.getState()}</div>
}

Could not find “store” in either the context or props of “Connect(App)” in Jest/React

I'm trying to run my first jest test but it seems like there's an issue with the way my react files are set up:
app_test:
it('renders without crashing', () => {
const app = shallow(<App />);
});
app.js
class App extends Component {
componentWillMount() {
this.fetchUserAccountInfo();
}
render() {
return <MainRoutes auth={this.props.loggedIn} />;
}
}
function mapStateToProps(state) {
return {
loggedIn: state.loggedIn.userLoggedIn,
};
}
export default connect(
mapStateToProps,
{ fetchUserAccountInfo }
)(App);
index.js (embeds App.s)
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
const token = localStorage.getItem("token");
const bugsnagClient = bugsnag({...//bugsnag stuff})
bugsnagClient.use(bugsnagReact, React);
const ErrorBoundary = bugsnagClient.getPlugin("react");
const RootApp = () => (
<ErrorBoundary>
<Provider store={store}>
<App id={token} />
</Provider>
</ErrorBoundary>,
);
ReactDOM.render(<RootApp />, document.getElementById('.app'));
They say i have an issue with shallow rendering "App"
Invariant Violation: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(App)".
I'm not sure what else I need to pass through App or if I need to move the Provider down?
The error log clearly states here that you need to provide a store to your component. To do so, you need to mock your store beforehand, and then give it to the <Provider /> component, containing the <App /> as child. Your file should look like this :
/* app_test */
import React from 'react';
import { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import App from './App';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
it('renders without crashing', () => {
const store = mockStore({}); // Instead of {}, you can give your initial store
shallow(
<Provider store={store}> // Provides the store to your App
<App />
</Provider>
);
});
This piece of code should do the trick !
For more info, you can check this page: https://redux.js.org/recipes/writing-tests

Resources