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!
Related
I'm have the title error on my application but i don't know how to solve this problem.
This is my application running:
First-image
When i click on each of these maps, a shadow is rendered on them and they are passed to a list on redux state, also a counter of how many maps you have completed it's showed.
Second-image
When you click the button awakened, the maps are re-rendered and new tier of maps are shown, and you can also click them to complete each map. Works just like the normal map logic.
First step to error
This third image it's just like the first, the difference is that i took out one map of the list, and here is where the error occurs, at this point nothing wrong happens, but the moment i click the "awakened" button the application stops and it give me that error.
I know where the error it's happening, but i didn't figure out how to solve it.
This is the component i'm working on:
import React from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Container } from "./styles";
import "./mapsScss";
import { mapCompleted, mapUncompleted, awakenedMapCompleted, awakenedMapUncompleted } from "./mapActions";
const base_map = require("./map_base_icon.png");
const Map = props => {
const { maps, awakenedMaps } = props;
const toggleCompletedMap = id => {
if (maps.includes(props.id)) {
props.mapUncompleted(id);
} else {
props.mapCompleted(id);
}
};
const toggleAwakenedCompletedMap = id => {
if (awakenedMaps.includes(props.id)) {
props.awakenedMapUncompleted(id);
} else {
props.awakenedMapCompleted(id);
}
};
const onClickToggle = () => {
if (props.normalActive) {
toggleCompletedMap(props.id);
}
if (props.awakenedActive) {
toggleAwakenedCompletedMap(props.id);
}
};
const baseMapRender = () => {
if (props.color_tag === "Unique") {
return;
} else {
return <img src={base_map} alt="Base Map" />;
}
};
return (
<Container id={props.id}>
<div className="map_name">{props.map_name}</div>
<div>
{baseMapRender()}
<img src={props.map_color} alt={`${props.map_name} ${props.color_tag} Map`} />
<div
className={`toggle-completed
${props.normalActive ? (maps.includes(props.id) ? "completed-map" : "") : ""}
${props.awakenedActive ? (awakenedMaps.includes(props.id) ? "completed-awakened-map" : "") : ""}
`}
onClick={onClickToggle}
></div>
</div>
<div className="map_tier">Tier {props.map_tier}</div>
</Container>
);
};
const mapStateToProps = state => ({
maps: state.map.maps,
awakenedMaps: state.map.awakenedMaps,
normalActive: state.atlas.normalActive,
awakenedActive: state.atlas.awakenedActive,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
mapCompleted,
mapUncompleted,
awakenedMapCompleted,
awakenedMapUncompleted,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(Map);
this is the action creatos file:
export const mapCompleted = id => ({
type: "MAP_COMPLETED",
payload: id,
});
export const mapUncompleted = id => ({
type: "MAP_UNCOMPLETED",
payload: id,
});
export const awakenedMapCompleted = id => ({
type: "AWAKENED_MAP_COMPLETED",
payload: id,
});
export const awakenedMapUncompleted = id => ({
type: "AWAKENED_MAP_UNCOMPLETED",
payload: id,
});
and this is the reducers file:
const INITIAL_STATE = {
maps: [],
mapCounter: 0,
awakenedMaps: [],
awakenedMapCounter: 0,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case "MAP_COMPLETED":
return { ...state, maps: [...state.maps, action.payload], mapCounter: state.mapCounter + 1 };
case "MAP_UNCOMPLETED":
return { maps: state.maps.filter(item => item !== action.payload), mapCounter: state.mapCounter - 1 };
case "AWAKENED_MAP_COMPLETED":
return {
...state,
awakenedMaps: [...state.awakenedMaps, action.payload],
awakenedMapCounter: state.awakenedMapCounter + 1,
};
case "AWAKENED_MAP_UNCOMPLETED":
return {
awakenedMaps: state.awakenedMaps.filter(item => item !== action.payload),
awakenedMapCounter: state.awakenedMapCounter - 1,
};
default:
return state;
}
};
There are 2 problems that need fix as I can see
Here
case "AWAKENED_MAP_UNCOMPLETED":
return {
awakenedMaps: state.awakenedMaps.filter(item => item !== action.payload),
awakenedMapCounter: state.awakenedMapCounter - 1,
};
and here
case "MAP_UNCOMPLETED":
return { maps: state.maps.filter(item => item !== action.payload), mapCounter: state.mapCounter - 1 };
you don't return the previous state, so the other properties are lost and will become undefined. This will reflect at next update of your component where you check with includes function
Try these
case "AWAKENED_MAP_UNCOMPLETED":
return {...state,
awakenedMaps: state.awakenedMaps.filter(item => item !== action.payload),
awakenedMapCounter: state.awakenedMapCounter - 1,
};
and
case "MAP_UNCOMPLETED":
return { ...state, maps: state.maps.filter(item => item !== action.payload), mapCounter: state.mapCounter - 1 };
I'm curently working on a React app and trying to detect the time taken to reach a certain input value length.
It is a controlled input which value is stored and set via redux action and reducer.
I want to start counting time when the input value is !== "" and stop counting when the value .length is equal to 13.
Further, in the app logic, if the time taken to reach .length === 13 is something like under 100ms ( + or - ) it will mean that the app user used a barcode scanner, else, he typed the barcode with the keyboard.
i've tried to use vars with new Date() to get the time diff but the render() logic blocks the maintain of the elapsed time count...
Any idea of how i could achieve my goal ?
I leave you the component code just below,
Thank you in advance !
import React from "react";
import StoreInput from "../StoreInput/index";
import { connect } from "react-redux";
import "./index.scss";
import { setStoreInputFieldValue } from "../../actions/store.actions";
import { addArticleToStore } from "../../actions/articles.actions";
type ScanSetProps = {
// Redux State
storeInputFieldValue?: any;
// Redux Actions
setStoreInputFieldValue?: any;
addArticleToStore?: any;
};
class ScanSet extends React.Component<ScanSetProps> {
handleScanSet = (event) => {
const { setStoreInputFieldValue } = this.props;
setStoreInputFieldValue(event.target.value);
};
// Component render
render() {
const { storeInputFieldValue, addArticleToStore } = this.props;
return (
<div className="ScanSet">
<StoreInput
idStoreInput={"scanSetInput"}
typeStoreInput={"number"}
placeholderStoreInput={
"Scannez le code barre ou saisissez le code EAN"
}
storeInputFillMethod={this.handleScanSet}
/>
<button
id="scanSetButton"
className={
storeInputFieldValue.length === 13
? "enabledButton"
: "disabledButton"
}
onClick={() => addArticleToStore(storeInputFieldValue)}
>
Ajouter
</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
storeInputFieldValue: state.store.storeInputFieldValue,
});
const mapDispatchToProps = (dispatch) => ({
setStoreInputFieldValue: (input_value) =>
dispatch(setStoreInputFieldValue(input_value)),
addArticleToStore: (article_ean) => dispatch(addArticleToStore(article_ean)),
});
export default connect(mapStateToProps, mapDispatchToProps)(ScanSet);
I would recommend using state.
When input !== '',
this.setState((state) => {...state, startTime: new Date().getTime()})
When value.length === 13,
this.setState((state) => {...state, endTime: new Date().getTime()}
Then you can have another part which accounts for the difference, (endTime - startTime)
Since you're using redux, if you have a slice that accounts for this. You can simply dispatch the two actions (setStartTime, setEndTime) and let the reducer handle the logic above.
Urmzd's answer was a good approach, it solved my problem and also used the Redux Saga library each time a "SET_END_TIME" action is triggered to getTimeDiff and launch the further logic. Here is what code now looks like :
Component Code : index.tsx
import StoreInput from "../StoreInput/index";
import { connect } from "react-redux";
import "./index.scss";
import {
setStoreInputFieldValue,
setStartTime,
setEndTime,
resetTimeDiff,
} from "../../actions/store.actions";
import { handleInputEan } from "../../actions/articles.actions";
type ScanSetProps = {
// Redux State
storeInputFieldValue?: any;
// Redux Actions
setStoreInputFieldValue?: any;
handleInputEan?: any;
setStartTime?: any;
setEndTime?: any;
resetTimeDiff?: any;
};
class ScanSet extends React.Component<ScanSetProps> {
handleScanSet = (event) => {
const {
setStoreInputFieldValue,
storeInputFieldValue,
setStartTime,
setEndTime,
resetTimeDiff,
} = this.props;
setStoreInputFieldValue(event.target.value);
if (storeInputFieldValue.length + 1 === 1) {
setStartTime();
} else if (storeInputFieldValue.length + 1 === 13) {
setEndTime();
} else if (storeInputFieldValue.length - 13 === 0) {
resetTimeDiff();
}
};
// Component render
render() {
const { storeInputFieldValue, handleInputEan } = this.props;
return (
<div className="ScanSet">
<StoreInput
idStoreInput={"scanSetInput"}
typeStoreInput={"number"}
placeholderStoreInput={
"Scannez le code barre ou saisissez le code EAN"
}
storeInputFillMethod={this.handleScanSet}
/>
<button
id="scanSetButton"
className={
storeInputFieldValue.length === 13
? "enabledButton"
: "disabledButton"
}
onClick={() => handleInputEan()}
>
Ajouter
</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
storeInputFieldValue: state.store.storeInputFieldValue,
timeDiff: state.store.timeDiff,
});
const mapDispatchToProps = (dispatch) => ({
setStartTime: () => dispatch(setStartTime()),
setEndTime: () => dispatch(setEndTime()),
resetTimeDiff: () => dispatch(resetTimeDiff()),
setStoreInputFieldValue: (input_value) =>
dispatch(setStoreInputFieldValue(input_value)),
handleInputEan: () => dispatch(handleInputEan()),
});
export default connect(mapStateToProps, mapDispatchToProps)(ScanSet);
Actions Code : store.actions.js ( with consts from store.const.js )
import * as storeConst from "../const/store.const";
export const setStartTime = () => ({
type: storeConst.SET_START_TIME,
payload: new Date().getTime(),
});
export const setEndTime = () => ({
type: storeConst.SET_END_TIME,
payload: new Date().getTime(),
});
export const getTimeDiff = () => ({
type: storeConst.GET_TIME_DIFF,
});
export const resetTimeDiff = () => ({
type: storeConst.RESET_TIME_DIFF,
});
Reducers Code : store.reducer.js ( with consts from store.const.js and reducers combined in an index.js file)
import * as storeConst from "../const/store.const";
const initState = {
startTime: null,
endTime: null,
timeDiff: null,
};
const store = (state = initState, action) => {
switch (action.type) {
case storeConst.SET_START_TIME:
return { ...state, startTime: action.payload };
case storeConst.SET_END_TIME:
return { ...state, endTime: action.payload };
case storeConst.GET_TIME_DIFF:
return { ...state, timeDiff: state.endTime - state.startTime };
case storeConst.RESET_TIME_DIFF:
return { ...state, timeDiff: null };
default:
return state;
}
};
export default store;
Redux Saga Code : store.saga.js ( combined: in an index.js file as rootSaga)
import { put } from "redux-saga/effects";
import { store } from "../store";
import {
getTimeDiff,
resetTimeDiff,
} from "../actions/store.actions";
import {
handleInputEan,
} from "../actions/articles.actions";
export function* getTimeDiffLogic() {
yield put(getTimeDiff());
const timeDiff = yield store.getState().store.timeDiff;
if (timeDiff < 250) {
yield put(handleInputEan());
yield put(resetTimeDiff());
}
}
Hope that will help someone like it helped me a lot !
i have such problem: I'm making To-Do-List, and now I want to make EditMode for my tasks. But when I try to do it, it returns string not an array, and that's why I have 3 errors (map,some,filter = is not a function). So I don't know how to change state(task) and return changed array.
Some details: I'm using connect to get props.
Component's code
class Item extends React.Component {
state = {
statusChange: false,
task: ''
}
activeStatusChange = () => {
this.setState( {
statusChange: true
}
);
}
deActivateStatusChange = () => {
this.setState( {
statusChange: false
}
);
this.props.editTask(this.state.task)
}
onStatusChange = (e) => {
this.setState({
task: e.currentTarget.value
})
}
render(){
return (
<div className={s.item}>
<span onClick={this.props.editStatus} className={s.statusTask}>
{this.props.status ? <img src="https://img.icons8.com/doodle/48/000000/checkmark.png"/>
: <img src="https://img.icons8.com/emoji/48/000000/red-circle-emoji.png"/>}
</span>
{ this.state.statusChange
? <input onChange={this.onStatusChange} autoFocus={true} onBlur={this.deActivateStatusChange} value={this.state.task} />
: <span className={this.props.status === true ? s.task : s.taskFalse} onClick={this.activeStatusChange}> {this.props.task} </span>}
<span onClick={this.props.deleteTask} className={s.close}><img src="https://img.icons8.com/color/48/000000/close-window.png"/></span>
</div>
)
}
}
export default Item;
Reducer's code
import React from 'react'
import shortid from 'shortid';
const ADD_TASK = 'ADD_TASK'
const EDIT_STATUS = 'EDIT_STATUS'
const TASK_DELETE = 'TASK_DELETE'
const REMOVE_ALL_DONE = 'REMOVE_ALL_DONE'
const REMOVE_ALL_TASKS = 'REMOVE_ALL_TASKS'
const EDIT_TASK = 'EDIT_TASK'
const initialState = {
tasks: []
};
const mainReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK: {
return {
...state,
tasks: [{
id: shortid.generate(),
task: action.task,
status: false
}, ...state.tasks]
}
}
case EDIT_STATUS: {
return {
...state,
tasks: state.tasks.map(task => task.id === action.id ? {...task, status: !task.status} : task)
}
}
case TASK_DELETE: {
return {
...state,
tasks: state.tasks.filter(t => t.id !== action.id)
}
}
case REMOVE_ALL_DONE: {
return {
...state,
tasks: state.tasks.filter(t => !t.status)
}
}
case REMOVE_ALL_TASKS: {
return {
...state,
tasks: []
}
}
case EDIT_TASK: {
return {
...state,
tasks: action.task
}
}
default:
return state
}
}
export const addTask = task => ({type: 'ADD_TASK', task});
export const editStatus = id => ({type: 'EDIT_STATUS', id})
export const deleteTask = id => ({type: 'TASK_DELETE', id})
export const removeAllDone = () => ({type:'REMOVE_ALL_DONE'})
export const removeAllTasks = () => ({type: 'REMOVE_ALL_TASKS'})
export const editTask = task => ({type: 'EDIT_TASK', task})
export default mainReducer;
You should create a container that uses the methods mapDispatchToProps so you can use your actions in the component.
https://react-redux.js.org/using-react-redux/connect-mapdispatch
So lets do it. Just create a file that will be your container for that component and put the code like this:
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Item from 'wherever your component is';
import { addTask } from 'wherever your action is';
const mapStateToProps = ({ }) => ({
// Here you can pass the redu state to your component
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
// Here you pass the action to your component
addTask
}, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(Item);
Then when you want to use the Item component import it from the container and it will receive in props both the action and the state that you are passing from the container file.
In you Item component you can use the action like this:
// ITem component
render() {
return (
<button onClick={this.props.addTask} />
)
}
If any doubt just let me know!
This is just a sample code I am trying to control my controlled inputs using Redux, I add the Redux to my React project and add my reducer and action but everything works well except updating my component in one of my actions.
the following code is my Reducer:
import actionTypes from "./actions";
const uniqid = require("uniqid");
const firstID = uniqid();
const initialState = {
cons: [
{
value: "",
id: firstID,
added: false
}
],
pros: [
{
value: "",
id: firstID,
added: false
}
],
num: 0
};
const reducer = (state = initialState, action) => {
const newState = { ...state };
switch (action.type) {
case actionTypes.HANDLEINPUTCHANGE:
// const newState = state;
const changingItem = newState[action.case].find(item => {
return item.id === action.id;
});
const changingItemIndex = newState[action.case].findIndex(item => {
return item.id === action.id;
});
changingItem.value = action.event;
if (
changingItemIndex === newState[action.case].length - 1 &&
!changingItem.added
) {
alert(123);
const newItem = {
id: uniqid(),
value: "",
added: false
};
newState[action.case].push(newItem);
changingItem.added = true;
console.log(newState);
}
newState[action.case][changingItemIndex] = changingItem;
return newState;
case actionTypes.CLICK:
newState.num += 1;
return {
...newState
};
default:
return state;
}
};
export default reducer;
and the following code is my component, unfortunately, the HANDLEINPUTCHANGE action type did not update my component:
import React, { Component } from "react";
import FormElement from "../../base/components/formElement/FormElement";
import actionTypes from "../../base/store/actions";
import { connect } from "react-redux";
import "./style.scss";
class FormGenerator extends Component {
render() {
console.log(this.props);
return (
<ul className="row formGeneratorContainer fdiColumn">
<li onClick={this.props.click}>{this.props.num}</li>
{this.props[this.props.case].map((item, index) => {
return (
<li className="row formGeneratorItem" key={index}>
<div className="bullet d_flex jcCenter aiCenter">1</div>
{/* <FormElement onChange={(e,index,type,)}/> */}
<input
name={item.id}
type="text"
onChange={event =>
this.props.onFieldValueChange(
event.target.value,
index,
this.props.case,
item.id
)
}
/>
</li>
);
})}
</ul>
);
}
}
const mapStateToProps = state => {
return {
cons: state.cons,
pros: state.pros,
num: state.num
};
};
const mapDispachToProps = dispatch => {
return {
onFieldValueChange: (event, index, c, id) =>
dispatch({
event: event,
index: index,
case: c,
id: id,
type: actionTypes.HANDLEINPUTCHANGE
}),
click: () => dispatch({ type: actionTypes.CLICK })
};
};
export default connect(
mapStateToProps,
mapDispachToProps
)(FormGenerator);
You need to set value of your controlled component:
<input
name={item.id}
type="text"
value={item.value}
onChange={event =>
this.props.onFieldValueChange(
event.target.value,
index,
this.props.case,
item.id
)
}
/>
Other problems are in your reducer, you are mutating the redux state with these lines:
newState[action.case].push(newItem);
// ...
newState[action.case][changingItemIndex] = changingItem;
Look at these sections in the redux documentation:
Inserting and Removing Items in Arrays
Updating an Item in an Array
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,
};
};