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.
Related
I am trying to update my redux state from a child component(QResults.js) by calling a function that I pass to it but my reducer isn't being reached when I use the function. QResults.js has a link that I am clicking which I expect to alter my state via one of my reducers. Am I doing something wrong with my mapDispatchToProps() function?
Channel.js
class Channel extends Component {
...
render() {
return (
...
<div>
<QResults
allQueryResults={this.state.queryState}
requestHandler={queueNewRequest}/>
</div>
);
}
}
function mapStateToProps(state) {
...
}
function mapDispatchToProps(dispatch) {
return ({
queueNewRequest: (newRequestData) => { dispatch(queueNewRequest(newRequestData)) }
})
}
export default withRouter(connect(mapStateToProps , mapDispatchToProps )(Channel))
QResults.js
export default class QResults extends Component {
render() {
const {requestHandler} = this.props
return (
<ul>
{this.props.allQueryResults.items.map((trackAlbum, i) =>
<li key={i}>
<a href='#'
onClick={
() => requestHandler(trackAlbum.name)}>
Some link
</a>
</li>
)}
</ul>
)
}
}
Reducers.js
import { combineReducers } from 'redux'
function reducer1(state = {}, action) {
...
}
function reducer2(state = {}, action) {
switch (action.type) {
case QUEUE_NEW_REQUEST:
return{
...state,
newRequestInfo : action.newRequestInfo
}
default:
return state
}
}
const rootReducer = combineReducers({
reducer1,
reducer2
})
export default rootReducer
Actions.js
export const QUEUE_NEW_REQUEST = 'QUEUE_NEW_REQUEST'
export function queueNewRequest(newRequestInfo) {
return dispatch => {
return {
type: QUEUE_NEW_REQUEST,
newRequestInfo
}
}
}
Your action doesn't dispatch the action to the reducer. You just passed it in as an argument. I also slightly updated the pass of the param to a key called "payload". Try updating it like this
I've created a minimal sandbox here
If you click on one of the items and check your console you can see the reducer is being called.
export const QUEUE_NEW_REQUEST = "QUEUE_NEW_REQUEST";
export function queueNewRequest(newRequestInfo) {
return dispatch =>
dispatch({
type: QUEUE_NEW_REQUEST,
payload: newRequestInfo
});
}
I have an app using redux that currently has the reducer file set as rootReducer but I need to expand it so I want to use combineReducers but I can't get it to work. Here is the code to my rootReducer which works fine in the app
//Root Reducer
export default function rootReducer(state = {
filtered: [],
}, action) {
switch (action.type) {
case "SEARCH_IMAGES":
let filtered = action.payload
return {...state, images: action.payload, filtered, isFetching: false}
default:
return state
}
}
So I created a new reducer called filtered
export default function (state = {
filtered: []}, action) {
switch (action.type) {
case "SEARCH_IMAGES":
let filtered = this.state.action.payload
return {
...state, images: action.payload, filtered, isFetching: false
};
default:
return state
}
}
set up a comibined reducers index file to import the new reducer
import { combineReducers } from 'redux';
import comments from './comments';
import filtered from './filtered';
import images from './images';
export default combineReducers({
filtered, images
})
import it into my index and add it to my store and provider.
The issue seems to be with state = {
filtered: []
parameter. How do I get that to work in a new reducer using combined reducers. How do I add the empty array for filtered: []
this is the error message I am receiving
Here is the revised ImageContainer.js
import React from 'react'
import ImageCard from './ImageCard'
import ContentLoader from 'react-content-loader'
class ImageContainer extends React.Component {
render() {
const { filtered } = this.props;
const allImages = this.props.filtered.filtered.map((pic) => {
return <ImageCard key={pic.id} pic={pic} url={pic.images[0]["link"]} />
})
return (
<div className="image-wrapper">
{allImages}
<div className="note">
{allImages.length === 0 ? "No memes are set for this query tag": ""}
</div>
</div>
)
}
}
export default ImageContainer
Portal Page
import React from 'react'
import { Label } from 'semantic-ui-react'
import * as IchingTable from '../../constants/lookup.js';
import { HexagramImage } from '../HexagramImage.js';
import * as pictureActions from '../../actions/pictures'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import ImageContainer from './ImageContainer.js';
import ReactDOM from 'react-dom'
import classnames from 'classnames';
import { Segment } from 'semantic-ui-react'
import { NavLink, withRouter} from 'react-router-dom';
class PortalPage extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
selectedTabId: 1,
intervalId: null
}
}
componentDidMount() {
let query = this.props.hexagram.tags[0].label
this.props.searchImages(query)
}
detailsback(hex) {
this.props.history.push( `/details/${hex.number}/${hex.name}` );
this.props;
console.log("this is the bar hex");
}
labelClick = (label, event, selectedTabId, id) => {
event.preventDefault();
let query = event.target.innerText;
const { searchImages } = this.props
searchImages(query);
this.setState({ selectedTabId : label.id });
}
render() {
let hexNumber = Number( this.props.match.params.number );
let hex = IchingTable.getHexagram( hexNumber );
let {trigrams, name, number, description, tags, selectedTabId} = this.props.hexagram;
let searchtags = (tags).map( (tag, label, id, index) => {
let initActive = (match, location) => {
if (!match) {
return false
}
let selectedTabId = parseInt(match.selectedTabId)
return this.state.selectedTabId === tag.id;
}
const params = new URLSearchParams(this.props)
return (
<div className="labeltags" key={label} >
<Label
onClickCapture={this.labelClick.bind(null, tag)}
as={NavLink}
to="/"
activeClassName="slabel--active"
basic size={'large'}
value={tag.id}
key={label}
isActive={initActive}
>
{tag.label}
</Label>
</div>
);
})
return (
<div>
<Segment raised>
<Label
as='a'
onClick={this.detailsback.bind(this, hex)}
onTouchTap={this.detailsback.bind(this, hex)}
ribbon='right'
color='orange'
>
← Back
</Label>
<div className="hexagram-card">
<HexagramImage below={trigrams.below} above={trigrams.above} />
<div className="title">
<h3>{number}: {name}</h3>
<h2>{description}</h2>
</div>
</div>
</Segment>
<div>
<p>Click on search terms for </p><h4>{name} - {description} </h4>
{searchtags}
</div>
<div>
<ImageContainer filtered={this.props.filtered} />
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
filtered: state.filtered
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(pictureActions, dispatch)
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PortalPage))
Can you please share the error you are getting?
Looking at your code, I see one bug (below is the corrected code):
export default function (state = {
filtered: []}, action) {
switch (action.type) {
case "SEARCH_IMAGES":
// calling this.state.action.payload is incorrect. "action" variable contains the payload
let filtered = action.payload // Old Code - this.state.action.payload
return {
...state, images: action.payload, filtered, isFetching: false
};
default:
return state
}
}
Your question is quite unclear. But what I could figure out is you
want to know how t work with combine reducers
This is how we work with combine reducers
reducer1.js
const somedefault = [];
export default function (state = somedefault, action) {
switch (action.type) {
case "SEARCH_IMAGES":
let filtered = action.payload
return {
...state, images: action.payload, filtered, isFetching: false
};
default:
return state
}
}
reducer2.js
const somedefault = {};
export default function (state = somedefault, action) {
switch (action.type) {
default:
return state
}
}
combineReducer.js
import { combineReducers } from 'redux';
import Red1 from './reducer1.js';
import Red2 from './reducer2.js';
export default combineReducers({
red1: Red1,
red2: Red2
})
So when you see your redux state then it will look like
state
|___red1: []
|___red2: {}
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'm using Redux to build a web cart. My cart is working except when I delete an item on the cart, the page needs to refresh or change for the changes to be rendered. How can I display the changes as the item is removed? This is my cart component:
import React, { Component } from 'react';
import {addCart} from './Shop';
import { removeCart } from '../../actions';
import { connect } from 'react-redux';
export class Cart extends Component {
constructor(props) {
super(props);
this.state = {items: this.props.cart,cart: [],total: 0};
}
handleClick(item) {
this.props.onCartRemove(item);
}
...
render() {
return(
<div className= "Webcart" id="Webcart">
<div id='WebcartWrapper'>
<ul id='webCartList'>
{this.state.items.map((item, index) => {
return <li className='cartItems' key={'cartItems_'+index}>
<h4>{item.item}</h4>
<p>Size: {item.size}</p>
<p>Price: {item.price}</p>
<button onClick={() => this.handleClick(item)}>Remove</button>
</li>
})}
</ul>
<div>Total: ${this.countTotal()}</div>
</div>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
onCartRemove: (item) => {
dispatch(removeCart(item));
},
}
}
function mapStateToProps(state) {
return { cart: state.cart };
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
These are my actions:
export const ADD_CART = 'ADD_CART';
export const REMOVE_CART = 'REMOVE_CART';
export function addCart(item){
return {
type: ADD_CART,
payload: item
}
};
export function removeCart(item){
return{
type:REMOVE_CART,
payload: item
}
};
These are my reducers:
import {ADD_CART} from './actions';
import {REMOVE_CART} from './actions';
import { REHYDRATE } from 'redux-persist/constants';
export default Reducer;
var initialState = {
cart:{},
data: [],
url: "/api/comments",
pollInterval: 2000
};
function Reducer(state = initialState, action){
switch(action.type){
case REHYDRATE:
if (action.payload && action.payload.cart) {
return { ...state, ...action.payload.cart };
}
return state;
case ADD_CART:
return {
...state,
cart: [...state.cart, action.payload]
}
case REMOVE_CART:
return {
...state,
cart: state.cart.filter((item) => action.payload !== item)
}
default:
return state;
};
}
If more of my code is needed please ask. How can I have the web cart list being rendered to update automatically when an item is removed?
you need to update your state to make it render again..
in cart component, just add function
...
componentWillReceiveProps(nextprops)
{
this.setState({
items: nextprops.cart
})
}
...
*) componentWillReceiveProps will call after exec dispatch() in handleClick,will return news data from reducers to this.props of cart component.
in your code for example :
export class Cart extends Component {
constructor(props) {
...
}
componentWillReceiveProps(nextprops)
{
this.setState({
items: nextprops.cart
})
}
handleClick(item) {
...
}
render() {
...
}
}
After dispatching the removeCart action you can trigger a url change like this. If you have configured the router properly, it should work.
onCartRemove: (item) => {
dispatch(removeCart(item));
this.props.history.push('/');
},
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)