G'day, I am trying to do something simple, and cant get a hold of it.
I have a number of forms. A LOT of forms. To keep my API succinct on the client I want to pass two state values into my client API.
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { Link, browserHistory } from 'react-router';
import { apiCreateData } from '../actions/api';
import { CREATE_PERSON } from '../actions/api';
import _ from 'lodash';
The apiCreateData is a generic client side API which needs to simply call a server API with the form contents, a TARGET URI of the server REST API, and a constant "which type of action is going on".
//------------------------------------
class CreatePeople extends Component {
//------------------
constructor(props) {
super(props);
this.state = {
target: 'add_person',
type: CREATE_PERSON
};
}
I can see these in my REACT tools in Chrome. The 'go' button calls this code.
//----------------------
onSubmit = (fields) => {
console.log('onSubmit');
console.log(fields);
this.props.apiCreateData(...this.state, fields).then(() => {
browserHistory.push('/');
});
}
I can not get those two bits of state into the receiving props in the client API. I tried heaps of stuff! Any help appreciated.
Cheers,
Mark.
Related
I have inherited a project and are very new to React.
The problem is that I don't know how to extract an URL segment like 123456 from the URL page/123456.
Here is some code for I sidebar I want to use the URL segment in. I kept it short so it is as clear as possible where I want the URL segment.
import React, { Component } from 'react';
import { withRouter, Link } from 'react-router-dom';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { push } from 'connected-react-router';
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
URL segment:
</div>
);
}
}
export default compose(withRouter))(Sidebar);
If you have a router configured properly then you might be able to find that value as a string in the this.props.match.params. You can take a look to the following example(please note that it's using react hooks and the main difference for you would be to use this.props.match.params instead of useParams()).
P.S. you should call withRouter instead of passing it to the compose method. E.g. withRouter(Sidebar) and if you want to use it with connect I guess you can just call one inside the other: connect(...)(withRouter(Sidebar))
I am having a lot of trouble trying to implement tests for a component using the useSelector hook from react redux. I've seen some questions already about this subject but I didn't manage to fix my problem using the suggested solutions to those questions.
My component is pretty big so I won't post it all but the part giving me trouble looks like this :
Total.tsx
import React from 'react';
import clsx from 'clsx';
import i18next from 'i18next';
import { useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import Box from '#material-ui/core/Box';
import CustomTooltip from '../CustomTooltip/CustomTooltip';
import SkeletonTotal from 'components/Skeletons/Total';
import { ApplicationHelper } from 'helpers';
import './Total.scss';
//Some interfaces here for types since this is in TypeScript
function Total(props: TotalProps) {
const { currency } = useSelector(
(state: { currencyReducer: any }) => state.currencyReducer
);
...
}
I first tried to test it like another component that doesn't use redux like so :
Total.test.js (first attempt)
import React from 'react';
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
);
});
But I was getting an error saying I need a react-redux context value and to wrap my component in a Provider which led me to try this :
Total.test.js (attempt 2)
import React from 'react';
import { Provider } from 'react-redux'
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Provider>
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
</Provider>
);
});
I am now getting a "Cannot read property 'getState' of undefined" error for the Provider component. I did try to mock a store to pass to my Provider as well as using jest to mock a return value like so
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({currency: 'cad'})
Unfortunately I was unsuccessful to make this work and could not find a working solution in the other questions that might relate to this. Any ideas how I could make this work? Thanks
The useSelector hook relies on the redux Context in order to access the state, so it must be inside of a Provider component in order to work. Your second attempt is on the right track, but you haven't set the store prop on the Provider, so the store is undefined and you get error "Cannot read property 'getState' of undefined".
Since you'll likely have many components that you'll want to test with redux context, the redux docs suggest creating your own version of the react testing library's render function which wraps the element in a provider before rendering it. This new render function adds two new optional options to the standard RTL options: initialState and store.
You can basically copy and paste that entire test-utils.js example from the docs, but I modified the return to include the created store so that we can dispatch to it directly (rather than just interacting with the component in ways that will dispatch an action).
return {
...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }),
store
};
With typescript annotations.
Inside your component test file, you will use your test-utils to render the Total component. It's fine to return the container element but you don't actually need to because you can query matching elements on the global RTL screen object or on the bound queries for your base element. We are basically looking to see that the outputted HTML code matches the expectations. You could test the selector itself in isolation, but it seems like you are trying to test the component.
Your test might look something like this:
import React from "react";
import Total from "./Total";
import { render, screen } from "./test-utils";
// if you want events: import userEvent from "#testing-library/user-event";
test( 'gets currency from redux', () => {
// render with an initial currency
const { store, container, getByLabelText } = render(
// not sure where these props come from, presumable constants in the file
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />,
{ initialState: { currency: USD } }
);
// some sort of RTL matcher or document.querySelector
const currencyElement = getByLabelText(/currency/i); // uses regex for case-insensitivity
// some sort of check for value
expect(currencyElement?.innerText).toBe("USD");
// dispatch an action to change the currency
// might want to wrap in `act`?
store.dispatch(setCurrency("EUR"));
// check that the value changed
expect(currencyElement?.innerText).toBe("EUR");
});
Working example that I created based on a basic counter component.
I've been following the complete react native and redux guide on Udemy and there is this part where despite following down to a tee. My LibraryList component still gets called twice.
What could be the problem?
LibraryList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class LibraryList extends Component {
render() {
console.log(this.props);
return;
}
}
function mapStateToProps(state) {
return {
libraries: state.libraries
};
}
export default connect(mapStateToProps)(LibraryList);
App.js
import React from 'react';
import { View } from 'react-native';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducers from './reducers';
import { Header } from './components/common';
import LibraryList from './components/LibraryList';
const App = () => {
return (
<Provider store={createStore(reducers)}>
<View>
<Header headerText='Tech Stack' />
<LibraryList />
</View>
</Provider>
);
};
export default App;
LibraryReducer.js
import data from './LibraryList.json';
export default () => data;
index.js inside reducers folder
import { combineReducers } from 'redux';
import LibraryReducer from './LibraryReducer';
export default combineReducers({
libraries: LibraryReducer
});
LibraryList.json
[
{"id": 0,
"title": "Webpack",
"description": "Webpack is a module bundler. It packs CommonJs/AMD modules i. e. for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand."
},
....
]
Expected result: console.log(this.props) runs once and return libraries
Actual result: It runs twice
I had a similar problem with one of my projects.
In LibraryList.js instead of extending Component use PureComponent. PureComponents won't call the render function if the state and the props have not changed, since it integrates a simple check in the shouldComponentUpdate method. https://reactjs.org/docs/react-api.html#reactpurecomponent
You could always implements your own shouldComponentUpdate method with a React Component instead of using the PureComponent. You'll need to be careful when implement the shouldComponentUpdate method with Redux, you may create more bugs. https://redux.js.org/faq/react-redux#why-isn-t-my-component-re-rendering-or-my-mapstatetoprops-running
When I tried your example on Android and iOS, I wasn't able to replicate your issue, it only returned once.
I am trying to add the redux-file-upload library into a redux application.
In my component I am just adding the component exported from the lib.
I can see that the store is referred via context inside the library.
Sample code is as below,
import React, { Component } from 'react';
import { FileUpload } from 'redux-file-upload';
class Upload extends Component {
render() {
return (
<FileUpload
allowedFileTypes={['jpg', 'pdf']}
dropzoneId="fileUpload"
url="/api/path/action"
>
<button> Drag or click here
</button>
</FileUpload>
);
}
}
export default Upload;
However I get error as
Uncaught TypeError: dispatch is not a function
Any ideas? Guess it is some mistake in importing the component.
Uncaught TypeError: dispatch is not a function
I have come across this error many times for different reasons. I can't dive into the source code of that library, but it looks like that library is not able to get dispatch function from the redux store. Have you tried connecting your component to the redux store using connect() method? It's a long shot. Thought, it might work! Let me know...
UPDATE
I'm throwing related links here, hoping that you'd find the relevant piece of code.
https://github.com/reactjs/react-redux/issues/108
React with Redux? What about the 'context' issue?
https://github.com/reactjs/react-redux/issues/108
You need import 'redux-form' , your code should be like this...
import React, { Component } from 'react';
import { FileUpload } from 'redux-file-upload';
import {reduxForm} from 'redux-form';
class UploadForm extends Component {
render() {
return (
<FileUpload
allowedFileTypes={['jpg', 'pdf']}
dropzoneId="fileUpload"
url="/api/path/action"
>
<button> Drag or click here
</button>
</FileUpload>
);
}
}
export default reduxForm({
form: 'upload-form',
fields: ['fileUpload']
})(UploadForm);
Are you using a middleware with you store, like redux-thunk/redux-promise?
The library you are using requires it:
"Please note - a middleware that passes dispatch to actions, e.g.
redux-thunk, redux-promise-middleware, is required for this package to
work properly."
So if you are using react-redux and the Provider yuo can do the following:
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import promise from 'redux-promise'
const storeWithMiddleware = applyMiddleware(promise)(createStore);
I'm using react-redux-starter-kit on one of my views I just want to redirects users is somethig is not set My components is fairly simple
// MyComponentWithRedirect.js
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux'
import {go} from 'react-router-redux'
class MyComponentWithRedirect extends Component {
static propTypes = {
userInfo: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
}
componentDidMount() {
if (this.props.userInfo.firstTime) {
this.props.dispatch(go('/welcome'))
}
}
}
const mapStateToProps = (state) => ({userInfo: state.userInfo})
export default connect(mapStateToProps)(MyComponentWithRedirect)
I can see the LOCATION_CHANGE action triggered but doesn't show the destination view
I've found I missed the setup of syncHistoryWithStore after that it's works like a charm using the react-router API
import { browserHistory } from 'react-router'
...
browserHistory.push('/welcome')
I dont need dispatch any more for change the location