connect cannot find store in either the context or props - react-redux

Recently tried adding redux to one of the apps. One of the solutions is to have root component wrapped within Provider. Did this, but still seeing below issue (in the browser). [Pasted only potentially relevant code from files].
Uncaught Error: Could not find "store" in either the context or props
of "Connect(Gallery)". Either wrap the root component in a ,
or explicitly pass "store" as a prop to "Connect(Gallery)".
client/main.js
document.addEventListener('DOMContentLoaded', function() {
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('mount')
);
});
shared/App./js
class App extends React.Component {
render() {
return (
<BrowserRouter history={ browserHistory }>
<div>
<Route exact path="/" component={Gallery} />
<Route path="/viewitem/:id" component={ViewItem} />
</div>
</BrowserRouter>
);
}
}
shared/redux/index.js
export const reducers = combineReducers({
images: imageReducer,
});
export function configureStore(initialState = {}) {
const store = createStore(
reducers,
initialState,
applyMiddleware(...middleWare)
)
return store;
};
export const store = configureStore();
Could it be that BrowserRouter might not work with redux?

Related

Apollo GraphQL failing connection

My root component is already wrapped with an ApolloProvider tag, but the error message tells me it is not.
Error Message
Invariant Violation: Could not find "client" in the context or passed in as an option. Wrap the root component in an <ApolloProvider>, or pass an ApolloClient instance in via options.
This error is located at:
in App (created by ExpoRoot)
Problem is my root component is already wrapped with an ApolloProvider tag
React Native Code
IMPORT statements
import {
ApolloClient,
InMemoryCache,
useQuery,
ApolloProvider,
gql,
} from "#apollo/client";
CONNECTION WITH GraphQL
const client = new ApolloClient({
uri: "https://www.outvite.me/gql/gql",
cache: new InMemoryCache(),
defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network' } },
})
TEST QUERY
const USER_QUERY = gql`
query USER {
users {
nodes {
edge {
username
}
}
}
}
`
DEFAULT APP
THIS IS WHERE THE ERROR IS BEING THROWN
const { data, loading } = useQuery(USER_QUERY) is the line that traceback shows
export default function App() {
const { data, loading } = useQuery(USER_QUERY)
return (
<ApolloProvider client={client}>
<View>
<Text style={styles.text}>Open</Text>
<Text style={styles.text}>Another text</Text>
</View>
<Button title="Toggle Sidebar" onPress={() => toggleSidebarView()} />
<Button title="Change theme" onPress={() => toggleColorTheme()} />
</ApolloProvider>
);
}
If I'm not mistaken, the useQuery hook only works if you're in a component that is already wrapped in the ApolloProvider so you probably want to do something like this
export default function MainApp() {
const { data, loading } = useQuery(USER_QUERY)
return (
<View>
... use 'data' in here somewhere...
</View>
);
}
and then the top-level App component would look like
export default function App() {
return (
<ApolloProvider client={client}>
<MainApp />
</ApolloProvider>
);
}

React state variable being overwritten (useState)

