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.
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'm very new to React testing and I'm trying to write Jest/Enzyme Unit tests for the MyReload wrapper component on top of Apollo Query that I have created. This basically adds refetchers to an array (that can be executed at later point of time).
Usage:
<MyReloadQuery id='1234 .....> // do something.. children </MyReloadQuery>
Component:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Subscribe } from "unstated";
import { Query } from "react-apollo";
import { MyReloadContainer } from "./MyReloadContainer";
export class MyReloadQuery extends Component {
static propTypes = {
children: PropTypes.any
};
componentWillUnmount() {
this.onUnmount();
}
render() {
return (
<Subscribe to={[MyReloadContainer]}>
{refetchers => (
<Query {...this.props}>
{(...args) => {
const { refetch } = args[0];
this.onUnmount = () => {
refetchers.removeRefetcher(refetch);
};
refetchers.addRefetcher(refetch);
return this.props.children(...args);
}}
</Query>
)}
</Subscribe>
);
}
}
I'm very new to React and it's testing and I'm wondering how can one test with dummy data for this one?
Can someone please enlighten me? Any help would be appreciated :pray:
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.
I have a container which has access to the Redux state and maps a certain pieces of that state to its props, the piece I'm interested in is 'coins' so
this.props.coins
Using console.log() I can see that when rendering the container I have access to that piece of state.
This container houses one component that receives a callback function and this previously mentioned piece of state, coins, via its own props in
onClickHandler() && coinData
The container has a function to handle the callback called
_handleClick()
In this function for some reason this.props doesn't give access to the same coins I was able to access in the render function but has access to the props that I'm passing to the component, props I thought would belong to the component. So from _handleClick() in the container I have access to
this.props.onClickHandler() && this.props.coinData
but not
this.props.coins
which is what I was expecting to have access to from anywhere within my container. I get the error in the title of the post when I try to access this.props.coins in my callback handler _handleClick() but why is that and why do I have access to the props that should belong to the corresponding child component? Is there any way that I can access this.props.coins from the _handleClick() call?
My container:
import React, { Component } from 'react';
import '../App.css';
import { connect } from "react-redux";
import ChartSelectMenu from '../components/ChartSelectMenu';
import { selectCoinForChart } from '../actions/index';
class ChartSelectMenuContainer extends Component {
constructor(props) {
super(props);
}
_handleClick(ticker) {
//event.preventDefault();
console.log("Clicked: ", ticker);
console.log("coin_list: ", this.props);
const coin_list = this.props.coins;
//
for (let i=0; i<coin_list.length; i++) {
if (coin_list[i].ticker === ticker) {
this.props.dispatch(selectCoinForChart(ticker));
}
}
}
render() {
console.log("Container props on render(): ", this.props);
return (
<ChartSelectMenu
onClickHandler={this._handleClick}
coinData={this.props.coins}
/>
);
}
}
function mapStateToProps({ auth, coins, selectedCoin }) {
return { auth, coins, selectedCoin };
}
export default connect(mapStateToProps)(ChartSelectMenuContainer);
It's component:
import React, {Component} from 'react';
import Paper from 'material-ui/Paper';
import Menu from 'material-ui/Menu';
import MenuItem from 'material-ui/MenuItem';
const style = {
display: 'inline-block',
margin: '16px 0px 16px 0px',
};
class ChartSelectMenu extends Component {
constructor(props) {
super(props);
}
_renderMenuItems() {
if (this.props === null) {
return <h3>Loading...</h3>;
} else {
return(this.props.coinData.map((coin) => {
return <MenuItem
key={coin.ticker}
onClick={this.props.onClickHandler.bind(this, coin.ticker)}
primaryText={coin.ticker}
/>
}));
}
}
render() {
//console.log('ChartSelectMenu props: ', this.props);
return (
<div>
<Paper style={style}>
<Menu>
<ul>
{this._renderMenuItems()}
</ul>
</Menu>
</Paper>
</div>
)
}
}
export default ChartSelectMenu;
When #MukulSharma commented 'use var self = this inside your function' I thought to try to bind the _handleClick function in the constructor of the container and it worked.
The solution:
import React, { Component } from 'react';
import '../App.css';
import { connect } from "react-redux";
import ChartSelectMenu from '../components/ChartSelectMenu';
import { selectCoinForChart } from '../actions/index';
class ChartSelectMenuContainer extends Component {
constructor(props) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
_handleClick(ticker) {
//event.preventDefault();
console.log("Clicked: ", ticker);
console.log("coin_list: ", this.props);
const coin_list = this.props.coins;
//
for (let i=0; i<coin_list.length; i++) {
if (coin_list[i].ticker === ticker) {
this.props.dispatch(selectCoinForChart(ticker));
}
}
}
render() { ...
I have the following parent component:
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import _ from "lodash";
import ChildComponent from "./ChildComponent";
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
I'm at Parent
<ChildComponent/>
</div>
);
}
}
function mapStateToProps(state) {
return { }
}
export default connect(mapStateToProps, null)(ParentComponent);
Inside the parent has a component called ChildComponent that looks like this:
import React, { Component, PropTypes } from "react";
import { connect } from "react-redux";
import { reduxForm } from "redux-form";
import { bindActionCreators } from "redux";
class ChildComponent extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
}
render() {
return (
<div>
at the child
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
},
dispatch
);
}
function mapStateToProps(state) {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(
ChildComponent
);
When I try adding the child component I keep getting this error:
But if I click continue the page turns back to normal. I don't understand how the child component is undefined. It's just embedded and does not include any props.
UPDATE:
I'm not getting the error anymore but I notice my page turns blank when I open this particular component. I'll be doing a bit more troubleshooting.
I tried out your code, and it works fine for me. My thought was maybe what your entry file looks like? or file structure? If you like you can try the following syntax for the parent and child - it worked this way as well:
Child:
const mapStateToProps = () => {
return {}
}
const ConnectedChildComponent = connect(
mapStateToProps,
{})(ChildComponent)
export default ConnectedChildComponent;
Parent:
const mapStateToProps = () => {
return {}
}
const ConnectedParentComponent = connect(
mapStateToProps,
{})(ParentComponent)
export default ConnectedParentComponent;
In your ParentComponent change:
import ChildComponent from "./ChildComponent";
to
import ChildComponent from "./ChildComponent.jsx";
i.e. add the missing ".jsx" extension. Your code is most likely determining the import to be a ".js" file by default, whereas it's actually a ".jsx" file.