I'm extremely new to react/redux and very mediocre at Javascript, but I've been struggling with this for over a week now. Here's what I'm trying to do:
Clicking one of the navigation menu items on the left should dispatch an action to set the selected index in the store (at this point the entire store is just a number). When the index is updated it should automatically be reflected in the UI, at least by changing the css class of the selected item, but eventually it should toggle visibility for content components on the right.
Sidebar.js:
import React, { Component } from 'react';
import SidebarItem from './SidebarItem'
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export default class Sidebar extends Component
{
handleClick(index)
{
//Dispatch action here maybe?
this.props.selectedSidebarItem(index);
console.log(this);
}
render()
{
var sidebarItemNames = ["Verify Original Contract", "Evaluate Transfer Terms", "Create Future Customer", "Check Credit", "Generate Agreement", "Finalize Transfer"];
return (
<div>
<div id="sidebar-title">
<div id="sc-logo">
LOGO
</div>
<div>Contract Transfer for:</div>
<div>XYZ</div>
<br />
</div>
<ul className="list-group" id="sidebar-list">
{sidebarItemNames.map(function(n, index)
{
return <SidebarItem key={index} index={index} selectedIndex={this.selectedSidebarItem} name={n} handleClick={this.handleClick(index).bind(this)} />;
})}
</ul>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return {
selectedSidebarItem: (index) => dispatch(ActionCreators.setSelectedSidebarItem(index))
}
}
const conn = connect(
null,
mapDispatchToProps
)(Sidebar)
SidebarItem.js:
import React, { Component } from 'react';
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export class SidebarItem extends Component {
constructor(props) {
super(props);
}
setSelectedSidebarItem() {
this.props.handleClick(this.props.index);
this.props.selectedSidebarItem(this.props.index);
// const ul = document.getElementById('sidebar-list');
// const items = ul.getElementsByTagName('li');
// for (let i = 0; i < items.length; ++i) {
// items[i].classList.remove('sidebar-item-current');
// }
}
render() {
return (
<li className={"list-group-item sidebar-list-item sidebar-item-todo" + (this.props.index==this.props.selectedIndex? ' sidebar-item-current':'') } onClick={this.setSelectedSidebarItem.bind(this)}><i className="fa fa-circle fa-lg"></i> <span>{this.props.name}</span></li>
)
}
}
Store.js:
import { createStore } from 'redux'
import reducers from './Reducers'
const store = createStore(reducers)
export default store
Reducers.js
const initialState = {
selectedSidebarItem: window.initialPageState,
otherStuff: 5
};
const reducers = (state = initialState, action) => {
switch (action.type) {
case "SET_SELECTED_SIDEBAR_ITEM":
console.log("clicked sidebar index: " + action.index);
var result = Object.assign({}, state, {
selectedSidebarItem: action.index
})
console.log(result);
return result;
default:
return state
}
}
export default reducers
actions.js:
import constants from './constants'
let ActionCreators = {
setSelectedSidebarItem(index) {
var actionObject = {
type: constants.UPDATE_SELECTED_SIDEBAR_ITEM,
index
}
console.log("setting sidebar item", actionObject);
return actionObject
}
}
export default ActionCreators
Constants.js
const constants = {
UPDATE_SELECTED_SIDEBAR_ITEM: "UPDATE_SELECTED_SIDEBAR_ITEM",
ADD_ERROR: "ADD_ERROR",
CLEAR_ERROR: "CLEAR_ERROR"
};
export default constants;
I've tried a few variations of the above and have previously been able to dispatch actions, but am unsure the store is ever updated and nothing is reflected on the UI. Right now I'm getting this error when clicking sidebar items: "Cannot read property 'handleClick' of undefined"
Thanks for any help in advance.
in your sidebar.js:
instead of
handleClick={() => this.handleClick(index).bind(this)}
try this:
handleClick={this.handleClick(index).bind(this)}
And in handleClick method you have to dispatch action:
this.props.selectedSidebarItem(index)
Answer update:
import React, { Component } from 'react';
import SidebarItem from './SidebarItem'
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export default class Sidebar extends Component
{
handleClick(index)
{
//Dispatch action here maybe?
this.props.selectedSidebarItem(index);
this.selectedSidebarItem = index;
console.log(this);
}
render()
{
var sidebarItemNames = ["Verify Original Contract", "Evaluate Transfer Terms", "Create Future Customer", "Check Credit", "Generate Agreement", "Finalize Transfer"];
return (
<div>
<div id="sidebar-title">
<div id="sc-logo">
LOGO
</div>
<div>Contract Transfer for:</div>
<div>XYZ</div>
<br />
</div>
<ul className="list-group" id="sidebar-list">
{sidebarItemNames.map(function(n, index)
{
return <SidebarItem key={index} index={index} selectedIndex={this.selectedSidebarItem} name={n} handleClick={this.handleClick(index).bind(this)} />;
})}
</ul>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return {
selectedSidebarItem: (index) => dispatch(ActionCreators.setSelectedSidebarItem(index))
}
}
const conn = connect(
null,
mapDispatchToProps
)(Sidebar)
Related
I've got a connected component that gets information from the Redux store and displays it onscreen. But whenever I try and dispatch an action to update the state, it doesn't end up doing anything:
AppSettings.js
import {
MINUTE_MS,
MINUTE_S,
} from '../constants'
import {
decrementSession,
incrementSession,
} from './timerSlice'
import React from 'react'
import { connect } from 'react-redux'
import equal from 'fast-deep-equal'
/* eslint-disable no-useless-constructor */
export class AppSettings extends React.Component {
constructor(props) {
super(props)
this.state = {
sessionLength: this.props.sessionLength,
}
}
componentDidUpdate(prevProps) {
if (!equal(this.props, prevProps)) {
this.setState((_, props) => {
return {
sessionLength: props.sessionLength,
};
})
}
}
render() {
let sessionLength = Math.floor(this.props.sessionLength / MINUTE_MS) % MINUTE_S
sessionLength = ('0' + sessionLength).slice(-2)
return (
<div>
<div>
<h3>
session
</h3>
<button
id='sessionUp'
onClick={this.props.incrementSession}
>
up
</button>
<h4>
{sessionLength}
</h4>
<button
id='sessionDown'
onClick={this.props.decrementSession}
>
down
</button>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
sessionLength: state['sessionLength'],
};
}
function mapDispatchToProps() {
return {
decrementSession,
incrementSession,
};
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(AppSettings)
timerSlice.js
import {
DEFAULT_SESSION,
MINUTE_MS,
} from '../constants'
import { createSlice } from '#reduxjs/toolkit'
export const timerSlice = createSlice({
name: 'timer',
initialState: {
sessionLength: DEFAULT_SESSION,
},
reducers: {
decrementSession(state) {
state['sessionLength'] -= MINUTE_MS
},
incrementSession(state) {
state['sessionLength'] += MINUTE_MS
},
}
})
export const {
decrementSession,
incrementSession,
} = timerSlice.actions
export default timerSlice.reducer
store.js
import { configureStore } from '#reduxjs/toolkit'
import reducer from '../features/timerSlice'
const store = configureStore({
reducer: reducer
})
export default store
Upon initial render, the component reads from the store just fine, and displays the appropriate value. Unit testing showed that the component updates the value rendered onscreen when it's passed new props. My unit test also shows that the appropriate functions are called whenever buttons are pressed. I ran my app in the browser, and it showed that the Redux store wasn't being updated at all.
How come my store isn't responding whenever I try and dispatch actions from my component?
Something seems to be wrong with mapDispatchToProps in the current setup. I got it working by converting it to a function component and using useDispatch to call the actions.
The local state is unnecessary. Just use the value selected from redux directly.
export const AppSettings = () => {
const dispatch = useDispatch();
const sessionLength = useSelector(state => state.sessionLength);
const seconds = Math.floor(sessionLength / MINUTE_MS) % MINUTE_S
const secondsString = ('0' + seconds).slice(-2)
return (
<div>
<div>
<h3>
session
</h3>
<button
id='sessionUp'
onClick={() => dispatch(incrementSession())}
>
up
</button>
<h4>
{secondsString}
</h4>
<button
id='sessionDown'
onClick={() => dispatch(decrementSession())}
>
down
</button>
</div>
</div>
);
}
Code Sandbox Link
I wanna know why will my state remain the same upon rerender of the components.
Here is my parent component
import React, {useEffect} from 'react';
import {connect} from "react-redux"
import NoteList from '../Components/NoteList';
import NoteDetail from '../Components/NoteDetail';
import "./NotePage.scss"
const NotePage = ({note}) => {
const selectNote = (item) => {
if (!item){
return <div className="emptySection"/>
}
console.log("return a new component")
return <NoteDetail/>
}
return (
<div className="NotePage">
<div className="noteList">
<NoteList/>
</div>
<div className="noteDetail">
{selectNote(note)}
</div>
</div>
)
}
const mapState = (state) => (
{
note: state.notesReducer.note
}
)
export default connect(mapState)(NotePage);
I have already checked that this component rerender when the note in redux store changed. The selectNote is executed proper as well. But does it return a brand new component of NoteDetail?
Here is my child component:
import React, {useState, useRef, useEffect} from 'react';
import {connect} from "react-redux";
import Editor from 'draft-js-plugins-editor';
import {EditorState, ContentState} from "draft-js";
import CreateInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import CustomInlineToolbar from "./CustomInlineToolbar";
import {updateNote} from "../redux/action"
import "./NoteDetail.scss";
import 'draft-js-inline-toolbar-plugin/lib/plugin.css';
const InlineToolbarPlugin = CreateInlineToolbarPlugin();
const {InlineToolbar} = InlineToolbarPlugin;
const NoteDetail = ({updateNote, title, note, date, _id}) => {
let initContentState = ContentState.createFromText(note);
let [editorState, setEditorState] = useState(EditorState.createWithContent(initContentState));
let [titleState, setTitleState] = useState(title)
let editorRef = useRef(React.createRef());
useEffect(()=>{
updateNote(_id, titleState, editorState)
},[editorState, titleState, _id])
const focus = () => {
editorRef.current.focus()
}
return(
<div>
<div className="NoteDetail-container">
<input type="text" id="title"
value={titleState === "Untitled Page"? "":titleState}
onChange={(e)=>setTitleState(e.target.value)}
/>
<div id="content" onClick={focus}>
<Editor
plugins={[InlineToolbarPlugin]}
onChange={(e)=>setEditorState(e)}
editorState={editorState}
ref={ele => editorRef.current = ele}
/>
<CustomInlineToolbar InlineToolbar={InlineToolbar} />
</div>
</div>
</div>
)
};
const mapProps = (state) => {
const {title, _id, note, date} = state.notesReducer.note
return {
title,
_id,
note,
date
}
}
export default connect (mapProps, {updateNote})(NoteDetail)
It rerenders when the note state changed, but the state remain the same. I checked that the title did change, but the titleState didn't. So how does state and component comparison behave in React?
my reducer:
import {
GET_NOTE,
UPDATE_NOTE,
DEL_NOTE,
CREATE_NOTE,
SELECT_NOTE,
GET_NOTES,
} from "../action/types";
import {
api_getNote,
api_delNote,
api_updateNote,
api_postNote
} from "../../api/noteService"
//TODOS: finish update_title part
const notesReducer = ( state={}, action) => {
switch (action.type){
case GET_NOTES:
return{notes: action.payload}
case UPDATE_NOTE:
const {id, content} = action.payload
api_updateNote(id,content)
return state
case DEL_NOTE:
api_delNote(action.payload)
return state
case CREATE_NOTE:
api_postNote(action.payload, (res)=> {
return {notes: res}
})
return state
case SELECT_NOTE:
return {...state, note: action.payload}
default:
return state
}
}
export default notesReducer
I want to edit one element from an array with react and redux.
My problem is I set once the state of array which I map. And in this map I try to change this element with reducer.
Is it possible?? I try to use Object.assing() to avoid mutate the state, BUT I must mutate the state. Isn`t it true?
Below the reducer:
import * as actionTypes from '../actions';
const iniState = {
components: []
};
const rootReducer = (state = iniState, action) => {
switch (action.type) {
case actionTypes.ADD_COMPONENT:
const newComponent = {
id: Math.random(),
co: action.data.compToReducer
}
return {
...state,
components: state.components.concat(newComponent)
};
case actionTypes.DELETE_COMPONENT:
return {
...state,
components: state.components.filter(component=> component.id !== action.index)
}
case actionTypes.EDIT_COMPONENT:
return
Object.assing({}, state, {co: state.co = action.data.componentToReducer})
}
return state;
}
export default rootReducer;
And the container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import FoodComponent from '../../components/FoodComponent/FoodComponent';
import AddComponent from '../../components/Buttons/AddComponent';
import * as actionTypes from '../../store/actions';
import classes from './FoodComponents.scss';
class FoodComponents extends Component {
render() {
return (
<div>
<AddComponent
text="Add component"
click={this.props.onAddComponent}
/>
<ul>
{
this.props.compons.map(component=>(
<li key={component.id}
>
<p className={classes.Component}>{component.co}</p>
<input
type="text"
/>
<button
onClick={this.props.onEditComponent}>
Edit Component
</button>
<button
onClick={()=>this.props.onDeleteComponent(component.id)}>
Delete component
</button>
</li>
))
}
</ul>
</div>
)
}
}
const mapStateToProps = state => {
return {
compons: state.components
}
}
const mapDispatchToProps = dispatch => {
return {
onAddComponent: (component) => dispatch({type: actionTypes.ADD_COMPONENT, data: {compToReducer: component}}),
onDeleteComponent: (id) => dispatch({type: actionTypes.DELETE_COMPONENT, index: id }),
onEditComponent: (component, id) => dispatch({type: actionTypes.EDIT_COMPONENT, data:{componentToReducer: component, index: id}})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(FoodComponents);
onEditComponent: (component, id) => dispatch({type: actionTypes.EDIT_COMPONENT, data:{componentToReducer: component, index: id}})
<button onClick={this.props.onEditComponent}>
Edit Component
</button>
This won't work as you try to pass SyntheticEvent to the reducer. Synthetic events get nullified after callback executes.
Fairly new to these technologies and am at wit's end. I've got two components; a parent which contains a form (using redux-form) and writes a new record to a database, and a child which lists some data.
The only thing I can't get to work is refreshing that child list when the form submit completes. If I refresh the page, the new data is visible. From what I had read, it was my understanding that by wiring up redux-form, that my state would refresh automatically...or something like that. Am I even going about this the right way? Here's everything...
My index reducer:
import { combineReducers } from 'redux';
import { reducer as formReducer } from "redux-form";
import ItemsReducer from "../reducers/items";
const rootReducer = combineReducers({
form: formReducer,
items: ItemsReducer
});
export default rootReducer;
My items reducer:
import { GET_ALL_ITEMS } from "../actions/items";
export default (state = {}, action) => {
switch (action.type) {
case GET_ALL_ITEMS:
return action.payload.data;
default:
return state;
}
}
My actions:
import axios from "axios";
export const GET_ALL_ITEMS = "GET_ALL_ITEMS";
export const SAVE_ITEM = "SAVE_ITEM";
const ROOT_API_URL = "http://myapi:3000/api";
export function getAllItems() {
let request = axios.get(`${ROOT_API_URL}/items`);
return {
type: GET_ALL_ITEMS,
payload: request
};
}
export function saveItem(item, callback) {
let request = axios
.post(`${ROOT_API_URL}/item`, item)
.then(() => callback());
return {
type: SAVE_ITEM,
payload: request
};
}
The (abbreviated) parent (list and form):
import ItemsList from "./items_list";
...
onSubmit = (item) => {
let { saveItem } = this.props;
saveItem(item, () => {
// this is successful
});
}
...
//the list in render()
<div>
<ItemsList />
</div>
...
//redux-form wired up at bottom
export default reduxForm({
form: "EditItemForm",
})(connect(null, { saveItem })(Items));
The child component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { getAllItems } from "../actions/items";
class Shows extends Component {
componentDidMount() {
this.props.getAllItems();
}
render() {
return(
<div className="body-content-partial">
{this.renderItems()}
</div>
);
}
renderItems() {
let { items } = this.props;
return items.map(item => {
return(
<a href="#" key={item.id}>
<div className="list-item-noavatar list-lines-div">
<div className="list-title">
{item.name}
</div>
<div className="status-div">
<span className="status-indicator"></span>
{item.active}
</div>
</div>
</a>
);
});
}
}
function mapStateToProps(state) {
return { items: state.items };
}
export default connect(mapStateToProps, { getAllItems })(Items);
OK, absolutely fixed it this time. I had to make a call to getAllItems() on the form submit as well as pass it into the dispatch portion of the connect() call, for the redux-form setup. Respectively:
import { saveItem, getAllItems } from "../actions/items";
...
onSubmit = (item) => {
let { saveItem, onSave, getAllItems } = this.props;
saveItem(item, () => {
onSave();
getAllItems();
});
}
...
export default reduxForm({
form: "ItemEditForm",
})(connect(null, { saveItem, getAllItems })(ItemEditForm));
I have just taken over a new reactjs project -- and I am trying to review how language switching has been invoked.
so like there are two links in the footer to do this language switch.
//footer.js
import React from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { selectLanguage, getLangDetails } from '../../actions/action_language'
import langObject from './Footer.lang'
class Footer extends React.Component {
constructor (props) {
super(props)
this.changeLanguageToGerman = this.changeLanguageToGerman.bind(this)
this.changeLanguageToEnglish = this.changeLanguageToEnglish.bind(this)
}
changeLanguageToGerman () {
this.props.selectLanguage('de')
}
changeLanguageToEnglish () {
this.props.selectLanguage('en')
}
render () {
let activeLang = 'language--active'
let alternativeLang = 'language--hover'
const lang = getLangDetails(this.props.active_language, langObject)
return (
<div>
<footer className='main-footer show-for-medium-only'>
<div className='medium-15 columns'>
<p className='text--white grid__row--offset--15 footer-text'>
<Link to={this.props.deURL} className={`text--white footer-text ${this.props.active_language === 'de' ? activeLang : alternativeLang}`} onClick={this.changeLanguageToGerman}>DE</Link>
|
<Link to={this.props.enURL} className={`text--white footer-text ${this.props.active_language === 'en' ? activeLang : alternativeLang}`} onClick={this.changeLanguageToEnglish}>EN</Link>
</p>
</div>
</footer>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
active_language: state.active_language
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({selectLanguage: selectLanguage}, dispatch)
}
const { string, func } = React.PropTypes
Footer.propTypes = {
deURL: string,
enURL: string,
selectLanguage: func,
active_language: string
}
export default connect(mapStateToProps, mapDispatchToProps)(Footer)
// header.js
import React from 'react'
import { connect } from 'react-redux'
import { getLangDetails } from '../../actions/action_language'
import langObject from './Header.lang'
class Header extends React.Component {
render () {
let transparent
transparent = this.props.transparent ? 'transparent' : ''
const lang = getLangDetails(this.props.active_language, langObject)
return (
<div>
<header className={` main_headerbar__landing transition show-for-large-up ${transparent} `}>
<div className='contain-to-grid'>
{lang}
</div>
</header>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
active_language: state.active_language
}
}
const { bool, string } = React.PropTypes
Header.propTypes = {
transparent: bool,
active_language: string
}
export default connect(mapStateToProps)(Header)
--- so these are the header/footer components - and each has a json file that splits into an array of lang.
there is a file that looks like some global js that I think hooks into this - but I am struggling to extend this functionality into the rest of the site components/pages
//action_language.js
export const LANGUAGE_SELECTED = 'LANGUAGE_SELECTED'
export function selectLanguage (language) {
return {
type: LANGUAGE_SELECTED,
payload: language
}
}
export function getLangDetails (language = 'de', langObject) {
const langData = langObject.langs.filter((langVar) => langVar.lang === language)
return langData['0'].lines
}
ok - so here is the first page -- called services. Now what throws me first here is rather than use active_language its now just language.
//services.js
import React from 'react'
import Header from '../HeaderLanding/Header'
import Footer from '../Footer/Footer'
import NoBundle from './NoBundle'
import HowTiles from './HowTiles'
import CarouselTiles from './CarouselTiles'
import YourAdvantages from './YourAdvantages'
import InformationTiles from './InformationTiles'
import ContactTiles from './ContactTiles'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { selectLanguage, getLangDetails } from '../../actions/action_language'
// language file
import langObject from './services.lang.json'
// services css
import './services.css'
// getting the distinct URLs from the lang files
const deURL = langObject.langs[0].pageURL
const enURL = langObject.langs[1].pageURL
const Spacers = () => (
<div>
<div className='row show-for-large-up' style={{ height: '250px' }} />
<div className='row show-for-medium-only' style={{ height: '150px' }} />
<div className='row show-for-small-only' style={{ height: '80px' }} />
</div>
)
class Services extends React.Component {
constructor (props) {
super(props)
this.language = props.match.params.langURL
}
componentWillMount () {
document.getElementById('body').className = 'overlay-background-services'
this.updateLanguage()
}
updateLanguage () {
console.log('updatelang', this.language)
if (this.language === 'de' || !this.language) {
this.props.selectLanguage('de')
} else {
this.props.selectLanguage('en')
}
}
componentWillUnmount () {
document.getElementById('body').className = ''
}
render () {
const lang = getLangDetails(this.language, langObject)
return (
<div>
<Header transparent />
<Spacers />
<NoBundle lang={lang} />
<HowTiles />
<CarouselTiles />
<YourAdvantages />
<InformationTiles />
<ContactTiles />
<Footer deURL={deURL} enURL={enURL} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
language: state.language
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({selectLanguage: selectLanguage}, dispatch)
}
const { func, string, object } = React.PropTypes
Services.propTypes = {
selectLanguage: func,
langURL: string,
params: object,
match: object
}
export default connect(mapStateToProps, mapDispatchToProps)(Services)
The Footer component deals with setting the current language by invoking the Redux action creator selectLanguage. Essentially this dispatches an action (you can think of this as a custom event with some corresponding data - the language) to the store that will persist the user's language selection for use elsewhere.
In order to consume the language in other components, that language selection needs to be passed into the component (in this case the Header) from the Redux store.
This is the code of interest in header that does that...
const mapStateToProps = (state) => {
return {
active_language: state.active_language
}
}
export default connect(mapStateToProps)(Header)
Here you are connecting the Header to the store, with a function that describes how the store should map values into props on your react component. state.active_language is where the language that the user has selected is stored, and this is telling it to be passed as a prop called active_language on your Header component
The connect function is a decorator that will create what's know as a Higher Order Component (HOC) which is essentially a component with props or functionality automatically injected into it (decorated in this case with an automatically passed value for the active_language prop from the store)
You can do the same for any other component that need this language setting, or go a step or two further
Instead of passing the active language name, pass the corresponding language itself...
import { getLangDetails } from '../../actions/action_language'
import langObject from './Header.lang'
const mapStateToProps = (state) => {
return {
active_language: getLangDetails(state.active_language, langObject)
}
}
export default connect(mapStateToProps)(Header)
OR better yet write another HOC that wraps any component you pass with this info...
import { getLangDetails } from '../../actions/action_language'
export default const injectLanguage = (component, langObject) => connect((state) => ({
language: getLangDetails(state.active_language, langObject)
})
)(component)
Then in subsequent components with a language prop, decorate with this
import injectLanguage from './some/where'
import langObject from './MyLanguageDetailsAwareComponent.lang'
class MyLanguageDetailsAwareComponent extends React.Component {
render() {
return this.props.language
}
}
export default injectLanguage(MyLanguageDetailsAwareComponent, langObject)