I am having an issue where it seems my state variable is being overwritten by the default/initial value I set when calling useState(). I have const [token, setToken] = useState(""). I pass in the setToken function to my Login component, and when I log in I update the token via the setToken function.
In my App.js I only want to render the Login component if the token is not set, otherwise, I redirect to a Post.js component. Once I log in my token is being set and I am being redirected to my Posts.js page, the problem is if I refresh the page the token is overwritten again to the initial value and I'm not sure why that is.
My understanding is that useState() is only called on the initial render, otherwise the state is obtained from whatever is stored in state which since I called the setToken function, should be the state with the token, not an empty string.
What am I missing?
App.js
function App() {
const [errors, setErrors] = useState([]);
const [token, setToken] = useState("");
console.log('token', token)
return (
<Fragment>
<Router>
<Navbar />
<Route exact path="/" component={Landing} />
<Container>
{!errors.length ? <></> : <Alerts errors={errors} handleError={setErrors}/> }
<Switch>
<Route
exact path="/login"
render={ token ? (props) => <Redirect push to="/posts" {...props} /> : (props) => (<Login error={setErrors} isLoggedIn={setToken} {...props} />)}
/>
<Route exact path="/posts"
render={token ? (props) => <Posts token={token} {...props} /> : (props) => <Redirect push to="/login" {...props} />} />
</Switch>
</Container>
</Router>
</Fragment>
);
}
Pertinent section of Landing.js
const handleSubmit = async (e) => {
e.preventDefault();
const res = await login(email, password);
if(res.status !== 200){
props.error(res.data.errors);
}else{
props.error([])
props.isLoggedIn(res.data.token)
}
}
UPDATE:
I did figure out how to do what I wanted to do, by storing my token in local storage. In my App.js I'm doing this now
function App() {
const [errors, setErrors] = useState([]);
const [token, setToken] = useState(null);
useEffect(() => {
setToken(window.localStorage.getItem("token"));
})
This is not overwriting my token state as it was initially, so it's doing what I want, but I'm still wondering what I was doing wrong in my original implementation?
when you refresh the page your app runs from the beginning. If you don't want to lose your token on page refresh you should save/retrieve your to/from localStorage.

Jest + Enzyme: test Redux-form

My application has a lot of redux-form. I am using Jest and Enzyme for unit testing. However, I fail to test the redux-form. My component is a login form like:
import { login } from './actions';
export class LoginForm extends React.Component<any, any> {
onSubmit(values) {
this.props.login(values, this.props.redirectUrl);
}
render() {
const { handleSubmit, status, invalid } = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<TextField label="Email" name="email">
<TextField type="password" label="Password" name="password" autoComplete/>
<Button submit disabled={invalid} loading={status.loading}>
OK
</Button>
</form>
);
}
}
const mapStateToProps = (state) => ({
status: state.login.status,
});
const mapDispatchToProps = { login };
const form = reduxForm({ form: 'login' })(LoginForm);
export default connect(mapStateToProps, mapDispatchToProps)(form);
Mock the store, Import connected component
redux-form uses the store to maintain the form inputs. I then use redux-mock-store:
import ConnectedLoginForm from './LoginForm';
const configureStore = require('redux-mock-store');
const store = mockStore({});
const spy = jest.fn();
const wrapper = shallow(
<Provider store={store}>
<ConnectedLoginForm login={spy}/>
</Provider>);
wrapper.simulate('submit');
expect(spy).toBeCalledWith();
But in this way, the submit is not simulated, my test case failed:
Expected mock function to have been called with: []
But it was not called.
Mock the store, Import React component only.
I tried to create redux form from the testing code:
import { Provider } from 'react-redux';
import ConnectedLoginForm, { LoginForm } from './LoginForm';
const props = {
status: new Status(),
login: spy,
};
const ConnectedForm = reduxForm({
form: 'login',
initialValues: {
email: 'test#test.com',
password: '000000',
},
})(LoginForm);
const wrapper = shallow(
<Provider store={store}>
<ConnectedForm {...props}/>
</Provider>);
console.log(wrapper.html());
wrapper.simulate('submit');
expect(spy).toBeCalledWith({
email: 'test#test.com',
password: '000000',
});
In this case, i still got error of function not called. If I add console.log(wrapper.html()), I got error:
Invariant Violation: Could not find "store" in either the context or
props of "Connect(ConnectedField)". Either wrap the root component in
a , or explicitly pass "store" as a prop to
"Connect(ConnectedField)".
I cannot find documentations on official sites of redux-form or redux or jest/enzyme, or even Google.. Please help, thanks.
I used the real store (as redux-mock-store does not support reducers) and redux-form's reducer, it worked for me. Code example:
import { createStore, Store, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
form: formReducer,
});
let store;
describe('Redux Form', () => {
beforeEach(() => {
store = createStore(rootReducer);
});
it('should submit form with form data', () => {
const initialValues = {...};
const onSubmit = jest.fn();
const wrapper = mount(
<Provider store={store}>
<SomeForm
onSubmit={onSubmit}
initialValues={initialValues}
/>
</Provider>
);
const form = wrapper.find(`form`);
form.simulate('submit');
const expectedFormValue = {...};
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit.mock.calls[0][0]).toEqual(expectedFormValue);
});
});
You can find the answer here: https://github.com/tylercollier/redux-form-test
In short, you can use shallow dive() function to test higher-order component, but in your case, you have a higher-order component inside a higher-order component.
You need to break you component into two components, the first one is a presentation component, without
const form = reduxForm({ form: 'login' })(LoginForm);
export default connect(mapStateToProps, mapDispatchToProps)(form);
You then wrap the first component into the second component (container component).
You can easily test the first component (presentation component)
I had the similar problem. The answer can be found here https://github.com/airbnb/enzyme/issues/1002.
Long story short, you should pass store as a prop into your form and use .dive() function on the wrapper.
Regards
Pavel
I made a tool which helps with problems like that. It make a test-cases with real data (chrome extension collect it and save to file) which you can run by CLI tool.
I recommend you to try it: https://github.com/wasteCleaner/check-state-management

