How to Call selector function from React Component? - reactjs

This is my Selector , I can able to get data with in the selector but don't know how to call this into view (Component) ,
import {todos} from '../reducers/todos';
import { createSelector } from 'reselect'
var visibilityFilter='SHOW_ALL';
var getVisibilityFilter = (state) => visibilityFilter;
var getTodos = (state) => todos;
export const getVisibleTodos = createSelector(
[ getVisibilityFilter, getTodos ],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
)
export default getVisibleTodos;
I have Tried in Component
<button onClick={()=>props.getVisibleTodos(props.SHOW_ALL , props.experimentData.lights)}> SHOW_COMPLETED</button>
Error
Uncaught Error: Actions must be plain objects. Use custom middleware
for async actions.
Blockquote
Help me Out ...

You should call the selector on the connect function like this:
import { connect } from 'react-redux';
import getVisibleTodos from 'your/selector/file';
function YourComponent({ visibleTodos }) {
// You can access visibleTodos inside your component
// because now it's on the props
return (
<div>
//...
</div>
);
}
const mapping = (state, props) => ({
visibleTodos: getVisibleTodos(state, props),
});
connect(mapping)(YourComponent);
Inside the mapping function, you have access to the state and props for the current component. Keep in mind that all selectors must receive the redux store in order to query the data.
Good luck!

I expect that your Redux store state looks something like this:
{
todos: [
{
id: 1,
text: 'Buy milk',
completed: false
},
...
],
visibilityFilter: 'SHOW_ALL'
}
If it is so, then you have to rewrite your getVisibilityFilter and getTodos selectors.
const getVisibilityFilter = (state) => state.visibilityFilter;
const getTodos = (state) => state.todos;
Previously you weren't accessing the values from your state, using this edited functions you are. See how I am using dot notation to access the keys of state, which is nothing more than a JavaScript Object.
Also, when u want to use a selector, you should use it in a container component, where u can access the store's state using mapStateToProps function.
The container could look something like this:
import React from 'react';
import { connect } from 'react-redux';
import { getVisibleTodos } from './selectors.js';
import TodosList from './TodosList.jsx';
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
const VisibleTodosList = connect(
mapStateToProps
)(TodosList);
export default VisibleTodosList;
Where the TodosList component is your own component that displays the todos. It will receive all visible todos using props (this.props.todos).
In my opinion, selectors aren't called from your view (presentational) components, they are meant to be used in containers, where you can access the application's data.
If you want to learn more about containers and presentational components, take a look at this article, it's worth reading.

Related

Logic in component or mapStateToProps

If MyComponent gets data from the redux store, but organises it in some way first before mapping it, should that organisation be done in the component or mapStateToProps function and why?
const MyComponent = ({ data }) => {
// IN HERE?
return (
<div>
{data.map((d) => (...))}
</div>
);
};
const mapStateToProps = (state) => {
const output = state.data
// OR HERE?
return { data: output };
};
export default connect(mapStateToProps)(MyComponent);
Hello have a nice day.
i think is better have a file with all the logic to conect with redux, so every time i need to connect with redux i create a file that name is ComponentNameContainer.jsx, this file looks like that:
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import Row from '../components/Row';
import {doSomething} from '../redux/somethingActions'
// here the imports of function from your actions
export default withRouter(connect(
(state, ownProps) => {
return {
// props that u need from redux
// example: state.sessionReducer.user
}
},
{
// functions that u need from redux
//example: doSomething
}
)(Row))
i have a folder call containers to store all the container files to keep track of the components that are connected with redux.

can't pass the state as a props from redux

