Render array of objects from Redux store as React components - javascript

I'm trying to display my initial state from my store. I know my code is not correct but i'm hoping to learn the most simple method for this. Can I simply receive data from the store with props? or do I need some lifecycle event to target data in the store?
Here is my current attempt: I have edited this to include my reducer and I have updated my component as per the comments below.
//store .js
import { createStore, applyMiddleware,compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers'; //the index.js file
const initialState = {
}
const middleware = [thunk]
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__&& window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
Now when trying to map out my props in the below component, I get the error:
this.props.properties.map is not a function
//Properties component that renders a map of my props
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {getListings} from '../actions/postActions';
class Properties extends Component {
componentDidMount(){
getListings()
}
render() {
const properties = this.props.properties.map(property => (
<div key = {property.id}>
<h3>{property.propertyName}</h3>
<div style={{width: '4em', height:'4em', backgroundColor: 'blue'}}>image</div>
<p>{property.footage}</p>
<p>{property.address}</p>
<p>{property.price}</p>
</div>
))
return (
<div>
<h1>Current Listings</h1>
{properties}
</div>
)
}
}
Properties.propTypes = {
properties: PropTypes.array.isRequired,
newListing: PropTypes.object
}
const mapStateToProps = state =>({
properties: state.properties.properties,
newListing: state.properties.newListing
});
export default connect(mapStateToProps)(Properties)
//my actionsHandler.js
import {GET_LISTINGS, NEW_LISTING} from './types';
export function newProperty(formdata){
return function(dispatch){
dispatch({
type: NEW_LISTING,
payload: formdata
})
}
}
export function getListings(form){
return function(dispatch){
dispatch({
type: GET_LISTINGS,
})
}
}
//my reducer
import {NEW_LISTING, GET_LISTINGS} from '../actions/types';
const initialState={
properties: [
{
id: 1,
propertyName: 'The boogaloo',
footage: '3500 sqft',
address: '123 hill bounty rd',
price:'$ 350,000.00'
}
],
newListing: {}
}
export default function(state=initialState, action){
switch(action.type){
case NEW_LISTING:
return{
...state,
properties:state.properties.push(action.payload)
}
case GET_LISTINGS:
return{
state
}
default:
return state
}
}

use a lifecycle method for updating the component
like shouldComponentUpdate method,
props change Doesn't cause rerender only state change cause rerender,
is such case store the props as state is also a solution (inappropriate)

Your general approach is viable in general, with certain corrections required though:
your state is expected to be an object (not an array as it is currently)
in order for your entire array of objects to appear as a bunch of React elements, you may want to wrap them into some parent component and map() your individual posts as small components that receive corresponding object as props from parent component
You may inquiry the following live-demo as a reference:
//dependencies
const { render } = ReactDOM,
{ createStore } = Redux,
{ connect, Provider } = ReactRedux
//initial state, doomy reducer and basic store (no middleware)
const initialState = {posts:[{id:'0',propertyName:'house',footage:'1800 sqft',address:'108 boogaloo dr. thisplace, CA',price:'$145,300.00'},{id:'1',propertyName:'closet',footage:'110 sqft',address:'23 dirt dr. badplace, NC',price:'$3'},{id:'2',propertyName:'garage',footage:'1000 sqft',address:'10 meadow st. decent, NY',price:'$100,000.00'},{id:'3',propertyName:'fishing shack',footage:'500 sqft',address:'108 fishy dr. forestgrove, NJ',price:'$200,300.00'},{id:'4',propertyName:'huge mansion',footage:'8000 sqft',address:'32 T-Pain st. onlytwentythree, WI',price:'$100.00'},{id:'5',propertyName:'dog house',footage:'200 sqft',address:'2367 goodboy dr. borks, KA',price:'$1000.00'},{id:'6',propertyName:'beehive',footage:'too big to measure',address:'108 stingya dr. Bidibi, NJ',price:'$300.00'},{id:'7',propertyName:'my house',footage:'4000 sqft',address:'42 treeface dr. bigwoods, FL',price:'$190,300.00'},]},
appReducer = (state=initialState, action) => state,
store = createStore(appReducer)
//post ui component
const Post = ({propertyName, footage, address, price}) => (
<div>
<h3>{propertyName}</h3>
<p>footage: {footage}</p>
<p>address: {address}</p>
<p>price: {price}</p>
</div>
)
//wrapper ui component relying on storedPosts property
//that will get connected to global state posts later on
const PostBoard = ({storedPosts}) => (
<div>
{storedPosts.map((post, key) => <Post key={key} {...post} />)}
</div>
)
//connect storedPosts to global state posts
const mapStateToProps = ({posts}) => ({storedPosts: posts}),
PostBoardComponent = connect(mapStateToProps)(PostBoard)
//render Redux Provider
render (
<Provider store={store}>
<PostBoardComponent />
</Provider>,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.1.3/react-redux.min.js"></script><div id="root"></div>

In redux, you will be defining an initial state in reducer and will do some updation or removal of the data from the state using actions and will store i.e. you are updating the object the initial state object will be updated..In few cases you need the initial state directly. So,you need to make a copy of initial state object and perform the tasks on it. When the new object updated the initial state will not have any effect.
You can update using components by directly dispatching an action from the component i.e. mapdispatchtoProps.
You can access the entire store data in component by connecting it i.e. connect(mapStatetoProps, mapDispacthtoProps).
mapSatettoProps will give you store data as state. You can modify it or you can do whatever you want. state access can be done in component also but you will get updated one.

Related

How to call a method defined in others components

Assuming we have two functional components App and Product. From the Product component you can easily call a method defined in the parent component (App) by simply passing the method as props (methodA={methodA}). Can someone please tell me how to call a method defined in the Cart component from the Product component?
//App.js
import React from "react";
import Cart from "./Cart";
import Order from "./Order";
import Product from "./Product";
const App = props => {
const { order } = props;
const methodA = props => {
console.log(methodA);
}
return (
<div className="container">
<Cart>
{order.map((products, i) => {
return (
<Order key={i}>
{products.map((items, i) => (
<Product item={items} key={i} methodA={methodA} />
))}
</Order>
);
})}
</Cart>
</div>
);
};
export default App;
//Product.js
import React from 'react';
const Product = props => {
return <div className="delete" onClick={props.methodA}></div>;
};
export default Product;
That's why Redux is developed for easier state management. You should have Redux store and actions dispatched within this store. Once you have set up your redux store and actions, you can easily dispatch the same action from multiple components.
You have a few options:
Pass the products to the cart and let it render them (the best way in this case)
Use some state management like context or redux (in this case, an context passed by the cart component would be perfect, then the product component consume and use it functions)

Render functional component that is reliant on useState, on condition

I am new to react, hooks and functional components.
I have a Dashboard component wrapped in a context provider Store, when Store loads it establishes a socket connection, the server then sends the required DB (allChats) data to the client, which is loaded by a reducer dispatch.
In Dashboard i get the keys (topics) from allChats, i have a list that's populated by a key (activeTopic) value in topics.
activeTopic is hooked with useState, initial value is supposed to be the first key entry in topics.
Problem is Dashboard renders before the client recieves the DB data, i need to render Dashboard either after the DB payload is recieved or render a placeholder until allChats is updated in the Context provider and re-render is triggered.
I can't conditionally set the useState hook, and when i try to return render contents conditionally it throws undefined errors because the variable assignments are happening after the render is triggered.
App.js
import Dashboard from "./Dashboard";
import Store from './Store'
function App() {
return (
<div className="App">
<Store>
<Dashboard />
</Store>
</div>
);
}
export default App;
Store.js
let socket;
function reducer(state, action) {
switch(action.type) {
//Replace state with DB payload
case 'LOAD_MESSAGES':
return action.payload
default:
return state
}
}
export default function Store(props) {
//Hook reducer for DB data
const [allChats, dispatch] = React.useReducer(reducer, {});
if (!socket) {
socket = io(':3001');
//listen for DB data from server upon connection
socket.on('initialize', function(msg) {
dispatch({type: 'LOAD_MESSAGES', payload: msg})
})
}
return (
<CTX.Provider value={{allChats}}>
{props.children}
</CTX.Provider>
)
}
Dashboard.js
export default function Dashboard() {
const classes = useStyles();
//Import DB data
const {allChats} = React.useContext(CTX);
//Get keys from DB data
const topics = Object.keys(allChats);
//Hook activeTopic, initialise with the first key entry
const [activeTopic, changeActiveTopic] = React.useState(topics[0]);
return (
<div>
<div>
{
//populate list with messages from activeTopic key of DB data
allChats[activeTopic].map((chat, i) => (
<div key={i}>
<Chip label={chat.from} className={classes.chip}/>
<Typography variant='h5' gutterBottom>{chat.msg}</Typography>
</div>))
}
</div>
</div>
)
}
I am obviously missing the key concepts of the react, context and hook lifecycle. I don't want to hack out a solution i want to be able to handle it the proper way.
I would really appreciate the help, thank you.
I would add a isLoaded flag to the allChats reducer. Then, you can use that in the dashboard to show <div>loading</div> or the loaded chat view above.

How to update redux state using a react variable passed up to the component from a child

Im trying to update my Redux state in a component using a variable passed up to that component from a child, following a form submital callback. The form submits a users comment, which i want to store in my redux state. I'm unsure how to send this variable into the redux chain so i can use it in my action creator. I want to pass the newComment variable inside handleCommentSubmit into the this.props.getVideoComments() action creator. Here is the code:
CommentSection.js (where i want to update my state)
//redux stuff
import {connect} from 'react-redux'
import {getVideoComments} from '../actions'
class CommentsSection extends React.Component{
constructor(props){
super(props)
//this.state={comments:[], loading:false}
}
componentDidMount(){
this.props.getVideoComments()
}
handleCommentSubmit = (newComment) =>{
// call action creator to dist action to all reducers and update relevant states
this.props.getVideoComments()
//this.setState({comments: [...this.state.comments, newComment]})
//this.setState({comments: newComments},console.log('The current state is now',this.state.comments));
//comment is object with author and message. Add new comment to old comments
//this.setState({comments:[...this.state.comments,newComment]},console.log(this.state, 'state updated'))
}
//Comments are create in comment form, passed up then sent down through commentList to individual comment rendering inside comment.js
// comment form oncommentsubmit running everytime it renders, not only on submital
render(){
const {comments} = this.props
console.log({comments})
return(
<div>
<span><h4> Comments </h4></span>
<div className="ui grid">
<div className = "right floated eight wide column" >
<CommentList comments={comments}/>
</div>
<div className="left floated eight wide column">
<CommentForm onCommentSubmit={this.handleCommentSubmit}/>
</div>
</div>
</div>
)
}
}
//redux stuff
//called following state update
const mapStateToProps = (state) => {
return {comments:state.videoComments}
}
export default connect(mapStateToProps,{getVideoComments:getVideoComments})(CommentsSection)
index.js (for action creators)
import React from 'react'
export const getVideoComments= ()=> {
return (dispatch, getState)=> {
const videoComments = getState().videoComments
return ({
type: 'GET_VIDEO_COMMENTS',
payload: videoComments
})
}
}
videoCommentsReducer.js
import React from 'react'
const videoCommentsReducer=function(state= [], action){ // reads in previous state
switch (action.type){
case 'GET_VIDEO_COMMENTS':
return action.payload //reducer will update state to be payload.videoComments. Action only describes what happened
// reducer describes how what happened effects state. Could also use previous state and action to create new data
default:
return state
}
}
export default videoCommentsReducer
index.js (in reducer folder where they are combined)
import React from 'react'
import {combineReducers} from 'redux'
import videoCommentsReducer from './videoCommentsReducer'
export default combineReducers({
videoComments:videoCommentsReducer
})
From your action creator file, it seems that you are using the redux-thunk middleware, so make sure to import this library and apply it in the store. This codesandbox shows a complete example based on yours.
When using this thunk, make sure to always use the dispatch that it provides in order to send the action to the store. Don't return an object from the bound action creator:
export const getVideoComments = () => {
return (dispatch, getState) => {
const videoComments = getRandomComments();
dispatch({
type: "GET_VIDEO_COMMENTS",
payload: videoComments
});
};
};
Also, it doesn't make sense to use getState here to get the video comments. You would simply update the store with the same state over and over again. getState is useful when you want to interact with a different part of the state, that is outside the reducer that captures your action type.
Use mapDispatchToProps in your CommentSection.js and there's no need to use getState in your action creator.
Action Creator
const getVideoComments = (comments) => ({
type: 'GET_VIDEO_COMMENTS',
payload: comments,
});
CommentSection.js
// handleCommentSubmit
handleCommentSubmit = (newComment) => {
this.props.getVideoComments(newComment); //pass comment to action then access newComment in reducer then add it to your state
}
mapDispatchToProps = (state) => {
getVideoComments: (newComment) => dispatch(getVideoComments(newComment)),
}
export default connect(mapStateToProps, mapDispatchToProps)(CommentsSection);
Reducer.js
case 'GET_VIDEO_COMMENTS':
return [...state, action.payload];
Thought id post my solution as an answer as it combined parts of both answers, but needed bits of both to fully solve the issue.
By combining parts of both the above answers I was able to fully solve the problem. I removed getState() as both fctmolina and Rodrigo Amaral suggested. I also simplified the action creator to returning a javascript object, rather than a function, and so no longer needed to include a dispatch function, or use redux-thunk. I passed the newComment variable into my action creator, and then combined it with my old state inside the reducer. The solution only required a simply definition of the mapDispatchToProps as a JS object containing the action creator getVideoComments, which made it available as a prop to commentSection, and resulted in the action creator being dispatched when the this.props.getVideoComments() function call was made. Here is the altered code:
CommentSection.js
import React from 'react'
import CommentList from './CommentList'
import CommentForm from './CommentForm'
//redux stuff
import {connect} from 'react-redux'
import {getVideoComments} from '../actions'
class CommentsSection extends React.Component{
constructor(props){
super(props)
//this.state={comments:[], loading:false}
}
componentDidMount(){
console.log(this.props.comments)
}
handleCommentSubmit = (newComment) =>{
// call action creator to dist action to all reducers and update relevant states
this.props.getVideoComments(newComment)
}
//Comments are create in comment form, passed up then sent down through commentList to individual comment rendering inside comment.js
// comment form oncommentsubmit running everytime it renders, not only on submital
render(){
const {comments} = this.props
console.log({comments})
return(
<div>
<span><h4> Comments </h4></span>
<div className="ui grid">
<div className = "right floated eight wide column" >
<CommentList comments={comments}/>
</div>
<div className="left floated eight wide column">
<CommentForm onCommentSubmit={this.handleCommentSubmit}/>
</div>
</div>
</div>
)
}
}
//redux stuff
//called following state update
const mapStateToProps = (state) => {
return {comments:state.videoComments}
}
export default connect(mapStateToProps,{getVideoComments:getVideoComments})(CommentsSection)
videoCommentsReducer.js
import React from 'react'
const videoCommentsReducer=function(state= [], action){ // reads in previous state
switch (action.type){
case 'GET_VIDEO_COMMENTS':
return [...state, action.payload] //reducer will update state to be payload.videoComments. Action only describes what happened
// reducer describes how what happened effects state. Could also use previous state and action to create new data
default:
return state
}
}
export default videoCommentsReducer
index.js (for action creator)
import React from 'react'
export const getVideoComments = (newComment) => {
return({
type: 'GET_VIDEO_COMMENTS',
payload: newComment
})
};

React Redux: Connected child component not receiving props

I can't seem to figure out what the issue is. I have a component called DisplayItems that gets some data from an API (a list of items).
I am using Redux actions and thunk middleware to get the items and data from the API. I save the items and other data to the redux store as an array of objects, and I name it entriesData. The actual array is in entriesData.data. I am using connect to connect the state and actions to DisplayItems' props.
Next, I have a child component called GalleryItemContentV1. GalleryItemContentV1 isn't connected to redux. It just receives the data from DisplayItems which loops through the array of items and passes each one to GalleryItemContentV1. So, again, DisplayItems loops through this.props.entriesData.data and passes each item to GalleryItemContentV1 as a prop called entry.
Finally, I have a child component in GalleryItemContentV1 called TestCom. That's where the issue is. I am also passing the item that I got from DisplyItem to TestCom. So, the data flow is DisplayItems (connected) > GalleryItemContentV1 (not connected) > TestCom (connected).
TestCom is connected to redux. It uses some actions from redux to update an item in entriesData.data. When I update this.props.entriesData.data in TestCom, GalleryItemContentV1 receives the updated data just fine, but TestCom doesn't receive anything. The item in TestCom is never updated.
I figured out that the issue is when I connect TestCom to redux. If TestCom is not connected to redux, it also receives the updated props, just like GalleryItemContentV1.
The components are below. I've tried to simplify them as much as possible.
DisplayItems
import React, {Component} from 'react';
import GalleryItemContentV1 from './GalleryItemContentV1';
import { connect } from 'react-redux';
import {getContestEntriesPageData} from '../../actions/contest';
class DisplayItems extends Component {
componentDidMount(){
this.props.getContestEntriesPageData(uri, search);
}
render(){
console.log('DisplayItems props', this.props);
return (
<div>
<div className="card-deck thumbnails" id="card-list">
{ this.props.entriesData.data instanceof Array ?
this.props.entriesData.data.map(function(object, i){
return <GalleryItemContentV1
entry={object} key={i}
arr_key={i}
/>;
}, this) : ''
}
</div>
</div>
)
}
}
const mapStateToProps = state => (
{
entriesData: state.entriesData,
}
)
const mapDispatchToProps = (dispatch) => {
return {
getContestEntriesPageData: (uri, search) => {
dispatch(getContestEntriesPageData(uri, search));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DisplayItems);
GalleryItemContentV1
import React, { Component } from 'react';
import TestCom from './TestCom';
class GalleryItemContentV1 extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<TestCom entry={this.props.entry} />
</div>
);
}
}
export default GalleryItemContentV1;
TestCom
I am outputting the results of this.props.entry.VotesCount (the item from DisplayItems) in TestCom. That's where the issue occurs. For some reason, when I connect TestCom to redux, its props aren't updated, but when it's disconnected from Redux, then its props are updated.
import React, {Component} from 'react';
import { connect } from 'react-redux';
class TestCom extends Component {
constructor(props) {
super(props);
}
render(){
console.log('TestCom props', this.props);
return (
<div>{this.props.entry.VotesCount}</div>
)
}
}
const mapStateToProps = state => (
{
user: state.user
}
)
export default connect(mapStateToProps)(TestCom);
Here's where I'm configuring redux, but I don't think it's relevant to know since GalleryItemContentV1 is seeing the updated data just fine.
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import ContestReducer from '../reducers/contest';
export default function configureStore(initialState) {
return createStore(
ContestReducer,
initialState,
applyMiddleware(thunk)
);
}
So, to summarize.
DisplayItems (is connected, receives updated data from store okay) > GalleryItemContentV1 (is not connected, receives updates okay) > TestCom (is connected, does not receive updates, but receives updates if disconnected)
Here's my reducer, just in case it helps.
import * as ContestActionTypes from '../actiontypes/contest';
const initialState = {
contest: null,
subscriptions: [],
user: null,
page: null
}
export default function Contest(state=initialState, action) {
console.log('redux action reducer: ', action)
switch(action.type) {
case ContestActionTypes.HANDLE_VOTE:
const newState = { ...state };
if (newState.entriesData) {
const index = newState.entriesData.data.findIndex(x => x.id === action.entry.id)
newState.entriesData.data[index].VotesCount = action.vote;
} else {
newState.entry.VotesCount = action.vote;
}
return newState
default:
return state;
}
}
I also noticed the same thing happening with GalleryItemContentV1. If I connect it, then it stops receiving updates from DisplayItems/the redux store.
Any input is appreciated. Thanks!
You're mutating your state. You're making a shallow copy of the top-level state in that slice reducer, but you aren't making copies of all the nested levels inside. Then, your connected component is extracting state.entriesData, which hasn't changed by reference. So, the connect wrapper component thinks that the state hasn't changed at all, and doesn't re-render your own component.
The Redux docs page on "Immutable Update Patterns" specifically points this out as a common mistake.
If updating nested state immutably is too much of a pain, I'd suggest looking into the immer immutable update library. Also, our new redux-starter-kit package uses Immer internally to let you write simpler reducers.
Also, see my post Idiomatic Redux: The History and Implementation of React-Redux for background and details on how connect works internally.
Yes, I also think you are mutating your state. The following is the TOGGLE_TODO case from reducer. I hope this code snippet will work for your code.
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
})
So, in your case if id matches then update is with action.vote. Also read about deep and shallow object copies.
markerikson was right, but I am posting the specific answer that helped me, here.
The problem was that I was trying to update an array inside of an object (entriesData being the object and entriesData.data being the array), using the normal method of Object.assign({}, state, {}) when I should have been using JSON.parse(JSON.stringify(state)). I had to read Mozilla's explanation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) to understand what was going on. I saw the JSON.parse solution before, but I didn't really understand shallow and deep cloning, and, whatever shallow cloning was, I didn't think I was guilty of it. Turns out, I was.
Here's my updated reducer that works perfectly.
case ContestActionTypes.HANDLE_VOTE:
const newState = JSON.parse(JSON.stringify(state));
if (newState.entriesData) {
const index = newState.entriesData.data.findIndex(x => x.id === action.entry.id)
newState.entriesData.data[index].VotesCount = action.vote;
} else {
newState.entry.VotesCount = action.vote;
}
return newState;

