Comments (5)
Hey @jononu, when you say
if the shape generator took the prop name or ideally a path
could you give an example of what this might look like? Does "path" refer to a file path, or an object-tree path?
I don't ๐ฏ grok the question, so just trying to get a bit more detail on what you're thinking in terms of inputs and expected outputs.
That said, the intent of this library is to generate values based on PropTypes, so in the context of components themselves. It's not so much interested in generating state, but rather props. Often times the two will share similar or identical shapes, but not necessarily.
Any additional info you can provide will help me determine if it's something this lib can/should do, or maybe suggest alternatives.
Thanks!
from react-generate-props.
I'd like to be able to return something for a particular prop, not all props of a particular type. To do that, I was thinking it could match on the object tree path and return a particular result based on that path.
This would be used in our tests, so we don't have to duplicate all the props when testing the component. We could generate the props in the test and simply change the particular values the test is interested in.
someReducer
export initialState = {
foo: 'bar',
asdf: 'jklm'
};
... reducer-y things
In our tests
import { initialState } from './someReducer';
props = generateProps(propTypes, {
generators: {
shape: (shape, path) => {
if (path === 'store.someReducer`) {
return initialState;
}
return generateProps(shape, path);
}
}
})
props.store.someReducer.foo = 'baz'
const wrapper = shallow(<TestedComponent {...props} />);
... describe / expect-y things
I think ideally we'd make it smart enough to find the correct initialState automatically based on the object tree path.
from react-generate-props.
That makes sense, thank you @jononu. The only issue I think I see here is that it's based on the assumption that a PropType.shape will share the same shape as a reducer's โย which might not always, and in many cases will never, be the case. It essentially assumes that there are no complex "selectors" between the state and the component.
For instance, it'd be reasonable for your state to look like this
state = {
posts: [
{ id: 1, title: 'A Post' },
{ id: 2, title: 'Another Post' }
],
comments: [
{ id: 1, postId: 1, text: 'Great post.' },
{ id: 2, postId: 1, text: 'Agreed.' },
{ id: 3, postId: 2, text: 'Another great post.' }
]
}
But for your component to have PropTypes like this:
Post.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
comments: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
text: PropTypes.string.isRequired
}).isRequired
)
}
So the shape of your state looks more like a relational DB, but the shape of your Post
component's props is more streamlined to simplify rendering. Depending on how you select a post and its comments, your selector might need to be something complex like
mapStateToProps = (state, ownProps) => {
const currentPost = _.find(state.posts, { id: ownProps.currentPostId })
const currentComments = _.filter(state.comments, { postId: ownProps.currentPostId })
return { ...currentPost, comments: currentComments }
}
I think it'd be difficult to try to derive the correct state to generate in the shape
generator given a situation like this. This is all just a long-winded way of saying that this feels like it might be outside the scope of this library's duties since it feels tied to a particular state/component relationship that some apps may not have.
However, if you do have a component whose state matches some part of your app's state tree, I think the simplest solution would be to (1) generate your initial state, (2) generate default props for your component, and then (3) merge the two objects. The simplest way would be using something like lodash's merge.
I just added this test scenario to ensure this would produce the expected results:
// The component takes props A through E.
// Each one is a shape containing a String in "..._a", and a Shape in "..._b".
// The Shape in "..._b" contains a String in its own "..._a".
const propTypes = {
a: PropTypes.shape({ a_a: PropTypes.string.isRequired, a_b: PropTypes.shape({ a_b_a: PropTypes.string.isRequired }).isRequired }).isRequired,
b: PropTypes.shape({ b_a: PropTypes.string.isRequired, b_b: PropTypes.shape({ b_b_a: PropTypes.string.isRequired }).isRequired }).isRequired,
c: PropTypes.shape({ c_a: PropTypes.string.isRequired, c_b: PropTypes.shape({ c_b_a: PropTypes.string.isRequired }).isRequired }).isRequired,
d: PropTypes.shape({ d_a: PropTypes.string.isRequired, d_b: PropTypes.shape({ d_b_a: PropTypes.string.isRequired }).isRequired }).isRequired,
e: PropTypes.shape({ e_a: PropTypes.string.isRequired, e_b: PropTypes.shape({ e_b_a: PropTypes.string.isRequired }).isRequired }).isRequired,
}
// Initial state has all potential shapes of this prop type definition,
// from fully-defined to undefined.
const initialState = {
a: { a_a: 'initialState', a_b: { a_b_a: 'initialState' } },
b: { b_a: 'initialState', b_b: {} },
c: { c_a: 'initialState' },
d: {}
}
// Where a value was defined in initialState, it should be used.
// Where one didn't exist, the generateProps value should be used instead.
const expected = {
a: { a_a: 'initialState', a_b: { a_b_a: 'initialState' } },
b: { b_a: 'initialState', b_b: { b_b_a: 'string' } },
c: { c_a: 'initialState', c_b: { c_b_a: 'string' } },
d: { d_a: 'string', d_b: { d_b_a: 'string' } },
e: { e_a: 'string', e_b: { e_b_a: 'string' } },
}
const props = generateProps(propTypes)
const merged = _.merge(props, initialState)
merged.should.deep.equal(expected)
โ
Do you think you could adapt this technique to your needs?
Thanks for submitting this issue, and let me know if I've misunderstood or if this doesn't cover your usecase!
Full disclosure, I actually spent some time trying to create a recursive custom shape generator with initialState and generateProps fallbacks as described. It turned out to be unnecessarily complex and maybe impossible given current constraints. I'm leaving my attempts below for posterity.
Click to expand
I believe that the shape
generator's override should give you the ability to generate a shape based on the shape of your state, and catered to your own app.
import generateProps from 'react-generate-props'
import { initialState } from './someReducer'
// initialState === { foo: 'bar', asdf: 'jklm' }
import Component from './someComponent'
// Component.propTypes === { foo: PropTypes.string.isRequired }
// For each key in the propTypes definition, pull the value from initialState:
const simpleShapeGenerator = shape => _.mapValues(shape, (v, k) => initialState[k])
const props = generateProps(Component, { generators: { shape: simpleShapeGenerator } })
// props === { foo: 'bar' }
That all works to pull in some initialState
via the generator override, but it's not doing anything particularly useful โย you could have just passed initialState
directly to Component
and gotten the same general results. The usefulness seems to come in when we can use the standard generators for fallbacks...
// For each key in the propTypes definition, pull the value from initialState,
// but if a value doesn't exist there, generate a default one:
const complexShapeGenerator = shape =>
_.mapValues(shape, (v, k) => initialState[k] || generateProps({ [k]: v })[k])
Component.propTypes = {
foo: PropTypes.string.isRequired,
someNewKey: PropTypes.string.isRequired
}
// initialState === { foo: 'bar', asdf: 'jklm' }
const props = generateProps(Component, { generators: { shape: complexShapeGenerator } })
// props === { foo: 'bar', someNewKey: 'string' }
The generateProps({ [k]: v })[k]
syntax is a little weird, and it's only necessary because generateProps
refuses to take an argument that isn't an object right now. So you can't just call generateProps(PropTypes.string.isRequired)
and get back 'string'
. That's something that I'd like to change (and I just created a ticket for it: #18).
But... this works. However, it doesn't recursively call generateProps
for any values missing in initialState
.
we-need-to-go-deeper.jpg
const recursiveShapeGenerator = shape =>
_.mapValues(shape, (v, k) =>
initialState[k] ||
generateProps({ [k]: v }, { generators: { shape: recursiveShapeGenerator } })[k]
)
This works in this case:
pts = {
a: PropTypes.shape({
b: PropTypes.shape({
c: PropTypes.shape({
d: PropTypes.shape({
e: PropTypes.string.isRequired
}).isRequired
}).isRequired
}).isRequired
}).isRequired
}
initialState = { a: undefined }
but breaks here...
initialState = { a: {} }
Ok... rethinking...
from react-generate-props.
Apologies for the slow reply.
We're pretty selective (hah!) on our use of selectors. Most of our store state is kept simple and used directly by components.
The _.merge
approach or something like it would probably work for me. I want to build something framework-y that doesn't require developers to write their own code to merge initialState with the generated props. I could probably use this library to generate an initial set of props, then inspect those props to find known properties that could be replaced with various reducer initialState objects.
Something like
const propTypes = {
foo: PropTypes.string.isRequired,
store: PropTypes.shape({
reducer1: PropTypes.shape({
bar: PropTypes.string.isRequired
}),
reducer2: PropTypes.shape({
baz: PropTypes.string.isRequired,
})
}).isRequired
}
const props = generateProps(propTypes)
if (props.store) {
Object.keys(props.store).forEach((propName) => {
props.store[propName] = reducers[propName].initialState; // assume reducers is an object of all the reducers
});
}
I'm good with that approach if you think this feature is out of scope for this project. Knowing what prop you're generating in a custom generator seemed more generally useful.
Thanks for going deep and prototyping a solution!
from react-generate-props.
@jononu just a note: since you're using Redux and its combine reducers functionality, you can generate an entire state tree made up of all reducers' initial states by simply calling your top-level reducer. I've done this as a way to programmatically test every selector throughout an entire application, and ensure that our initial states are actually valid.
So rather than hooking directly to reducers[name].initialState
, you could generate your entire application's state tree like...
import reducers from 'reducers/index.js'
const fullInitialState = reducers({}, {})
At that point, fullInitialState
will contain the entire state tree for your application, but where each branch of that tree is made up of its reducer's initial state. Rather than reducers[propName].initialState
you should be able to access the same values at fullInitialState[propName]
, and you won't need to worry about exporting initialState
from all your reducers and assembling it into that reducers
object.
Let me know if any of this doesn't suit your needs, I'm happy to try to find another solution if we think one deserves to make its way into this lib, or if it feels like there's an extension to this lib that might be useful!
from react-generate-props.
Related Issues (18)
- Handle defaultProps
- Support React 15.5 and above
- Allow for deterministic tests
- { optional: true } doesn't generate deeply-nested optional props in shapes HOT 1
- generateProps should be able to take a PropType as its argument
- Wrapping of complex types seem to be broken HOT 9
- Generate reasonable values for all simple types
- Return more information in generator callbacks HOT 2
- High order component HOT 2
- Generators could receive prop name as argument HOT 6
- Add PropType.symbol support HOT 1
- Allow for Generation of Custom Prop Types HOT 3
- Generate reasonable values for all complex types
- Generate PropTypes.exact
- Maybe generate values for non-required props
- Take generator overrides
- Support older node versions
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ๐๐๐
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-generate-props.