Redux: How to pass store to form created outside the Provider scope

I have written code, which uses a Modal dialog to display a form.
My react app is rendered at "root"
index.html
<div id="root"></div>
App.js
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<ExampleBasic/>
</Provider>
, document.getElementById('root'));
ExmpleBasic.js
Please ignore state management in component here. this is just for example.
import React, { PureComponent } from 'react';
import Lorem from 'react-lorem-component';
import Modal from '#atlaskit/modal-dialog';
import Button from '#atlaskit/button';
export default class ExampleBasic extends PureComponent {
state = { isOpen: false }
open = () => this.setState({ isOpen: true })
close = () => this.setState({ isOpen: false })
secondaryAction = ({ target }) => console.log(target.innerText)
render() {
const { isOpen } = this.state;
const actions = [
{ text: 'Close', onClick: this.close },
{ text: 'Secondary Action', onClick: this.secondaryAction },
];
return (
<div>
<Button onClick={this.open}>Open Modal</Button>
{isOpen && (
<Modal
actions={actions}
onClose={this.close}
heading="Modal Title"
>
<BasicFormContainer />
</Modal>
)}
</div>
);
}
}
BasicFormContainer.js
const mapStateToProps = state => ({
addDesignation: state.designations.addDesignation,
});
const mapDispatchToProps = dispatch => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(BasicForm);
BasicForm.js
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
class BasicForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit(values) {
console.log(values);
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.submit)}>
<Field
name="designationName"
component="input"
placeholder="Name"
label="Enter name"
autoFocus
/>
</form>
);
}
}
export default reduxForm({
form: 'BasicForm',
enableReinitialize: true,
})(BasicForm);
However modal is rendered using portal, outside current DOM.
As modal is rendered outside the scope of redux context, it is not getting the
store. and i am getting an error "Uncaught Error: Field must be inside a component decorated with reduxForm()"
Below is link to same kind of problem, where redux form within portal is not working.
Redux Form Wrapped Inside Custom Portal Component?
in React 16 it is handled by portals, but version before then that you can try something like as follow.
export default class ExampleBasic extends PureComponent {
...
static contextTypes = { store: React.PropTypes.object };
render() {
const { isOpen } = this.state;
const actions = [
{ text: 'Close', onClick: this.close },
{ text: 'Secondary Action', onClick: this.secondaryAction },
];
return (
<div>
<Button onClick={this.open}>Open Modal</Button>
{isOpen && (
<Modal
actions={actions}
onClose={this.close}
heading="Modal Title"
>
<Provider store={this.context.store}>
<BasicFormContainer />
</Provider>
</Modal>
)}
</div>
);
}
}
You need to pass in the values of BasicForm.js to the Redux store and dispatch an action from there itself and not from the BasicFormContainer.js. This way, the Modal remains inside of the scope of your root element and thus there is no need to access the store outside of the Provider.
Then update the Redux store based on the values entered in the form. Once, the store is updated, you can then access it from anywhere in your application such as Modal in your case.
I downgraded to version 2.1.0 to solve the problem.

How to richly style AOR Edit page

