I have setup the redux store but when I try to make changes to the state using mapStateToProps and mapDispatchToProps I get always the default state. So at account.js I want to get the selected language and then add it to the redux store. I try to call it in other components but I always end up with reducers/Language.js defaultState. What I'm doing wrong?
Account.js
class Account extends React.Component {
static navigationOptions = ({ navigation }) => {};
constructor(props) {
super(props);
this.state = {
language: {
sq: true,
en: false,
selected: '',
},
};
}
changeLanguage = (selected) => {
if (this.state.sq) {
this.setState({ selected: 'sq' });
} else {
this.setState({ selected: 'en' });
}
};
render() {
const navigation = this.props.navigation;
return (
<ScrollView>
<View>
<ThemeProvider>
<TableView header={I18n.t('account.lang_label')}>
<CheckboxRow
selected={this.state.language.sq}
onPress={() => {
this.setState(state => ({
language: {
sq: !state.language.sq,
en: !state.language.en,
},
}));
this.changeLanguage();
}}
title={I18n.t('account.albanian')}
/>
<CheckboxRow
selected={this.state.language.en}
onPress={() =>
this.setState(state => ({
language: {
en: !state.language.en,
sq: !state.language.sq,
},
}))
}
title={I18n.t('account.english')}
/>
</TableView>
</ThemeProvider>
</View>
</ScrollView>
);
}
}
const mapDispatchToProps = dispatch => {
return {
changeLanguage: (selected) => { dispatch(changeLanguageEn(selected))},
};
};
const mapStateToProps = state => {
return {
language: state.language.selected,
};
};
export default withNavigation(connect(
mapStateToProps,
mapDispatchToProps
)(Account));
actions/Language.js
import {CHANGE_LANGUAGE_EN, CHANGE_LANGUAGE_AL} from "./types";
export const changeLanguageEn = (language) => {
return {
type: CHANGE_LANGUAGE_EN,
lang: language,
}
};
export const changeLanguageAl = (language) => {
return {
type: CHANGE_LANGUAGE_AL,
lang: language,
}
};
reducers/Language.js
const defaultState = {
lang: '',
};
export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'CHANGE_LANGUAGE_EN':
return {...state, lang: 'en'};
case 'CHANGE_LANGUAGE_AL':
return Object.assign({}, state, {
lang: 'sq',
});
default:
return state;
}
}
In your mapStateToProps function try with state.lang directly
const mapStateToProps = state => {
return {
language: state.lang,
};
};
Hope this will work.
Not entirely sure I understand your question, but it sounds like you're trying to update the redux state with the internal state of Account? You should be able to do:
this.props.changeLanguage(this.state.language.selected)
You have a method on your component defined changeLanguage as well, perhaps you could do the line above in that method, after changing the internal state
additionally, in your changeLanguage method in your Account class, I don't think this.state.sq exists since sq is a key in the language state object. Instead it should be this.state.language.sq. You don't need to add the selected argument to this method either. Try making your changeLanguage method to look like this
changeLanguage = () => {
if (this.state.sq) {
this.setState({ language.selected: 'sq' });
} else {
this.setState({ language.selected: 'en' });
}
// dispatch your action here after updating the state
this.props.changeLanguage(this.state.language.selected)
};
Now calling this.changeLanguage(); will update your internal state, and then dispatch your changeLanguage redux action
You are accessing the selected language incorrectly. state.language.selected.
In the reducer you are adding lang property in the state, so access it with the same property name in the mapStateToProps.
const mapStateToProps = state => {
return {
language: state.language.lang,
};
};
Related
I'm using React Redux and want to be able to change the title and description of a post, using the onChange method. When only using React the way you would do this is that you keep an useState which you change whenever a change occurs, but I can't seem to get it to work with using redux in react. Instead of the state changing the original title, and description remains and cannot be changed.
From what I have read the basic idea is to have a listener on the input (onChange, usually) and have that fire a redux action. You then have the action tell the reducer to make the change to the store.
I have tried doing this, but could make it work correctly. What am I doing wrong and how do you solve it? I'm also wondering how do I specify that I want to change either title or description when using onChange, or do I simply send everything in post each time a change occurs?
This is what the redux state looks like when entering a post:
{
auth: {
isSignedIn: true,
user: {
id: '624481f22566374c138cf974',
username: 'obiwan',}
},
posts: {
'62448632b87b223847eaafde': {
_id: '62448632b87b223847eaafde',
title: 'hellothere',
desc: 'its been a long time since I heard that name...',
username: 'vorbrodt',
email: 'example#gmail.com',
categories: [],
createdAt: '2022-03-30T16:32:50.158Z',
updatedAt: '2022-03-30T16:32:50.158Z',
__v: 0
}
},
}
Here is where the onChange happens.
Post.js
import { getPostById, editPost } from "../actions";
const Post = ({ getPostById, editPost, username }) => {
const [updateMode, setUpdateMode] = useState(false);
let { id } = useParams();
let post = useSelector((state) => state.posts[id]);
const handleInputChange = (e) => {
try {
editPost(e.target.value);
} catch (err) {}
};
return (
<div className="post">
<div className="post-wrapper">
{updateMode ? (
<input
type="text"
value={post.title}
className="post-title-input"
autoFocus
onChange={(e) => handleInputChange(e)}
/>
) : (
<h1 className="post-title">
{post.title}
</h1>
)}
<div className="desc-area">
{updateMode ? (
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e)}
/>
) : (
<p className="post-desc">{post.desc}</p>
)}
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return { username: state.auth.user.username };
};
export default connect(mapStateToProps, { getPostById, editPost })(Post);
Here is the action creator:
//edit post in redux state
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
And here is the reducer which is suppose to change the state.
postReducer.js
import _ from "lodash";
import { GET_POSTS, GET_POST, CREATE_POST, EDIT_POST } from "../actions/types";
function postReducer(state = {}, action) {
switch (action.type) {
case GET_POSTS:
return { ...state, ..._.mapKeys(action.payload, "_id") };
case GET_POST:
return { ...state, [action.payload._id]: action.payload };
case CREATE_POST:
return { ...state, [action.payload._id]: action.payload };
case EDIT_POST:
//here the change should occur, not sure how to specify if title or desc should
//change
return { ...state, [action.payload._id]: action.payload };
default:
return state;
}
}
export default postReducer;
Hey there something like this should be of help
const handleInputChange = (e, key, id) => {
try {
editPost({ [key]: e.target.value, id });
} catch (err) {}
};
Usage
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e, "title", post.id)}
/>
action
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
Reducer
case EDIT_POST:
//here we destructure the id and return the data without the id cause we //need it below
const {id, ...newData} = action.payload
const indexToUpdate = state.posts.find(post => post.id === id)
const newPostsData = [...state.posts]
//Here we update the actual object and its property that is in the state at //the specific value
newPostsData[indexToUpdate] = {...newPostData[indexToUpdate], {...newData}
return { ...state, posts: newPostsData};
EDIT:
I fixed the problem in the reducer...changed this:
case ADD_LIST_ITEM:
return {
...state,
lists: {
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
},
loading: false
};
to this:
case ADD_LIST_ITEM:
return {
...state,
lists: [
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
],
loading: false
};
Stupid error on my part.
I have a MERN todo application using redux for state management and useEffect() for UI updates (all functional instead of class-based components). However, when I change state in the redux store, the UI does not update. This seems to only happen during an update triggered by a post request from the front end to the backend, where I pass data to an action, which is handled in a reducer (a js file rather than the useReducer() hook in this app). My backend will update properly, but the UI will crash.
What happens is, I input, say, a new list item in a given todo list, and the error I get is:
Uncaught TypeError: list.lists.map is not a function
at Dashboard (Dashboard.jsx:32)
I'm not sure where to use an additional useEffect(), if needed, or if there's a problem in my reducer...here's the relevant flow (removed all className declarations and irrelevant parts):
/* Dashboard.jsx */
// imports //
const Dashboard = ({ auth: { user }, list, getLists }) => {
useEffect(() => {
getLists();
}, [getLists]);
return (
<>
<p>Lists...</p>
{list.lists &&
list.lists.map(list => <List key={list._id} list={list} />)}
</>
);
};
Dashboard.propTypes = {
getLists: PropTypes.func.isRequired,
list: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
list: state.list
});
export default connect(mapStateToProps, { getLists })(Dashboard);
/* List.jsx */
// imports
const List = ({ list, addListItem, getLists }) => {
const [text, setText] = useState('');
useEffect(() => {
getLists();
}, []);
const handleAddItem = e => {
e.preventDefault();
addListItem(list._id, { text });
setText('');
};
return (
<div>
{list.listItems &&
list.listItems.map((item, index) => (
<ListItem
key={index}
item={item}
listId={list._id}
itemIndex={index}
/>
))}
<div>
<form onSubmit={handleAddItem}>
<input
type="text"
name="text"
placeholder="add a to-do item"
value={text}
onChange={e => setText(e.target.value)}
/>
<input type="submit" value="add" />
</form>
</div>
</div>
);
};
List.propTypes = {
addListItem: PropTypes.func.isRequired,
getLists: PropTypes.func.isRequired
};
export default connect(null, {
addListItem,
getLists
})(List);
/* list.actions.js */
// imports
export const addListItem = (listId, text) => async dispatch => {
try {
const res = await api.post(`/lists/${listId}`, text); // returns all list items after adding new item
dispatch({
type: ADD_LIST_ITEM,
payload: { id: listId, data: res.data }
});
} catch (err) {
dispatch({
type: LIST_ERROR,
payload: { message: err.response.statusText, status: err.response.status }
});
}
};
/* list.reducer.js */
// imports
const initialState = {
lists: [],
list: null,
loading: true,
error: {}
};
const list = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case GET_LISTS:
return { ...state, lists: payload, loading: false };
case LIST_ERROR:
return { ...state, error: payload, loading: false };
case ADD_LIST_ITEM:
return {
...state,
lists: {
...state.lists.map(list =>
list._id === payload.id
? { ...list, listItems: payload.data }
: list
)
},
loading: false
};
default:
return state;
}
};
export default list;
I assume when creating your app's store, you are passing list as rootReducer,
Meaning your app's main state is exactly the state list is managing.
So if you need to access property lists of the state, you need to do it like this:
const mapStateToProps = state => ({
lists: state.lists /// state in here is exactly the state of list reducer
});
Now, in Dashboard lists is that array that you manipulate in list reducer.
Also, you have defined a property also named list in list reducer. It is initially defined to be null, also in the reducer, you never change it:
const initialState = {
lists: [],
list: null, /// none of actions ever change this, meaning it's currently useless.
loading: true,
error: {}
};
React/Redux application goes into an infinite loop on using useEffect with object references..
I am trying render pending todos for my application using useEffect.. and passing the array of todos as the second param in useEffect ..but why is not checking the values of the object ?
Container:
const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(RootActions, dispatch) });
const Home = (props) => {
const { root, actions } = props;
useEffect(() => {
getTodos(actions.loadPendingTodo);
}, [root.data]);
return (
<Segment>
<Error {...root } />
<TodoList { ...root } actions={actions} />
</Segment>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Action:
export const loadPendingTodo = () => ({
type: LOAD_PENDING_TODO,
data: todoService.loadPendingTodo(),
});
Reducer:
const initialState = {
initial: true,
data: [{
id: 0,
text: 'temp todo',
dueDate: new Date(),
completedDate: '',
isDeleted: false,
isCompleted: false,
}],
error: false,
isLoading: false,
isEdit: false,
};
export default function root(state = initialState, action) {
switch (action.type) {
case LOAD_PENDING_TODO:
return {
...state,
data: [...action.data],
};
...
default:
return state;
}
}
getTodos Method:
export const getTodos = (loadTodo) => {
try {
loadTodo();
} catch (error) {
console.log(error); // eslint-disable-line
}
};
Service:
export default class TodoAppService {
loadPendingTodo() {
return store.get('todoApp').data.filter(todo => !todo.isCompleted && !todo.isDeleted);
}
Can anyone please help me out how to resolve this issue.. and there is no official documentation for this case too :/
Moreover changing the useEffect to the following works but i want to render on every change
useEffect(() => {
getTodos(actions.loadPendingTodo);
}, []);
Fixed it by removing the loadPedningTodo redux actions in useEffect that was causing it to loop and directly setting the data in function from service..
const Home = (props) => {
const { root, actions } = props;
return (
<Segment>
<Error {...root } />
<TodoList isEdit={root.isEdit} todo={todoService.loadPendingTodo()} actions={actions} />
</Segment>
);
};
thanks :)
There have been quite a few similar questions raised here but I couldn't find the answer to my problem.
Here is my React component:
class MyTransitPage extends Component {
componentDidMount() {
this.props.getPassengerProfile();
}
render() {
return (
<View>
<Text>{this.props.passenger.pid}</Text>
</View>
);
}
}
const mapStateToProps = ( state ) => {
return {
passenger: state.transit.passenger
}
}
export default connect(mapStateToProps, { getPassengerProfile })(MyTransitPage)
Here is my reducer:
const initialState = {
passenger: {}
};
export default(state = initialState, action) => {
console.log("reducer ");
console.log(action.payload);
switch(action.type) {
case 'GET_PASSENGER_PROFILE':
return { ...state, passenger: action.payload};
default:
return state;
}
}
When I do console.log in the reducer I can see that the payload is there:
"passenger": Object {
[20:57:55] "createdAt": "2019-02-10T13:02:40.897Z",
[20:57:55] "objectId": "YKzeH2Nh3C",
[20:57:55] "pid": "iwKHqfCQSu",
[20:57:55] "updatedAt": "2019-02-10T13:02:40.897Z",
[20:57:55] },
But when I try to access {this.props.passenger.pid} in the component, I get pid as undefined. I can't find where the problem is. Where did I get it wrong?
EDIT:
Root reducer:
export default combineReducers({
auth: AuthenticationReducer,
transit: TransitReducer
});
EDIT2: Action:
export const getPassengerProfile = () => {
const currentUser = Parse.User.current();
const currentUserId = currentUser._getId()
const Passenger = Parse.Object.extend('Passenger');
const query = new Parse.Query(Passenger);
query.equalTo("pid", currentUserId);
return (dispatch) => {
query.first().then(response => {
dispatch ({
type: 'GET_PASSENGER_PROFILE',
payload: response
});
});
};
}
You are calling this.props.getPassengerProfile() in the componentDidMount which means the component is already mounted and rendered before the state is populated from that value. I would just add a check in your render so it doesn't error out and then when it does get there it should show properly. Like this:
render() {
//console.log(this.props.transit);
console.log(this.props);
return (
<View>
<Text>{this.props.passenger ? this.props.passenger.pid : ''}</Text>
</View>
);
}
I used JSON.parse(JSON.stringify()) on the action.payload, which seems to have resolved the issue.
I got one container connected to one component. Its a select-suggestion component. The problem is that both my container and component are getting too much repeated logic and i want to solve this maybe creating a configuration file or receiving from props one config.
This is the code:
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { goToPageRequest as goToPageRequestCompetitions } from '../ducks/competitions/index';
import { getSearchParam as getSearchCompetitionsParam, getCompetitionsList } from '../ducks/competitions/selectors';
import { goToPageRequest as goToPageRequestIntermediaries } from '../ducks/intermediaries/index';
import { getSearchParam as getSearchIntermediariesParam, getIntermediariesList } from '../ducks/intermediaries/selectors';
import SelectBox2 from '../components/SelectBox2';
export const COMPETITIONS_CONFIGURATION = {
goToPageRequest: goToPageRequestCompetitions(),
getSearchParam: getSearchCompetitionsParam(),
suggestions: getCompetitionsList()
};
export const INTERMEDIARIES_CONFIGURATION = {
goToPageRequest: goToPageRequestIntermediaries(),
getSearchParam: getSearchIntermediariesParam(),
suggestions: getIntermediariesList()
};
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(SelectBox2));
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Flex, Box } from 'reflexbox';
import classname from 'classnames';
import styles from './index.scss';
import Input from '../Input';
import { AppButtonRoundSquareGray } from '../AppButton';
import RemovableList from '../RemovableList';
const MIN_VALUE_TO_SEARCH = 5;
const NO_SUGGESTIONS_RESULTS = 'No results found';
class SelectBox extends Component {
/**
* Component setup
* -------------------------------------------------------------------------*/
constructor(props) {
super(props);
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
}
/**
* Component lifecycle
* -------------------------------------------------------------------------*/
componentWillMount() {
console.log(this.props);
document.addEventListener('mousedown', this.onClickOutside, false);
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
}
componentWillUnmount() {
console.log(this.props);
document.removeEventListener('mousedown', this.onClickOutside, false);
}
componentWillReceiveProps(nextProps){
console.log(this.props);
if (this.props.suggestionsType === 'competition') {
this.state.suggestions = nextProps.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries') {
this.state.suggestions = nextProps.intermediariesSuggestions;
}
}
/**
* DOM event handlers
* -------------------------------------------------------------------------*/
onButtonClick = (ev) => {
ev.preventDefault();
const itemIncluded = this.state.items.find(item => item.id === this.state.selection);
if (this.state.selection && !itemIncluded) {
const item =
this.state.suggestions.find(suggestion => suggestion.id === this.state.selection);
this.setState({ items: [...this.state.items, item] });
}
};
onChangeList = (items) => {
const adaptedItems = items
.map(item => ({ label: item.name, id: item.itemName }));
this.setState({ items: adaptedItems });
};
onClickOutside = (ev) => {
if (this.wrapperRef && !this.wrapperRef.contains(ev.target)) {
this.setState({ displayBox: false });
}
};
onSuggestionSelected = (ev) => {
this.setState({
displayBox: false,
value: ev.target.textContent,
selection: ev.target.id });
};
onInputChange = (ev) => {
this.generateSuggestions(ev.target.value);
};
onInputFocus = () => {
this.generateSuggestions(this.state.value);
};
/**
* Helper functions
* -------------------------------------------------------------------------*/
setWrapperRef = (node) => {
this.wrapperRef = node;
};
executeSearch = (value) => {
if (this.props.suggestionsType === 'competition'){
this.props.searchCompetitions(value);
}
if (this.props.suggestionsType === 'intermediaries'){
this.props.searchIntermediaries(value);
}
};
generateSuggestions = (value) => {
if (value.length > MIN_VALUE_TO_SEARCH) {
this.executeSearch(value);
this.setState({ displayBox: true, value, selection: '' });
} else {
this.setState({ displayBox: false, value, selection: '' });
}
};
renderDataSuggestions = () => {
const { listId } = this.props;
const displayClass = this.state.displayBox ? 'suggestions-enabled' : 'suggestions-disabled';
return (
<ul
id={listId}
className={classname(styles['custom-box'], styles[displayClass], styles['select-search-box__select'])}
>
{ this.state.suggestions.length !== 0 ?
this.state.suggestions.map(suggestion => (<li
className={classname(styles['select-search-box__suggestion'])}
onClick={this.onSuggestionSelected}
id={suggestion.get(this.props.suggestionsOptions.id)}
key={suggestion.get(this.props.suggestionsOptions.id)}
>
<span>{suggestion.get(this.props.suggestionsOptions.label)}</span>
</li>))
:
<li className={(styles['select-search-box__no-result'])}>
<span>{NO_SUGGESTIONS_RESULTS}</span>
</li>
}
</ul>
);
};
renderRemovableList = () => {
if (this.state.items.length > 0) {
const adaptedList = this.state.items
.map(item => ({ name: item.name, itemName: item.id }));
return (<RemovableList
value={adaptedList}
className={classname(styles['list-box'])}
onChange={this.onChangeList}
uniqueIdentifier="itemName"
/>);
}
return '';
};
render() {
const input = {
onChange: this.onInputChange,
onFocus: this.onInputFocus,
value: this.state.value
};
return (
<Flex className={styles['form-selectBox']}>
<Box w={1}>
<div
ref={this.setWrapperRef}
className={styles['div-container']}
>
<Input
{...this.props}
input={input}
list={this.props.listId}
inputStyle={classname('form-input--bordered', 'form-input--rounded', styles.placeholder)}
/>
{ this.renderDataSuggestions() }
</div>
</Box>
<Box>
<AppButtonRoundSquareGray type="submit" className={styles['add-button']} onClick={this.onButtonClick}>
Add
</AppButtonRoundSquareGray>
</Box>
<Box>
{ this.renderRemovableList() }
</Box>
</Flex>
);
}
}
SelectBox.propTypes = {
items: PropTypes.instanceOf(Array),
placeholder: PropTypes.string,
listId: PropTypes.string,
className: PropTypes.string
};
SelectBox.defaultProps = {
items: [],
placeholder: 'Choose an option...',
listId: null,
className: ''
};
export default SelectBox;
As you see, in many places i am validating the type of suggestions and do something with that. Its suppose to be a reusable component, and this component could accept any kind of type of suggestions. If this grows, if will have very big validations and i don't want that. So i think that i want something similar to this:
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
How can i make something similar to that?
Here are a few things to consider:
The purpose of using Redux is to remove state logic from your components.
What you've currently got has Redux providing some state and your component providing some state. This is an anti-pattern (bad):
// State from Redux: (line 22 - 24)
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
// State from your component: (line 65 - 71)
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
If you take another look at your SelectBox component - a lot of what it is doing is selecting state:
// The component is parsing the state and choosing what to render (line 79 - 86)
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
Turns out, this is precisely what mapStateToProps() is for. You should move this selection logic to mapStateToProps(). Something like this:
const mapStateToProps = (state) => {
let suggestions = null;
switch (state.suggestionType) {
case 'competition':
suggestions = state.suggestions.competition;
break;
case 'intermediaries':
suggestions = state.suggestions.intermediaries;
break;
default:
break;
}
return {
suggestions
};
};
Every time the state updates (in Redux) it will pass new props to your component. Your component should only be concerned with how to render its part of the state. And this leads me to my next point: When your application state is all being managed by Redux and you don't have state logic in your components, your components can simply be functions (functional components).
const SelectBox3 = ({ suggestions }) => {
const onClick = evt => { console.log('CLICK!'); };
const list = suggestions.map((suggestion, index) => {
return (
<li key={index} onClick={onClick}>suggestion</li>
);
});
return (
<ul>
{list}
</ul>
);
};
Applying these patterns, you get components that are very easy to reason about, and that is a big deal if you want to maintain this code into the future.
Also, by the way, you don't need to use mergeProps() in your example. mapDispatchToProps can just return your search function since connect() will automatically assemble the final props object for you.:
const mapDispatchToProps = (dispatch, ownProps) => ({
// 'search' will be a key on the props object passed to the component!
search: searchParam => {
dispatch(ownProps.reduxConfiguration.goToPageRequest({ searchParam });
// (also, your 'reduxConfiguration' is probably something that belongs in
// the Redux state.)
}
});
I highly recommend giving the Redux docs a good read-through. Dan Abramov (and crew) have done a great job of laying it all out in there and explaining why the patterns are the way they are.
Here's the link: Redux.
Also, look into async actions and redux-thunk for dealing with asynchronous calls (for performing a search on a server, for example).
Finally let me say: you're on the right track. Keep working on it, and soon you will know the joy of writing elegant functional components for your web apps. Good luck!