How can i assign the redux state to react state while the redux state change in React-Redux?

I am update the redux state by triggering the action from a form component. The updated state will read from the mapStateToProps in the same Component. Now my question is how do i assign the updated redux state to the existing react state in the Component.
Thanks in advance
You could use componentWillReceiveProps handler.
componentWillReceiveProps(nextPropsFromRedux) {
this.setState({ somethingFrom: nextPropsFromRedux.property})
}
You can use setState method of Component, and pass to it object with properties which were changed.
If you really want to do that, you probably want to you the componentWillReceiveProps component function and actually assign these values using this.setState.
But you have to be careful dealing with two application states. Ideally the component should only by able to receive props and transform or display them. In other words, we should avoid having component with state.
Take a look about stateless components.
After mapStateToProps componentWillRecieveProps is called where you set your new state.
/**
* Created by consultadd on 22/2/17.
*/
import React, { Component } from 'react'
import Head from 'next/head'
import { Router } from '../../../routes'
import { connect } from 'react-redux'
export class EventOrderContainer extends Component {
constructor (props) {
super(props)
this.state = {
orders: [],
orderpage: 0,
ShowCancel: false,
}
}
componentWillRecieveProps(newProps){
this.setState({
orderpage:newProps.ordersPage})
}
render () {
return (
)
}
}
const mapStateToProps = (state) => {
return {
ordersPage:state.orders.ordersPage
}
return state
}
const mapDispatchToProps = (dispatch) => {
}
export default connect(mapStateToProps, mapDispatchToProps)(EventOrderContainer)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Categories

Resources