I'm stacked for a few hours trying to make a todo app by using react + redux.
I was trying on like a hundreds ways and, it looks like I can't get it
Mainly, I'm trying to get my state as a props in my component and I can't everytime when I try to display sth, im receving "undefined"
`
import React from 'react'
import { connect } from 'react-redux'
export const TodoList = () => {
return (
<div>
<h1>There's going to be a list of todos.</h1>
<h2>{this.props.todos}</h2>
</div>
)
}
const mapStateToProps = (state) => ({
todos: state.todos
})
export default connect(mapStateToProps)(TodoList)
`
code looks like that, and here's the reducer aswell.
const todosReducer = ( state = [{
id: 1,
content: 'asdads',
isDone: false
}], action) => {
switch(action.type){
case 'ADD_TODO' :
return [
...state,
{
id: action.id,
content: action.content,
isDone: action.isDone
}
]
case 'DELETE_TODO':
return state.filter(todo => {
return todo.id !== action.id
})
default:
return state;
}
}
export default todosReducer
and then here's the combineReducer function , because I want to add some filters later on.
And actually im marking it as todos but it won't work
import { combineReducers } from 'redux';
import todosReducer from './todos';
const rootReducer = combineReducers({
todos: todosReducer
})
export default rootReducer
Can anyone of you good boys explain a correct way for me ?
you need to pass props as an argument to the functional component,
otherwise the component has no idea where the identifier is declared thus it is undefined.
Also you need to use props.todos instead of this.props.todos as you are using a functional component instead of a class component.
export const TodoList = (props) => {
return (
<div>
<h1>There's going to be a list of todos.</h1>
<h2>{props.todos}</h2>
</div>
)
}
Hope this helps.
Edit: as pointed by #Shubham Khatri you also need to map over the array and render the list
For example:
{props.todos.map((todo) => {
return <h2>{todo.content}</h2>
})}
You can also use the short hand syntax to auto return the value.

TypeError: state.getIn is not a function

I'm implementing immutable on my react project, using it with redux, making state an immutable object using fromJS() function (from immutable library). In my reducer file, everything works, I receive an action, I can set the new value using setIn() function, and I can use getIn() function to access state.
But when I get state from connect() function, using mapStateToProps, even if console.log shows an apparently immutable object, I can't use immutable functions, like toJs() or getIn() here. I receive always this error: TypeError: state.getIn is not a function.
My index.js file
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import PropTypes from 'prop-types';
import Button from '#material-ui/core/Button';
import { template as templateAction } from './actions';
import withReducer from '../../../reducer/withReducer';
import templateReducer from './reducer';
export const Template = ({ name, template }) => (
<Button onClick={template}>
{name}
</Button>
);
Template.propTypes = {
name: PropTypes.string.isRequired,
template: PropTypes.func.isRequired,
};
export const mapStateToProps = (state) => {
console.log('state is equal to', state);
return (
{
name: state.getIn(['templateReducer', 'name']),
});
};
export const mapDispatchToProps = (dispatch) => ({
template: () => dispatch(templateAction()),
});
export default compose(
withReducer('templateReducer', templateReducer),
connect(mapStateToProps, mapDispatchToProps),
)(Template);
Result of console.log(state)
Result of console.log(state)
PS: When I don't use immutable state, everything works well.
Looks like state inside mapStateToProps function is an object with one property of 'templateReducer' that has a value of type Map.
I'm a bit rusty with my React knowledge, but maybe sharing the code for templateReducer would be helpful.
What does withReducer('templateReducer', templateReducer) do with the reducer function?
Seems like it is setting the state from the reducer to the key templateReducer before sending it to the mapStateToProps function. (maybe??)
Probably, changing this function to access the State before using immutable methods will remove the error.
export const mapStateToProps = (state) => {
console.log('state is equal to', state);
return (
{
name: state['templateReducer']?.getIn(['name']),
});
};
Use connect from griddle-react not react-redux.

Subscribing to single reducer with Redux Connect still delivers all reducers to component

