import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropType from 'prop-types';
import Counter from '../components/Counter';
import * as counterActions from '../store/modules/counter';
class CounterContainer extends Component {
handleIncrement = () => {
console.log('+');
const { CounterActions } = this.props;
CounterActions.increment();
}
handleDecrement = () => {
console.log('-');
const { CounterActions } = this.props;
CounterActions.decrement();
}
render() {
const { handleIncrement, handleDecrement } = this;
const { number } = this.props;
return (
<Counter
onIncrement={handleIncrement}
onDecrement={handleDecrement}
number={number}
/>
);
}
}
I am using ESLint airbnb.
When I write the above code
ESLint
raise Error an 'CounterActions' is missing in props validation. And 'number' is missing in props validation
so i add
CounterContainer.propTypes = {
number: PropType.number,
CounterActions: PropType.func,
};
propType "number" is not required, but has no corresponding defaultProps declaration.
propType "CounterActions" is not required, but has no corresponding defaultProps declaration.
I do not know how to change it from now on.
I'm trying to apply ESLint while following the tutorial.
How do I change it?
It's asking for you to set defaultProps since they are both optional. Since it looks like they're both required for this component, set them to isRequired:
CounterContainer.propTypes = {
number: PropType.number.isRequired,
CounterActions: PropType.func.isRequired,
};
This will bypass the react/require-default-props rule for those props.
Related
I have the following parent React component. I would like to either pass the meeting.settings.ae (array of object) to the child useState Component. Don't know how I can fix it.
import React, {Component} from 'react';
import AEModal from "./AEModal";
export default class MAT extends Component {
constructor(props) {
super(props);
this.state = {
ae: !!meeting.settings.ae ? meeting.settings.ae : ""
};
}
getMeetingAEArray = () => {
let setting = this.state.settings();
return !!setting && !!setting.ae ? setting.ae : null;
}
getMeetingAE = () => {
return <AEModal
meeting={() => {
return this.state.meeting;
}}
AEArray={() => {
return getMeetingAEArray();
}}
/>
}
}
The child class
import React, { useState } from "react";
import i18n from 'Components/translations/i18n';
import {
Icon,
} from '#components';
function AEModal(props) {
const [form, setForm] = useState([]);
let meeting = props.meeting();
let ae = meeting.settings.ae; //ae is undefined
let aeArray = props.getAEArray();
}
To pass props coming from parent, you can do it as follows.
Following is an example. Please modify it for your implementation.
import React, { Component } from "react";
import AEModal from "./AEModal";
export default class MAT extends Component {
render() {
return (
<AEModal
meeting={this.props.meeting}
/>
);
};
}
Child component
import React, { useState } from "react";
function AEModal(props) {
console.log("meeting: ", props.meeting);
return <div>{props.meeting}</div>;
}
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 am getting the following errors when running my code and am not sure where to start with debugging this.
mapStateToProps() in Connect(EquipmentMetadata) must return a plain
object. Instead received undefined. Warning: Failed prop type: The
prop name is marked as required in EquipmentMetadata, but its value is
undefined.
This is the index file :
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
// import Root from './containers/Root'
import { setName } from './actions'
// import createStore from './common/store/'
import { Provider } from 'react-redux'
import EquipmentMetadata from './containers/EquipmentMetadata'
// import ReduxPiece from './components/ReduxPiece'
import configureStore from './configureStore'
const store = configureStore()
window.EquipmentMetadata = {
store,
setName
}
render(
<Provider store={store}>
{/* <Root /> */}
<EquipmentMetadata />
</Provider>,
document.getElementById('equipment-metadata')
)
equipmentmetadata.js:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Greeting from '../components/Greeting'
// import ReduxPiece from "../components/ReduxPiece";
class EquipmentMetadata extends Component {
render () {
// const { name } = this.props
return (
<div>
<Greeting
// name={name}
/>
{/* <ReduxPiece /> */}
</div>
)
}
}
EquipmentMetadata.propTypes = {
name: PropTypes.string.isRequired
}
function mapStateToProps (state) {
// const { name } = state
// return {
// name
// }
}
function mapDispatchToProps (dispatch) {
return {}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(EquipmentMetadata)
It is telling you that mapStateToProps should be returning something - you've commented all of it entirely. If you don't want it to return anything then just make it return {}. Also, in commenting it out, your EquipmentMetadata says in its propTypes that it requires a name prop, which you now aren't sending it.
mapStateToProps needs to be a function which returns an object. The notation of the mapStateToProps function can be as given below.
//ES5
function mapStateToProps (state) {
return {
key: name of the reducer function
}
}
//ES6
const mapStateToProps = (state) => ({
key: name of the reducer function
})
For your code you can try by making the changes like
function mapStateToProps (state) {
return {
name: state.nameOfReducer.valueinReducer
}
}
So i have written a provider to pass strings to any part of my application. The issue i have is that if i add a strings prop to my child component it overrides the entire prop and seems to ignore the merge i do in connector
Provider
import { Component, Children } from 'react'
import PropTypes from 'prop-types'
export default class StringsProvider extends Component {
static propTypes = {
children: PropTypes.element.isRequired,
strings: PropTypes.object
}
// Specify what is being added to context
static childContextTypes = {
strings: PropTypes.object.isRequired
}
getChildContext() {
const { strings } = this.props
return { strings: strings }
}
render() {
return Children.only(this.props.children)
}
}
Connector
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
// here we basically create a HOC that returns on Context (This is our component).
// The HOC adds the merged strings set to the Context Component and then renders it
export default (WrappedComp, defaultStrings, stringNameSpace) => {
return class StringsComponent extends Component {
static propTypes = {
strings: PropTypes.object.isRequired
}
static contextTypes = {
strings: PropTypes.object.isRequired
}
// Takes the defaultStrings from the context and merges them with the
// strings passed to the Context Component
getStrings = () => {
let out
if (this.props.strings) {
console.log('defaultStrings: ' + Object.values(defaultStrings)[0])
console.log(
'this.context.strings[stringNameSpace]: ' +
Object.values(this.context.strings[stringNameSpace])[0]
)
console.log(
'this.props.strings: ' + Object.values(this.props.strings)[0]
)
out = _.merge(
defaultStrings,
this.context.strings[stringNameSpace] ? this.context.strings[stringNameSpace] : {},
this.props.strings
)
} else {
out = _.merge(defaultStrings, this.context.strings[stringNameSpace])
}
console.warn('out')
console.log(out)
return out
}
render() {
return <WrappedComp strings={this.getStrings()} {...this.props} />
}
}
}
Component
import React, { Component } from 'react'
import { connectStrings } from './../../src'
import PropTypes from 'prop-types'
import defaultStrings from './strings'
class SampleComponent extends Component {
static propTypes = {
strings: PropTypes.object.isRequired
}
render() {
const { strings } = this.props
console.warn(strings)
return (
<div>
<span>{strings.str1}</span>
<span>{strings.str2}</span>
<span>{strings.str3}</span>
</div>
)
}
}
export default connectStrings(
SampleComponent,
defaultStrings,
'sampleComponent.ui.SET_TWO'
)
Unit test to run
import renderer from 'react-test-renderer'
import React from 'react'
import { StringProvider } from '../src'
import SampleComponent from "../components/sampleComponent";
describe('String Provider Tests', () => {
it('should render', () => {
const strings = {
'sampleComponent.ui.SET_ONE': {
str1: 'SET ONE'
},
'sampleComponent.ui.SET_TWO': {
str1: 'SP Str1',
str2: 'SP Str2'
}
}
const tree = renderer.create(
<StringProvider strings={strings}>
<SampleComponent strings={{str3: 'test'}}/>
</StringProvider>
)
expect(tree).toMatchSnapshot()
})
})
Im not sure what i am missing here, but strings seems to be updated in the connector but not in the component, is there something im not doing correctly?
How silly, ht answer was simple, below i just needed to change the order the props were added in the wrapped component
return <WrappedComp strings={this.getStrings()} {...this.props} />
It seems that as this.props also contained strings it was overwriting the result of the GetStrings() function.
I am trying to convert a non-TS React component to using Typescript.
In this component I am getting this error:
Class 'Component' defines instance member function
'componentWillMount', but extended class 'Root' defines it as instance
member property.
Here is my component:
import * as React from "react";
import { render } from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import createStore from "./state";
import { getCurrentUser } from "./util";
import { setValidUser } from "./state/actions/auth-actions";
const { store } = createStore();
class Root extends React.Component<any> {
componentWillMount = async () => {
const currentUser = await getCurrentUser();
if (currentUser) {
store.dispatch(setValidUser(currentUser));
}
}
render() {
return (
<Provider store={store}>
<Router>
{this.props.children}
</Router>
</Provider>
);
}
}
export default (Component, props = {}) => {
render(
<Root>
<Component {...props} />
</Root>,
document.getElementById("root"),
);
};
I am not a pro at TS yet, obvioulsy, and not sure how to resolve this. thanks!
Class 'Component' defines instance member function 'componentWillMount', but extended class 'Root' defines it as instance member property.
Change your property:
componentWillMount = async () => {
to a member function:
async componentWillMount() {
These names come from OO concepts
More
I would even:
componentWillMount() {
this.asyncMount();
}
asyncMount = async () => {
const currentUser = await getCurrentUser();
if (currentUser) {
store.dispatch(setValidUser(currentUser));
}
}
As React will not wait for your componentWillMount to wait and its async nature might confuse your fellow developers.