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: {}
Related
I'm trying to create a menu dynamically based on the user location in react using redux.
The makeMenu action has the following initialState in its reducer: (The whole reducer code is at the end of the post)
const initialState = {
current_url: url,
menu_urls: urls.filter(item => item !== url)
}
It works fine after use it and apply mapStateToProps function in the component:
const mapStateToProps = (state) => ({
state: state.makeMenu
})
So in the component I have accessed to its value using this.props.state.makeMenu, it works fine, as well and I get the following result after console.log(urls):
{current_url: "", menu_urls: Array(4)}
current_url: ""
menu_urls: Array(4)
0: "edu"
1: "projects"
2: "skills"
3: "honors"
length: 4
__proto__: Array(0)
__proto__: Object
The problem is when I want to map the menu_urls to create sub-menu (Menu.Item in code):
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Breadcrumb, Menu } from 'antd';
import { HomeOutlined } from '#ant-design/icons'
import { makeMenu } from '../../redux/actions'
import './navigation-ls.css';
class NavigationLS extends Component {
render() {
const urls = this.props.state.makeMenu
const menu = (
<Menu>
{console.log(urls)}
{
urls.menu_urls.map((url) =>
<Menu.Item>
<Link to={"/" + url}>
{url}
</Link>
</Menu.Item>
)
}
</Menu>
);
return (
<Fragment>
{console.log('hey')}
{console.log(this.props.state)}
<div className="nav">
<div className="pwd">
$PWD
</div>
<div className="breadcrumb">
<Breadcrumb>
<Breadcrumb.Item>
<HomeOutlined/>
</Breadcrumb.Item>
<Breadcrumb.Item overlay={menu}>
<Link to="/">
info
</Link>
</Breadcrumb.Item>
</Breadcrumb>
</div>
</div>
</Fragment>
)
}
}
const mapStateToProps = (state) => ({
state: state.makeMenu
})
export default connect(
mapStateToProps,
{ makeMenu }
)(NavigationLS)
As I said console.log(urls) works fine but the urls.menu_urls doesn't work. I have read the similar question on stackoverflow but none of them helps.
EDIT:
makeMenu reducer:
import { MAKE_MENU } from '../actionTypes'
const urls = [
"",
"edu",
"projects",
"skills",
"honors",
]
const url = window.location.href.split("/")[3]
const initialState = {
current_url: url,
menu_urls: urls.filter(item => item !== url)
}
const makeMenu = (state = initialState, action) => {
switch(action.type) {
case MAKE_MENU: {
const { content } = action.payload
return {
current_url: content,
menu_urls: urls.filter(item => item !== content)
}
}
default:
return state
}
}
export default makeMenu
You are setting urls to this.props.state.makeMenu while it is undefined. You should set urls to this.props.state.
Pretty new to Redux. I'm trying to pass a handleClick event as a prop from a container component to a presentational component, the handleClick event is supposed to call upon an action which has been received as a prop with mapDispatchToProps.
Could someone tell me how to do this correctly please?
I'm building a calculator, just started, this only has three actions so far, add, Record_input_1 and Record_Input_2.
containers/ButtonsContainer.js:
import React, { Component } from 'react';
import { Buttons } from '../components/Buttons'
import { Record_Input_1 } from '../actions/sum-action';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class ButtonsContainer extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(num) {
return this.props.onRecordInput1(num)
}
render() {
return(
<Buttons handleClick={this.handleClick} />
)
}
mapStateToProps = (state) => {
return {
inputValue1: state.inputValue1,
inputValue2: state.inputValue2,
answer: state.answer
}
}
mapDispatchToProps = (dispatch) => {
return bindActionCreators({
onRecordInput1: Record_Input_1,
onRecordInput2: Record_Input_2
}, dispatch);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ButtonsContainer);
components/Buttons.js
import React, { Component } from 'react';
class Buttons extends Component {
render() {
const buttonMaker = (buttons, row) => {
for (let value of buttons) {
row.push(<button onClick={() => this.props.handleClick(value)} key={value}>
{value}
</button> )
}
}
let row1 = [];
let buttons1 = [1,2,3]
buttonMaker(buttons1, row1)
let row2 = [];
let buttons2 = [4,5,6]
buttonMaker(buttons2, row2)
let row3 = [];
let buttons3 = [7,8,9]
buttonMaker(buttons3, row3)
return (
<div>
<div>{row1}</div>
<br />
<div>{row2}</div>
<br />
<div>{row3}</div>
</div>
)
}
}
export default Buttons;
actions/sum-actions/js:
export const ADD = 'ADD';
export const RECORD_INPUT_1 = 'RECORD_INPUT_1';
export const RECORD_INPUT_2 = 'RECORD_INPUT_2';
export const add = (newInput1, newInput2) => {
return {
type: ADD,
newAnswer: newInput1 + newInput2
}
}
export const Record_Input_1 = (newInput1) => {
return {
type: RECORD_INPUT_1,
newInput1
}
}
export const Record_Input_2 = (newInput2) => {
return {
type: RECORD_INPUT_2,
newInput2
}
}
reducders/sum-reducer.js:
import { ADD, RECORD_INPUT_1, RECORD_INPUT_2 } from '../actions/sum-action'
export const initialState = {
inputValue1: '',
inputValue2: '',
answer: 0
}
export const sumReducer = (state = initialState, action) => {
switch (action.type) {
case ADD:
return [
...state,
{
answer: action.newAnswer
}
]
case RECORD_INPUT_1:
return [
...state,
{
inputValue1: action.newInput1
}
]
case RECORD_INPUT_2:
return [
...state,
{
inputValue2: action.newInput2
}
]
default:
return state;
}
}
store.js:
import { combineReducers, createStore } from 'redux';
import { initialState, sumReducer } from './reducers/sum-reducer';
const rootReducers = combineReducers({
sumReducer
})
export default createStore(rootReducers, initialState, window.devToolsExtension && window.devToolsExtension());
The buttons display ok, when I click on one I get this error:
TypeError: _this2.props.handleClick is not a function
for:
8 | render() {
9 | const buttonMaker = (buttons, row) => {
10 | for (let value of buttons) {
> 11 | row.push(<button onClick={() => this.props.handleClick(value)} key={value}
12 | {value}
13 | </button> )
14 | }
You are declaring mapStateToProps and mapDispatchToProps within ButtonsContainer. You are then passing those two methods to react-redux's connect as if they were declared outside of ButtonsContainer, hence they are undefined. Try moving them out of ButtonsContainer as shown here. It should look something like this:
class ButtonsContainer extends Component {
...
}
const mapStateToProps = (state) => {
return {
inputValue1: state.inputValue1,
inputValue2: state.inputValue2,
answer: state.answer
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
onRecordInput1: Record_Input_1,
onRecordInput2: Record_Input_2
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ButtonsContainer);
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'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('/');
},