I have a store with 9 reducers in it. I only want my component to listen to one of them discoverSearch. Using shorthand version of mapStateToProps this is my code. However, the component is still being delivered all reducers in componentWillReceiveProps.
Component
import React from 'react'
import { connect } from 'react-redux'
import { View, Text, Animated, Dimensions } from 'react-native'
const _ = require('lodash')
import colors from '../../Color'
import DiscoverSearchResultChannel from './DiscoverSearchResultChannel'
import DiscoverSearchResultEpisode from './DiscoverSearchResultEpisode'
const { height, width } = Dimensions.get('window')
class DiscoverSearchResultsContainer extends React.Component {
constructor() {
super()
this.generateResultsList = this.generateResultsList.bind(this)
}
generateResultsList(results, type) {
const components = []
for (let i = results.length - 1; i >= 0; i--) {
if (type === 'CHANNEL') {
const result =
(<DiscoverSearchResultChannel
entry={results[i]}
key={`dsearch-${results[i].id}`}
navigation={this.props.navigation}
/>)
components.push(result)
} else if (type === 'EPISODE') {
const result =
(<DiscoverSearchResultEpisode
entry={results[i]}
key={`dsearch-${results[i].id}`}
navigation={this.props.navigation}
/>)
components.push(result)
}
}
return components
}
render() {
const { episodes, channels } = this.props.discoverSearch.results
return (
<Animated.ScrollView
style={styles.shell}
contentContainerStyle={styles.innerContent}
>
<Text style={styles.divider}>Podcasts</Text>
{
_.isUndefined(channels) ? null : this.generateResultsList(channels, 'CHANNEL')
}
<Text style={styles.divider}>Episodes</Text>
{
_.isUndefined(episodes) ? null : this.generateResultsList(episodes, 'EPISODE')
}
</Animated.ScrollView>
)
}
}
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
Store
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import combinedReducers from './reducers/CombineReducers'
const middleware = applyMiddleware(
thunk,
createLogger(),
promise()
)
export default createStore(combinedReducers, middleware)
DiscoverSearchReducer
const initialState = {
results: []
}
const DiscoverSearchReducer = (state = initialState, action) => {
let newState
switch (action.type) {
case 'DISCOVER_SEARCH_REQUEST_OUT':
// TODO
break
case 'DISCOVER_SEARCH_RETURN':
newState = {
...state,
results: action.payload
}
break
default:
return state
}
return newState
}
Reducers
export default combineReducers({
network: NetworkReducer,
audioPlayer: AudioPlayerReducer,
fileHandler: FileHandlerReducer,
currentTrack: CurrentTrackReducer,
trackQueue: TrackQueueReducer,
asyncStatus: AsyncStatusReducer,
makeClip: MakeClipReducer,
userProfile: UserProfileReducer,
scrollListener: ScrollListenReducer,
userClips: UserClipsReducer,
discoverSearch: DiscoverSearchReducer,
})
App Entry
class App extends React.Component {
componentWillMount() {
// TODO
// Initialize Firebase => get UID then...
store.dispatch(fetchUser('7713BNBNPODPIE'))
}
componentDidMount() {
TrackPlayer()
}
render() {
return (
<Provider store={store} >
<View style={{ flex: 1 }}>
<Navigation />
<BottomPlayer />
</View>
</Provider>
)
}
}
export default App
The connect piece of Redux is a little new to me so I might be missing something obvious?
Edits:
Added App.js entry point
Added full DiscoverSearchResultsContainer component minus styles
The error is in your connect function:
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
You have parenthesis wrong, this is equivalent to:
connect(store => {
// this does nothing
{ discoverSearch: store.discoverSearch };
return dispatch => dispatch;
})(...)
Which is actually the same as
connect(state => state)(...)
What you probably meant to write was:
connect(store = ({ discoverSearch: store.discoverSearch }),
dispatch => dispatch)(...)
EDIT: Remove unnecessary dispatch
As commented, mapping the dispatch is useless, you can just do
const mapStateToProps = state => ({ discoverSearch: state.discoverSearch });
connect(mapStateProps)(Component);
The easiest to way use connect is to separate out mapStateToProps and mapDispatchToProps into their own function so as to avoid making syntactical errors and if you are anyways returning dispatch as the second parameter you might as well not use it since if the second parameter to connect is empty, dispatch is returned by default.
According to the connect documentaion
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or
Function): If an object is passed, each function inside it is assumed
to be a Redux action creator. An object with the same function names,
but with every action creator wrapped into a dispatch call so they may
be invoked directly, will be merged into the component’s props.
If a function is passed, it will be given dispatch as the first
parameter. It’s up to you to return an object that somehow uses
dispatch to bind action creators in your own way. (Tip: you may use
the bindActionCreators() helper from Redux.)
If your mapDispatchToProps function is declared as taking two
parameters, it will be called with dispatch as the first parameter and
the props passed to the connected component as the second parameter,
and will be re-invoked whenever the connected component receives new
props. (The second parameter is normally referred to as ownProps by
convention.)
If you do not supply your own mapDispatchToProps function or object
full of action creators, the default mapDispatchToProps
implementation just injects dispatch into your component’s props.
You could use your connect statmeent like
const mapStateToProps = store => {
return { discoverSearch: store.discoverSearch }
}
export default connect(mapStateToProps)(DiscoverSearchResultsContainer)
When you use connect like
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
You actually have your () at the wrong place as you want to return { discoverSearch: store.discoverSearch } and not { discoverSearch: store.discoverSearch }, dispatch => dispatch. It should be
export default connect(store => (
{ discoverSearch: store.discoverSearch })
)(DiscoverSearchResultsContainer)
The above snippet would be the same as
export default connect(store => {
return { discoverSearch: store.discoverSearch }
}
)(DiscoverSearchResultsContainer)
which is what you need. However you must go with the first approach
Is it possible that your DiscoverSearchReducer is returning a new object for each dispatch, even on a no-op?
function reduceSomething(state, action) {
if action.type == "forMe":
return {action.payload}
else
return {...state}
}
rather than:
return state
Because the former will give you a new object for every dispatch, with the expected content, but connect will be unable to suppress passing props to your component.
It's a weird one but it matches your symptoms.

