So i'm trying to write a test to my component while using redux-form and enzyme.
So far, for my simple components i've created the following function
export const mountWithContext = (node, store) => {
const nodeToMount = store ? <Provider store={store} >{node}</Provider> : node;
return mount(nodeToMount);
};
The issue is that when i'm trying to use react-redux as well it fails.
Trying to do:
class Form extends Component {
render() {
return <div><TestComponent /></div>
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form);
const enzymeWrapper = mountWithContext(TestForm, store)
And i'm getting the following error
'Warning: Failed prop type: Invalid prop `children` of type `function` supplied to `Provider`, expected a single ReactElement.
Invariant Violation: React.Children.only expected to receive a single React element child.
Any idea why this happens and how to solve this?
This question is 6 months old. For others who run into similar error messages:
The rendering of <Provider>{ node }</Provider> should actually be <Provider><Node /></Provider> or something similar. The error is basically saying that it requires an element and the children used as { node } instead of <Node/> is a function.
export const mountWithContext = (Node, store) => {
const nodeToMount = store ? (
<Provider store={store} >
<Node />
</Provider>
) : <Node />;
return mount(nodeToMount);
};
So just change the argument name of node to Node and usage in JSX from {node} to <Node /> or <Node></Node>
Related
I'm having some trouble migrating one thing from the old addon-knobs to the new controls. Let me explain, maybe it's not such difficult task but I'm blocked at the moment.
I'm using StencilJS to generate Web Components and I have a custom select component that accepts a options prop, this is an array of objects (the options of the select)
So, the story for this component in the previous version of Storybook looks something like this:
export const SelectWithArray = () => {
const selectElement = document.createElement('my-select');
selectElement.name = name;
selectElement.options = object('Options', options);
selectElement.disabled = boolean('Disabled', false);
selectElement.label = text('Label', 'Label');
return selectElement;
};
This works fine, the select component receives the options property correctly as an array of objects.
Now, migrating this to the new Storybook version without addon-knobs, the story is looking like this:
const TemplateWithArray: Story<ISelect> = (args) => {
return `
<my-select
label="${args.label}"
disabled="${args.disabled}"
options="${args.options}"
>
</my-select>
`;
};
export const SelectWithArray: Story<ISelect> = TemplateWithArray.bind({});
SelectWithArray.argTypes = {
options: {
name: 'Options',
control: { type: 'object' },
}
}
SelectWithArray.args = {
options: [
{ text: 'Option 1', value: 1 },
]
}
And with this new method, the component is not able to receive the property as expected.
I believe the problem is that now, the arguments is being set directly on the HTML (which would only be accepting strings) and before it was being set on the JS part, so you could set attributes other than strings.
Is there a way to achieve this? without having to send the arguments as a string.
Thanks a lot!!
One way I've discovered so far is to bind the object after the canvas has loaded via the .play function;
codeFullArgs.play = async () => {
const component = document.getElementsByTagName('your-components-tag')[0];
component.jobData = FullArgs.args.jobData;
}
I am testing PermissionDetail component which has graphql fragment, that is the data of node of PermissionTable component. I am getting a flow type error in this line when getting mock data from query const permissionDetail = data.viewPermissionScheme?.grantGroups[0].grantHolders?.edges[0].node.permission;.
Component hierarchy:
App -> PermissionTable (Paginated component fragment) -> PermissionDetail (fragment)
const TestRenderer = () => {
const data = useLazyLoadQuery<examplesPermissionQuery>(
graphql`
query examplesPermissionQuery #relay_test_operation {
viewPermission(id: "test-scheme-id") {
... on PermissionView {
groups {
holders(first: 10) {
edges {
node {
permission {
...permissionDetailsFragment
}
}
}
}
}
}
}
}
`,
{},
);
// Getting Flowtype Error here: Cannot get `data.viewPermission?.groups[0]` because an index signature declaring the expected key / value type is missing in null or undefined [1]
const permissionDetail =
data.viewPermissionScheme?.grantGroups[0].grantHolders?.edges[0].node.permission;
return permissionDetail ? (<PermissionDetails permissionDetail={permissionDetail}/>) : null;
};
What is the correct way to test such components? I am new to flow and graphql and relay. So need to understand the best way to test this.
I think the error is simply that data.viewPermission?.groups can be null or undefined. Therefore you are not allowed to access an(y) index on this property. One way to fix this is by using data.viewPermission?.groups?[0] to access the property.
You could also make groups non-nullable in your GraphQL schema. Some people like a lot of nullable fields because that allows the server to return as much partial data as possible in the case of an error. But for the developer this means that every field has to be checked for null.
I'm migrating from a traditional React application with the tree-structure to a state management structure with MobX.
Currently, my individual components propagate down data from an Ajax call made by the parent/grandparent, which works fine.
Now I wish to change this so that I don't work in the tree structure anymore due to the change of complexity and dependency of parallel grandchildren/children.
Say I do an axios.get in componentDidMount of a <Parent1/> React class. How do I then access the data using MobX?
What I've tried so far:
Creating a store.jsx that looks as such:
import { observable } from 'mobx';
const axios = require('axios');
class Store {
#observable parentdata;
loadParent = () => {
let that = this;
axios.get("/api/parent").then(function(response){
that.parentdata = response.parentdata;
}).catch(function(error){
// Error handling
})
};
}
export default Store;
My ReactDOM is rendered in container.jsx which contains all parents, meaning also <Parent1/>. In container.jsx we do the following:
import { Provider } from 'mobx-react';
import Store from './store/store.jsx';
let store = new Store();
and
ReactDOM.render(
<Provider store={store}>
<Main />
</Provider>,
document.getElementById('content')
);
.. in the end.
In the render method of container.jsx I don't do anything with <Parent1/> - it simply renders the component as normal(that's the idea here, right?)
In parent1.jsx, I remove the previous axios.get and add these:
import { inject, observer } from 'mobx-react';
#inject('store') #observer
export default class Parent1 extends React.Component {
// .....
componentDidMount () {
this.props.store.loadParent();
After that, the error is provided: MobX observer: Store 'parentdata' is not available! Make sure it is provided by some Provider
Where did I go wrong in binding the data here?
Edit: Removing #inject and only having #observer results in: TypeError: Cannot read property 'loadParent' of undefined
Few things to address:
Assuming you named your parentData store 'store' and not 'Store' like the classname, the Provider looks like it's configured correctly
Here's how I'd change the store itself:
import { observable } from 'mobx';
const axios = require('axios');
class Store {
#observable parentdata;
loadParent = () => {
// There's no need to clone this if you're using arrow functions
axios.get("/api/parent").then((response) => {
this.parentdata = response.parentdata;
}).catch(function(error){
// Error handling
})
};
}
export default Store;
In the component, I might do this:
import { inject, observer } from 'mobx-react';
#inject('store') #observer
export default class Parent1 extends React.Component {
// ... //
componentDidMount () {
this.props.store.loadParent();
}
render() {
if (!this.props.store.parentData) { return (<div>Loading...</div>) }
const { parentData } = this.props.store
return (<pre>{JSON.stringify(parentData, null, 4)}</pre>)
}
}
My suspicion is that your store is case sensitive and mistakenly named.
A nifty tricks to debugging could be to console.log important variables or attach them to a window object to test.
eg:
class Parent1 {
componentDidMount() {
window.Parent1 = this
window.Parent1Props = this.props
window.Store = this.props.store // or `this.props.Store` check case sensitivity!
}
// Open browser console and try to access your now global variables
I'm having some trouble making use of the Config.skip property inside of my graphql() wrapper.
The intent is for the query to be fired with an argument of currentGoalID, only after a user has selected an item from the drop-down (passing the associated currentGoalID) , and the (Redux) state has been updated with a value for currentGoalID.
Otherwise, I expect (as per Apollo documentation) that:
... your child component doesn’t get a data prop at all, and the options or props methods are not called.
In this case though, it seems that my skip property is being ignored based upon the absence of a value for currentGoalID, and the option is being called because the webpack compiler/linter throws on line 51, props is not defined...
I successfully console.log the value of currentGoalID without the graphql()
wrapper. Any idea why config.skip isn't working? Also wish to be advised on the proper use of this in graphql() function call. I've excluded it here, but am unsure of the context, thanks.
class CurrentGoal extends Component {
constructor(props) {
super(props)
}
render (){
console.log(this.props.currentGoalID);
return( <p>Current Goal: {null}</p>
)
}
}
const mapStateToProps = (state, props) => {
return {
currentGoal: state.goals.currentGoal,
currentGoalID: state.goals.currentGoalID,
currentGoalSteps: state.goals.currentGoalSteps
}
}
const FetchGoalDocByID = gql `
query root($varID:String) {
goalDocsByID(id:$varID) {
goal
}
}`;
const CurrentGoalWithState = connect(mapStateToProps)(CurrentGoal);
const CurrentGoalWithData = graphql(FetchGoalDocByID, {
skip: (props) => !props.currentGoalID,
options: {variables: {varID: props.currentGoalID}}
})(CurrentGoalWithState);
// export default CurrentGoalWithState
export default CurrentGoalWithData
See the answer here: https://stackoverflow.com/a/47943253/763231
connect must be the last decorator executed, after graphql, in order for graphql to include the props from Redux.
I'm trying to make a reusable form component group (section?) for use with Redux-Form v6. Ideally I don't want it to be aware of where it is used within a form, so that I can use it anywhere within a form (at the root or nested). How can I do this?
For example, I have an Address component that uses Redux-Form's Field for address1, city, state, zip, etc, like this:
class Address extends React.Component {
render() {
return (
<Field
component={MyReduxFormInput}
name="address1" // <== How to name for generic case?
/>
)
}
}
Here's a simplified MyReduxFormInput:
module.exports = field => {
return <input {...field.input} />
}
I'm creating a form where I need to collect the user's address, as well as multiple addresses from some professional references. So, I want to use Address once at the root and multiple times in a nested fashion. The trick is, I can't simply use Address as I've written it above, because while that will work for the root address, it won't work for nested situations. As shown in the FieldArray example docs, the name supplied to Field needs to be name={`${member}.address1`}. But then that wouldn't work at the root.
One idea which just occurred to me is for the Address component to take a member argument, which would be blank at the root. I'll try that and report back. But I was thinking Redux-Form Field components would be automatically aware of their nesting level; they should look up their hierarchy and be able to know their necessary name prefix.
My idea of my component taking a member prop (as in, a name prefix depending on the nesting) works great. Wish I would've thought of it many hours ago.
Here's what Address looks like:
class Address extends React.Component {
static propTypes = {
member: PropTypes.string
}
static defaultProps = {
member: ''
}
render() {
const { member } = this.props
const name = n => `${member}${n}`
return (
<Field
component={MyReduxFormInput}
name={name('address1')}
/>
)
}
}
And here's how it might be used:
const references = ({ fields }) => {
return (
<div>
fields.map((member, index) => {
return <Address member={member} key={index} />
})
</div>
)
}
<Address />
<FieldArray name="professionalReferences" component={references} />