I have amazingly simple and terrible problem.
Let's say, I have a React component (called List) wrapped by createContainer:
class List extends Component {
render() {
return (
...
);
}
}
export default createContainer({
...
}, List);
List has one prop from parent: activeListId.
I use createContainer to subscribe for subscription, but i need to pass a parameter inside that subscription. The parameter is activeListId value.
export default createContainer({
Meteor.subscribe('ListItems', this.props.activeListId);
return {
...
}
}, List);
So I need to have an access to props of the original component inside the createContainer code. It is so strange but I can not do this! this context inside createContainer is different from this inside List.
Who knows how to achieve that?
As its first argument, createContainer should receive a function that takes props as an argument. So you need to do this:
export default createContainer(props => {
Meteor.subscribe('ListItems', props.activeListId);
return {
...
}
}, List);
Related
The Redux library easy-peasy has a function called computed which is an alternative to standard Redux selectors:
import { computed } from 'easy-peasy'
const model = {
session: {
totalPrice: computed(state => state.price + state.tax)
}
}
And then the selector is called in the component like this:
import { useStoreState } from 'easy-peasy'
function TotalPriceOfProducts() {
const totalPrice = useStoreState(state => state.products.totalPrice)
return <div>Total: {totalPrice}</div>
}
The problem is that there doesn't seem to be a way to pass inputs to the selector. If I need a specific object in an array in the state, I can't pass the ID of the object as an input. My only option is to do this on the component side. Redux selectors have the advantage of being functions, so I can pass inputs to be used in the selector logic.
Anyone use easy-peasy come across this problem before?
I can't pass the ID of the object as an input
To enable this, you have to return a function as your computed property.
Here is an example, exposing a computed function getTodoById:
import { computed } from 'easy-peasy'
const model = {
todos: [],
getTodoById: computed(state => {
// Note how we are returning a function instead of state
// 👇
return (id) => state.todos.find(t => t.id === id)
})
}
Then you can use it like this:
import { useStoreState } from 'easy-peasy'
function Todo({ id }) {
const getTodoById = useStoreState(state => state.getTodoById)
const matchingTodo = getTodoById(id);
// ... etc
}
When I am debugging using the browser console console or react dev tools, they always refer to my components as "Constructor" and I would like to change that. See the example below:
I would have hoped to set defined names for my components so they would show up as "MyComponent" for example. This would help on pages where there are many components and one of them is throwing a warning that I would like to solve.
Add the displayName property to your components:
var Component = React.createClass({
displayName: 'MyComponent',
...
});
You don't need to set the displayName property to your components actually. It is automatically set.
But there are certain cases you need to consider.
1.You put your component in a separate file and content of that file is -
export default React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
In this case displayName will be undefined.
2.You assigned the component in a variable.
var TestComponent = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
Now displayName is set to TestComponent.
See corresponding jsx conversion for more clarification.
var TestComponent = React.createClass({
displayName: "TestComponent",
render: function render() {
return React.createElement(
"h1",
null,
"Hello, ",
this.props.name
);
}
});
3.If you are using es6 e.g
class HelloMessage extends React.Component {
render() {
return <div > Hello component {
} < /div>;
}
}
In this case displayName will be name of class you have given i.e HelloMessage.
Problem: IDE does not resolve props passed to the component via connect()
Note: this is not a bug, but an inconvenience to the coder
Say I have this React component connected to Redux via connect():
class SomeComponent extends Component {
render() {
return (
{this.props.someObject ? this.props.someObject : ''}
);
}
}
function mapStateToProps(state) {
return {
someObject: new SomeObject(state.someReducer.someObjectInfo),
};
}
function mapDispatchToProps(dispatch) {
return {
// ...
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatsList);
I'm using the IntelliJ IDE, and any prop connected to the component in the above manner, such as someObject, will get an unresolved variable warning. And if someObject has some properties/methods, they will neither be resolved nor show up in code suggestions (which are really helpful).
A workaround
Pass state and dispatch themselves as props:
function mapStateToProps(state) {return {state};}
function mapDispatchToProps(dispatch) {return {dispatch};}
Define my variables in the constructor (as opposed to via props):
constructor(props) {
this.someVar = props.state.someReducer.someVar;
this.someObj = new SomeObject(props.state.someReducer.someObjectInfo;
}
Update the variables manually whenever props change:
componentWillReceiveProps(nextProps) {
someObject.update(nextProps.state.someReducer.someObjectInfo);
}
The drawback is having additional boilerplate logic in componentWillReceiveProps, but now the IDE happily resolves the variables and code suggestion works.
Question
Is the workaround preferable? I'm using it, like it so far, and have not observed any other drawbacks thus far. Is there a better way to get the IDE to understand the code?
Motivation (verbose; only for those interested in why I want to accomplish the above)
The Redux tutorials show a simple way to connect state/dispatch to props, e.g.:
function mapStateToProps(state) {
users: state.usersReducer.users
chats: state.chatsReducer.chats
}
function mapDispatchToProps(dispatch) {
addUser: (id) => dispatch(usersActions.addUser(id))
addChatMsg: (id, msg) => dispatch(chatsActions.addChatMsg(id, msg)
}
In the example above, the coder of a component will need to know every relevant reducers' names and their state variables. This can get messy for the coder. Instead, I want to abstract these details away from the component. One way is with a "module" class that accepts state and dispatch, and provides all get/set methods:
class Chats {
// Actions
static ADD_MESSAGE = "CHATS/ADD_MESSAGE";
constructor(globalState, dispatch) {
this.chatsState = globalState.chats;
this.dispatch = dispatch;
}
// Get method
getChats() {
return this.chatsState.chats;
}
// Set method
addChatMessage(id, msg) {
return this.dispatch({
type: Chats.ADD_MESSAGE,
id,
msg
};
}
// Called by componentWillReceiveProps to update this object
updateChats(nextGlobalState) {
this.chatsState = nextGlobalState.chats;
}
}
Now, if a Component requires the Chats module, a coder simply does this:
class SomeComponent extends Component {
constructor(props) {
this.chats = new Chats(props.state, props.dispatch);
}
componentWillReceiveProps(nextProps) {
this.chats.updateChats(nextProps);
}
// ...
}
And now, all Chats get/set methods and properties will be available, and will be picked up by the IDE.
I think newest Idea can now understand component properties defined via propTypes and provides code completion for them. So you just declare propTypes. And it is not even a workaround, it's a good practice in my opinion.
class ChatsList extends Component {
static propTypes = {
someObject: PropTypes.shape({
color: PropTypes.string,
someFunc: PropTypes.func
}),
someDispatcher: PropTypes.func
};
render() {
return (
{this.props.someObject ? this.props.someObject : ''}
);
}
}
function mapStateToProps(state) {
return {
someObject: new SomeObject(state.someReducer.someObjectInfo),
};
}
function mapDispatchToProps(dispatch) {
return {
someDispatcher: Actions.someDispatcher
// ...
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatsList);
Also, passing the entire state is a bad idea, since a component will receive props and get re-renderend if anything changes in the entire state (unless you provide shouldComponentUpdate)
I've got a component that builds search/sort filters that can be selected. I want the selected state of those filters to be tracked in redux so that the search builder can subscribe and see when they change and update appropriately. the thing I'm trying to figure out how to do (in a way that doesn't feel weird) is populate the filter objects into the state. Eg, right now in the <Search /> component I have something like:
<OptionPicker
group={'searchFilters'}
options={{word: 'price', active: true},
{word: 'distance', active: false},
{word: 'clowns', active: false}}
/>
So how to get those props into state to be used without triggering multiple element renders. I'm also rendering the app on the server as well, so for the initial attachment render, the state already has the options.
In the OptionPicker component I've got:
class OptionPicker extends Component {
constructor(props) {
super(props)
if (!props.optionstate) {
this.props.addOptionState(props)
}
}
render() {
return {this.props.optionstate.word.map((word) => <Option ... />)}
}
}
function mapStateToProps(state, props) {
return {
optionstate: state.optionstate[props.group],
};
}
function mapDispatchToProps(dispatch) {
return {
addOptionState: (props) => {
dispatch(addOptionState(props));
},
optionToggled: (group, word) => {
dispatch(updateOptionState(group, word));
}
};
}
export default connect(mapStateToProps,mapDispatchToProps)(OptionGroup);
This kinda works, but there exists a time when render is called before the redux state has been populated, which throws an error. I could guard against that, but none of this feels "right". Shouldn't that prop always be there? Is there a pattern for this that I'm missing?
I agree with you in that the prop should always be there. The pattern I use for this is to set up an initial state and to pass it to the reducer function:
export const INITIAL_STATE = {
optionstate: { /* all filters are present but deactivated */ }
};
export default function (state = INITIAL_STATE, action) {
// reduce new actions into the state
};
I can change the states and update the UI by using Redux. But how to show injected props by Redux on console by using console like console.log(this.props) in run-time. I cannot. I've never seen the props.
Is there a way to show component (class) props -that are assigned from Redux store like the code below-?
function mapStateToProps(state) {
return { iconSize: state.iconSize }
}
function mapDispatchToProps(dispatch) {
return {
setIconSize: (size) => dispatch(setIconSize(size))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Main)
In this example you can rewrite mapStateToProps function:
function mapStateToProps(state) {
const props = { iconSize: state.iconSize };
console.log(props);
return props;
}
It will log props each time this function is invoked.