Have to create an edit Page editing a number of parameters on an instance of a'tale' resource.
However adding any element such as an MUI Card or even a div, is causing the app to freeze in various ways.
These are the approaches I have tried.
1) Adding a card component or placing my elements within a div for styling
export const EditorEditTale = (props) => {
return (
<Edit {...props} title="Tale Editor">
<SimpleForm >
<div>
<Image />
<TaleCardHeader props={ props } style={taleCardHeaderStyle.editor} />
</div>
</SimpleForm>
</Edit>
)
};
This is causing nothing to render.
Second approach, assuming that the record and basePath arent getting propagated to the children completely. Trying to use component like below.
const Input = ({record, basePath}) => {
return (
<div>
<LongTextInput source="taleText" />
</div>
)
}
This is causing the page to not render with everything in some kind of locking loop with the error - cannot read property touched of undefined.
How should I create a custom Edit page with a complex inputs and styling.
UPDATE: Been trying to write a custom form to substitute the SimpleForm component with no luck so far.
To create a custom form you can follow these steps:
make an exact copy of SimpleForm to your project.
rename SimpleForm to what you want.
fix all the relative imports.
test the new form until it works.
I made a minimum working form based on current master branch's SimpleForm
import React, { Children, Component } from 'react';
import PropTypes from 'prop-types';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import getDefaultValues from 'admin-on-rest/mui/form/getDefaultValues';
import FormField from 'admin-on-rest/mui/form/FormField';
import Toolbar from 'admin-on-rest/mui/form/Toolbar';
const formStyle = { padding: '0 1em 1em 1em' };
export class PostForm extends Component {
handleSubmitWithRedirect = (redirect = this.props.redirect) => this.props.handleSubmit(values => this.props.save(values, redirect));
render() {
const { children, invalid, record, resource, basePath, submitOnEnter, toolbar } = this.props;
return (
<form className="simple-form">
<Field name="name_of_a_field" component="input" />
{toolbar && React.cloneElement(toolbar, {
handleSubmitWithRedirect: this.handleSubmitWithRedirect,
invalid,
submitOnEnter,
})}
</form>
);
}
}
PostForm.propTypes = {
basePath: PropTypes.string,
children: PropTypes.node,
defaultValue: PropTypes.oneOfType([
PropTypes.object,
PropTypes.func,
]),
handleSubmit: PropTypes.func, // passed by redux-form
invalid: PropTypes.bool,
record: PropTypes.object,
resource: PropTypes.string,
redirect: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
save: PropTypes.func, // the handler defined in the parent, which triggers the REST submission
submitOnEnter: PropTypes.bool,
toolbar: PropTypes.element,
validate: PropTypes.func,
};
PostForm.defaultProps = {
submitOnEnter: true,
toolbar: <Toolbar />,
};
const enhance = compose(
connect((state, props) => ({
initialValues: getDefaultValues(state, props),
})),
reduxForm({
form: 'record-form',
enableReinitialize: true,
}),
);
export default enhance(PostForm);
The above code works for AOR's example.
I hope this helps.
(import might be slightly different when you have AOR as npm dependency :
import getDefaultValues from 'admin-on-rest/lib/mui/form/getDefaultValues';
import FormField from 'admin-on-rest/lib/mui/form/FormField';
import Toolbar from 'admin-on-rest/lib/mui/form/Toolbar';
)
Documenting my final answer. You have to create a custom Redux Form. You can use AOR Input components straight. They come prewrapped for Redux Form.
import { Field, reduxForm } from 'redux-form';
import compose from 'recompose/compose';
import { connect } from 'react-redux';
class StyledForm extends Component {
// Newer version of aor needs this function defined and passed to save buttons. All props are being passed by parent List component.
handleSubmitWithRedirect = (redirect = this.props.redirect) => this.props.handleSubmit(values => this.props.save(values, redirect));
render() {
const { handleSubmit, invalid, record, resource, basePath } = this.props
return (<div>
<form onSubmit={handleSubmit} >
<Card >
<CardText >
//This component simply displays data, something not possible very easily with SimpleForm.
<HeaderComp basePath={basePath} record={record} />
<Field source="category_id"
optionText="categoryName"
reference="categories"
resource={resource}
record={record}
basePath={basePath}
name="NAME OF THE FIELD IN YOUR REDUX DATASTORE"
component={REFERENCEFIELDCOMP} />
//create complex div structures now.
<div >
<span>Tale</span>
<Field resource={resource} record={record} basePath={basePath} name="taleText" component={TextInput} />
</div>
</CardText >
<MuiToolbar>
<ToolbarGroup>
<SaveButton handleSubmitWithRedirect={this.handleSubmitWithRedirect}/>
//Add custom buttons with custom actions
<Field record={record} name="status" component={EditButtons} />
</ToolbarGroup>
</MuiToolbar>
</Card>
</form>
</div>)
}
};
const enhance = compose(
connect((state, props) => ({
initialValues: getDefaultValues(state, props),
})),
reduxForm({
form: 'record-form',
enableReinitialize: true,
}),
);
export default enhance(StyledForm);
You will have to either import or copy getDefaultValues from AOR in the node modules.
I copied it into the file below.
import getDefaultValues from '../functions/getDefaultValues';
If you need a referenceField in your field. Then wrap it in a custom component like shown below
const DropDownSelector = ({optionText, ...props}) => {
return (
<ReferenceInput {...props} label="" >
<SelectInput optionText={optionText} />
</ReferenceInput>
)
}

Resources