I am writting React+Redux with typescript. I need to access the reference of one wrapped instance, like this.refs.items.getWrappedInstance(), but got typescript error Property 'getWrappedInstance' does not exist on type 'ReactInstance'
Which interface shall I give to items? Are there any interface defined by Redux that I can declare or I need to create my own?
I tried to google and search stackoverflow but cannot find an answer.
Thank you!
We got the same error using Flow and our most senior UI developer ultimately said to use // $FlowIgnore: some comment
I think that because accessing wrappedInstance is an antipattern flow and typescript may not support it...yet?
I am interested if anyone has a different answer.
I came across this issue and while I didn't find a native solution, I did manage to create one. Below is a working sample implementation with the solution.
Keep note of the { withRef: true } in the connect function.
Here is a small utility type to add the missing definition.
// connect.ts
type WrappedConnectedComponent<T> = {
getWrappedInstance(): T
}
export function unwrapConnectedComponent<T>(component: T): T | undefined {
if (component) {
const wrappedComponent: WrappedConnectedComponent<T> = component as any
if (wrappedComponent.getWrappedInstance) {
return wrappedComponent.getWrappedInstance()
}
}
return undefined
}
Here is a simple component that we'll be accessing later.
// SomeOtherComponent.tsx
import * as React from 'react'
import { connect } from 'react-redux'
class SomeOtherComponent extends React.Component {
log = () => {
console.log('logged')
}
render() {
return <div />
}
}
const mapStateToProps = () => ({})
const mapDispatchToProps = () => ({})
const ConnectedSomeOtherComponent = connect(
mapStateToProps,
mapDispatchToProps,
null,
{ withRef: true } // Important
)(SomeOtherComponent)
export default ConnectedSomeOtherComponent
export { SomeOtherComponent }
Here is the main component that does all the work.
// SomeComponent.tsx
import * as React from 'react'
import ConnectedSomeOtherComponent, { SomeOtherComponent } from './SomeOtherComponent'
import { unwrapConnectedComponent } from './connect'
class SomeComponent extends React.Component {
someOtherComponent?: SomeOtherComponent
private setSomeOtherComponent = (someOtherComponent: SomeOtherComponent) => {
this.someOtherComponent = unwrapConnectedComponent(someOtherComponent)
}
onClick = () => {
if (this.someOtherComponent) {
this.someOtherComponent.log()
}
}
render() {
return (
<div>
<button onClick={this.onClick} />
<ConnectedSomeOtherComponent ref={this.setSomeOtherComponent} />
</div>
)
}
}
export default SomeComponent
Related
Hi to all react geniuses. I am a newbie and I am trying to achieve a very simple thing here.
Below is the code, which tries to call a function sentTheAlert() on button click. However, I get error in my console.
import React from 'react';
import { connect } from 'react-redux';
import { Button } from 'reactstrap';
import { RouteComponentProps } from 'react-router-dom';
export interface IFancyAlerterProps extends StateProps, DispatchProps, RouteComponentProps<{}> {}
export class FancyAlerter extends React.Component<IFancyAlerterProps> {
handleSubmit= () => {
this.props.sendTheAlert('hello');
};
render() {
return (
<div>
<h1>Today Fancy Alert is {this.props.fancyInfo}</h1>
<Button color="primary" onClick={this.handleSubmit}>
See my Alert
</Button>
</div>
);
}
}
const SEND_MESSAGE = 'SEND_MESSAGE';
interface SendAlertType {
type: typeof SEND_MESSAGE;
payload: string;
}
function sendTheAlert(newMessage: string): SendAlertType {
return {
type: SEND_MESSAGE,
payload: newMessage,
};
}
const mapDispatchToProps = { sendTheAlert };
function mapStateToProps(state) {
return { fancyInfo: 'Fancy this:' + state.currentFunnyString };
}
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof mapDispatchToProps;
export default connect(mapStateToProps, mapDispatchToProps)(FancyAlerter);
Note: If this information helps, I create a jhispter application with react UI. And tried to add a new component (FancyAlerter). All the old components are able to get my function however, the new component is unable to get this function or any other function.
So, I just don't understand the mechanics I believe. Any help would be really appreciated.
UPDATE: In the above code, the props contains methods from RouteComponentProps but not from the other two types.
It looks like problem about using object for mapDispatchToProps. When you use mapDispatchToProps as an Object you should provide action creator, not void function :
const SEND_MESSAGE = 'SEND_MESSAGE'
interface SendAlertType {
type: typeof SEND_MESSAGE
payload: String
}
function sendTheAlert(newMessage: String): SendAlertType {
return {
type: SEND_MESSAGE,
payload: newMessage
}
}
const mapDispatchToProps = { sendTheAlert };
Later on you can fire an alert on middleware (saga, thunk, etc.).
Check for the usage : https://daveceddia.com/redux-mapdispatchtoprops-object-form/
Test your code : https://codesandbox.io/s/icy-lake-h3rxr?file=/src/CounterMapDispatchObj.js
Thanks for looking into the issue. I figured it out. Of course all your answers helped me eliminate the possible causes.
Seems like how a component is imported makes a lot of difference i the react router file where all the routes were defined.
Supposedly below is my route
<ErrorBoundaryRoute path="/fancy" component={FancyAlerter} />
And the way you import this component is
import FancyAlerter from './modules/fancyalert/fancyalert';
Instead of
import { FancyAlerter } from './modules/fancyalert/fancyalert';
I have two HOCs that add context to a component like so :
const withContextOne = Component => class extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
};
export default withContextOne;
Desired Result
I just want an syntactically concise way to wrap a component with this HOC so that it doesn't impact my JSX structure too much.
What I have tried
Exporting a component with the HOC attached export default withContextOne(withContextTwo(MyComponent)) This way is the most concise, but unfortunately it breaks my unit tests.
Trying to evaluate the HOC from within JSX like :
{ withContextOne(withContextTwo(<Component />)) }
This throws me an error saying
Functions are not valid as a React child. This may happen if you return a Component instead of < Component /> from render.
Creating a variable to store the HOC component in before rendering :
const HOC = withContextOne(Component)
Then simply rendering with <HOC {...props}/> etc. I don't like this method as it changes the name of the component within my JSX
You can set the displayName before returning the wrapped component.
const withContextOne = Component => {
class WithContextOneHOC extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
}
WithContextOneHOC.displayName = `WithContextOneHOC(${Component.displayName})`;
return WithContextOneHOC;
};
This will put <WithContextOneHOC(YourComponentHere)> in your React tree instead of just the generic React <Component> element.
You can use decorators to ease the syntactic pain of chained HOCs. I forget which specific babel plugin you need, it might (still) be babel-plugin-transform-decorators-legacy or could be babel-plugin-transform-decorators, depending on your version of babel.
For example:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
#withRouter
#resizeOnScroll
#injectIntl
#connect(s => s, (dispatch) => ({ dispatch }))
export default class FooBar extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
However, the caveat with decorators is that testing becomes a pain. You can't use decorators with const, so if you want to export a "clean" undecorated class you're out of luck. This is what I usually do now, purely for the sake of testing:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
export class FooBarUndecorated extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
export default withRouter(
resizeOnScroll(
injectIntl(
connect(s => s, ({ dispatch }) => ({ dispatch }))(
FooBarUndecorated
)
)
)
);
// somewhere in my app
import FooBar from './FooBar';
// in a test so I don't have to use .dive().dive().dive().dive()
import { FooBarUndecorated } from 'src/components/FooBar';
I have a connected container like so:
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { autobind } from 'core-decorators';
const propTypes = {
data: PropTypes.object,
userData: PropTypes.object,
};
class ConnectedContainer extends React.Component {
#autobind
doSomethingImportant() {
...
}
render() {
....
}
}
ConnectedContainer.propTypes = propTypes;
const mapStateToProps = state => ({ data, userData });
const mapDispatchToProps = (dispatch) => {
return { actions: bindActionCreators(actions, dispatch) };
};
export default connect(mapStateToProps, mapDispatchToProps)(ConnectedContainer);
I want to test doSomethingImportant, so I have test like so:
import React from 'react';
import { shallow } from 'enzyme';
import ConnectedContainer from '.....'';
import configureStore from 'redux-mock-store';
const mockStore = configureStore();
const store = mockStore({ getState: () => null, dispatch: () => null, data: { data from fixture }, userData: { data from fixture } });
const container = (
<ConnectedContainer
store={store}
actions={{}}
/>
);
describe('ConnectedContainer', () => {
describe('doSomethingImportant', () => {
it('returns something important', () => {
const wrapper = shallow(container);
expect(wrapper.instance().doSomethingImportant()).to.equal( ... some output here );
});
});
});
And no matter what I do, I get this error:
TypeError: wrapper.instance(...). doSomethingImportant is not a function
What is happening with my container that I'm unable to access its instance methods?
Test the unwrapped component
Export the ConnectedContainer class itself, not the wrapped version. You want to test your code, not the connect() function.
You can keep the default export as the wrapped component, and then add the word export in front of the class definition:
export class ConnectedContainer extends React.Component { // Named export, for testing purposes only
...
}
Then in your test, import { ConnectedContainer } from '....'
Render that with shallow instead of the default export.
Naming conventions
Also, naming your component ConnectedContainer is very confusing! It only becomes connected after it is wrapped with the connect function. So when you export ConnectedContainer (as I've suggested) you're actually exporting a non-connected component class. (And is it actually a container? That's also pretty vague.)
One naming convention people use is to define a constant that holds the return value of connect(), and name that xxxContainer, like so:
export class IconView extends React.Component { // Exported for testing
// ... class implementation
}
function mapStateToProps(){...}
function mapDispatchToProps(){...}
const IconViewContainer = connect(mapStateToProps, mapDispatchToProps)(IconView);
export default IconViewContainer; // Exported for use as a component in other views
Try:
const Container = (
<ConnectedContainer
store={store}
actions={{}}
/>
);
Note, the only difference is the C uppercase in container. The thing is that React only treats classes and methods as components if they start with a capital letter. That could be your problem.
Looking at the docs for react-async-poll I'm following the Usage example to integrate asyncPoll into my component, but I'm getting a Uncaught TypeError: dispatch is not a function complaint from within my onPollinterval function
import React, { Component } from 'react';
import { connect } from 'react-redux';
import asyncPoll from 'react-async-poll';
import { fetchCaCities, } from '../actions';
import MyMap from './my-map';
class CaliforniaMap extends Component {
componentDidMount() {
this.props.fetchCaCities();
}
render() {
return (
<div>
<h1>California Map</h1>
<MyMap center={[37.5, -120]} zoom={6} layers={[this.props.caCities]} />
</div>
);
}
}
const onPollInterval = (props, dispatch) => {
console.log(dispatch); // undefined
return dispatch(fetchCaCities());
};
const mapStateToProps = state => ({
caCities: state.map.california.caCities,
});
export default asyncPoll(60 * 1000, onPollInterval)(connect(
mapStateToProps, { fetchCaCities }
)(CaliforniaMap)
Maybe react-async-poll doesn't work for connected components?
According to the docs:
The dispatch parameter is only passed to [onInterval] if it is
available in props, otherwise it will be undefined.
The example they give is confusing because it does not define dispatch anywhere, but they show onPollInterval using it.
I seen 2 ways of doing the same thing but I am not sure what is the proper way.
Component
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {selectUser} from '../actions/index'
class UserList extends Component {
renderList() {
return this.props.users.map((user) => {
return (
<li
key={user.id}
onClick={() => this.props.selectUser(user)}
>
{user.first} {user.last}
</li>
);
});
}
render() {
return (
<ul>
{this.renderList()}
</ul>
);
}
}
// Get apps state and pass it as props to UserList
// > whenever state changes, the UserList will automatically re-render
function mapStateToProps(state) {
return {
users: state.users
};
}
// Get actions and pass them as props to to UserList
// > now UserList has this.props.selectUser
function matchDispatchToProps(dispatch){
return bindActionCreators({selectUser: selectUser}, dispatch);
}
// We don't want to return the plain UserList (component) anymore, we want to return the smart Container
// > UserList is now aware of state and actions
export default connect(mapStateToProps, matchDispatchToProps)(UserList);
https://github.com/buckyroberts/React-Redux-Boilerplate
Or
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
}
fetchTweets() {
this.props.dispatch(fetchTweets())
}
render() {
const { user, tweets } = this.props;
if (!tweets.length) {
return <button onClick={this.fetchTweets.bind(this)}>load tweets</button>
}
const mappedTweets = tweets.map(tweet => <li>{tweet.text}</li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
https://github.com/learncodeacademy/react-js-tutorials/tree/master/5-redux-react
The first way uses 2 different functions mapStateToProps() and matchDispatchToProps() while the other way uses #connect(....).
When I use the #connect I get a whole bunch of warnings saying that it has not been finalized and might change.
The # symbol is a decorator which is still considered experimental. So I would use that at your own risk. Your first code block is the safer way to do it as described in the official docs. Both blocks essentially do the same thing but decorators are more sugar than anything.
References:
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
What's the '#' (at symbol) in the Redux #connect decorator?
I think the first method will give you less problems in the end. Someone else can chime in though too.
The answer by Jackson is right in every sense however he is missing out the importance of using the first version for the usage of unit testing. If you want to be able to unit test a component (which usually means testing with the unconnected version) you need to be able to export the connected and unconnected component.
Using your example and assuming you are using jest/enzyme you could do something like this:
// notice importing the disconnected component
import { UserList } from '../relative/file/path/UserList'
import { mount } from 'enzyme'
describe('UserList', () => {
it('displays the Username', () => {
const users = [{fist: 'Person', last: 'Thing'}, ... ]
const UserList = mount(<UserList users={users} />)
export(UserList.find('li')[0].text()).toEqual('Person Thing')
});
});
Once you build larger projects being able to unit test will provide sanity to your coding life. Hope this helps