How can I get ownProps using reselect on redux?

I want to create a selector with memoization using reselect based on some ownProps of mapStateToProps.
You can do this by connecting the selector to a component using the connect method provided by react-redux, then passing the component props (ownProps) as the second argument to the selector.
container.js
import { connect } from 'react-redux';
import { getVisibleTodos } from './selectors';
...
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props),
};
};
const VisibleTodoList = connect(
mapStateToProps,
)(TodoList);
export default VisibleTodoList;
You can then access those props in your selector
selectors.js
import { createSelector } from 'reselect';
const getVisibilityFilter = (state, props) =>
state.todoLists[props.listId].visibilityFilter;
const getTodos = (state, props) =>
state.todoLists[props.listId].todos;
const getVisibleTodos = createSelector(
...
);
export default getVisibleTodos;
However, this will not memoize correctly if you have multiple instances of the component you're passing props from. In that case, the selector would receive a different props argument each time, so it would always recompute instead of returning a cached value.
To share a selector across multiple components while passing in props and retaining memoization, each instance of the component needs its own private copy of the selector.
You can do this by creating a function that returns a new copy of the selector each time it's called.
selectors.js
import { createSelector } from 'reselect';
const getVisibilityFilter = (state, props) =>
state.todoLists[props.listId].visibilityFilter;
const getTodos = (state, props) =>
state.todoLists[props.listId].todos;
const makeGetVisibleTodos = () => {
return createSelector(
...
);
}
export default makeGetVisibleTodos;
If the mapStateToProps argument supplied to connect returns a function instead of an object, it will be used to create an individual mapStateToProps function for each instance of the container.
With that in mind, you can create a function makeMapStateToProps that creates a new getVisibleTodos selector, and returns a mapStateToProps function that has exclusive access to the new selector:
import { connect } from 'react-redux';
import { makeGetVisibleTodos } from './selectors';
...
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos();
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props),
};
};
return mapStateToProps;
};
const VisibleTodoList = connect(
makeMapStateToProps,
)(TodoList);
export default VisibleTodoList;
Now each instance of the VisibleTodosList container will get its own mapStateToProps function with a private getVisibleTodos selector. Memoization will now work correctly regardless of the render order of the containers.
This was adapted (blatently copied) from the Reselect documentation

Resources