I'm trying to make my first application with Redux, I've already made a version of this without Redux and I know that Redux isn't necessarily needed for this but I want to learn Redux.
I have a store which has an array of to-do items, my action successfully dispatches and updates the store.
My list of task components connects to the store and renders each item in the array as its own component.
On initial load, my to-do list shows the to-do items from the store's initial state, but once I update the state the new items from the state do not get rendered. Instead the map method to return the array of components says it 'cannot read property 'map' of undefined'.
How do I solve this?
Cheers.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Container from './components/Container.js'
import TaskReducer from './reducers/Task.js'
require("./index.css");
const defaultState = {
items: [
"task 1",
"task 2"
]
};
const store = createStore(TaskReducer, defaultState);
// Allows access to store in console log
window.store = store;
ReactDOM.render( (
<Provider store={store}>
<Container />
</Provider>
),
document.getElementById('wrapper')
);
import React from 'react';
import ReactDOM from 'react-dom';
import TaskList from './TaskList.js';
import { createStore, bindActionCreators } from 'redux';
import * as ActionCreators from '../actions/Task.js';
import Redux from 'redux';
import {connect} from 'react-redux'
class Container extends React.Component {
constructor() {
super();
}
render() {
// What does this do???
const {dispatch} = this.props;
const deleteItem = bindActionCreators(ActionCreators.deleteTodoItem, dispatch);
const addItem = bindActionCreators(ActionCreators.addTodoItem, dispatch);
function _onSubmit(e) {
e.preventDefault();
addItem(e.target.elements.task.value);
// Resets the form
e.target.reset();
}
return (
<div className="">
<header className="header">
<h1>To Do:</h1>
</header>
<form autoComplete="off" onSubmit={_onSubmit}>
<input name="task" placeholder="Task" autoComplete="off"></input>
</form>
<TaskList />
</div>
);
}
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(Container);
import React from 'react';
import Task from './Task';
import { connect } from 'react-redux';
let TaskList = (props) => {
console.log('items', props.items);
var tasks = (props.items).map( (item, key) => { return <Task data={item} key={key} listItemKey={key} /> })
return(
<ul className="task-list">
{tasks}
</ul>
);
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(TaskList);
<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>
import * as action from '../actions/Task.js'
export default function toDoItems(state = [], action) {
switch(action.type) {
case 'DELETE_ITEM':
return [
...state,
];
case 'ADD_ITEM':
console.log('ADD ITEM');
console.log('Submitted value = ', action.submittedValue)
return [
...state,
// Reducer gets action object item and appends to array
action.submittedValue
]
default:
return state;
}
}
--- Reducer ---
import * as action from '../actions/Task.js'
export default function toDoItems(state = [], action) {
switch(action.type) {
case 'DELETE_ITEM':
return [
...state,
];
case 'ADD_ITEM':
console.log('ADD ITEM');
console.log('Submitted value = ', action.submittedValue);
console.log('the state', state);
return [
...state,
// Reducer gets action object item and appends to array
action.submittedValue
]
default:
return state;
}
}
--- Action ---
export function addTodoItem(submittedValue) {
return {
type: 'ADD_ITEM',
// The action object returned has the submittedValue
submittedValue
}
}
export function deleteTodoItem() {
return {
type: 'DELETE_ITEM',
}
}
I have edited the TaskList component. You are not using the map function correctly
import React from 'react';
import Task from './Task';
import { connect } from 'react-redux';
let TaskList = (props) => {
console.log('items', props.items);
var tasks = undefined;
if(props.items && props.items.length > 0 ){
tasks = props.items.map( (item, key) => { return <Task data={item}
key={key} listItemKey={key} /> })
} //edited code
return(
<ul className="task-list">
{tasks}
</ul>
);
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(TaskList);
<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>
And as for the store use the following to configure store in your main file.
...
import TaskReducer from './reducers/Task.js';
import * as redux from 'redux';
function configure(initialState = {}){
const reducers = redux.combineReducers({
tasks : TaskReducer
});
let store = redux.createStore(reducers, initialState);
return store;
};
const store = configure();
// Allows access to store in console log
window.store = store;
ReactDOM.render( (
<Provider store={store}>
<Container />
</Provider>
),
document.getElementById('wrapper')
);
Related
I am having some problems with the dispatch of an action under Redux.
I have applied the different tutorials to the letter, but yet this does not take, I always get an empty table in my props.
Here is my code:
Config.js
import React from "react"
import ReactDOM from "react-dom"
import { Provider } from 'react-redux'
import store from './store'
import ConfigPage from "./components/ConfigPage/ConfigPage"
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<ConfigPage />
</Provider>,
rootElement
);
Store
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
import { fetchWalletAddress } from './actions/index'
const store = createStore(rootReducer, applyMiddleware(thunk))
store.dispatch(fetchWalletAddress());
export default store;
Reducer wallet.js
import { GET_WALLET_ADDRESS } from "../actions/actionTypes.js";
const initialState = {
wallet:[]
}
export default function(state = initialState, action){
switch(action.type){
case GET_WALLET_ADDRESS:
return [ ...state, ...action.payload];
default:
return state;
}
}
My action :
import {GET_WALLET_ADDRESS} from './actionTypes.js'
import axios from 'axios'
const apiUrl = 'https://api.elrond.com/accounts/erd15qltd5ccalm5smmgdc5wnx46ssda3p32xhsz4wpp6usldq7hq7xqq5fmn6';
export const fetchWalletAddress = () => {
return (dispatch) => {
return axios.get(apiUrl)
.then(response => {
return response.data
})
.then(data => {
dispatch({
type: GET_WALLET_ADDRESS,
payload: data
})
})
.catch(error => {
throw (error);
});
};
};
And for finish, my Configpage.js
import React from 'react'
import Authentication from '../../util/Authentication/Authentication'
import './Config.css'
import { connect } from 'react-redux'
import { fetchWalletAddress } from '../../actions/index'
class ConfigPage extends React.Component{
constructor(props){
super(props)
this.Authentication = new Authentication()
//if the extension is running on twitch or dev rig, set the shorthand here. otherwise, set to null.
this.twitch = window.Twitch ? window.Twitch.ext : null
this.state={
finishedLoading:false,
theme:'light',
isVisible:true,
wallet_address:'erd15qltd5ccalm5smmgdc5wnx46ssda3p32xhsz4wpp6usldq7hq7xqq5fmn6'
}
this.walletAddressHandler = this.walletAddressHandler.bind(this);
this.onSubmitForm = this.onSubmitForm.bind(this);
}
walletAddressHandler(event){
this.setState({
[event.target.name]:event.target.value
});
}
onSubmitForm(){
fetchWalletAddress();
this.twitch.rig.log(this.props.wallet)
}
contextUpdate(context, delta){
if(delta.includes('theme')){
this.setState(()=>{
return {theme:context.theme}
})
}
}
visibilityChanged(isVisible){
this.setState(()=>{
return {
isVisible
}
})
}
componentDidMount(){
this.twitch.rig.log(this.props.wallet)
if(this.twitch){
this.twitch.onAuthorized((auth)=>{
this.Authentication.setToken(auth.token, auth.userId)
if(!this.state.finishedLoading){
// if the component hasn't finished loading (as in we've not set up after getting a token), let's set it up now.
// now we've done the setup for the component, let's set the state to true to force a rerender with the correct data.
this.setState(()=>{
return {finishedLoading:true}
})
}
})
this.twitch.listen('broadcast',(target,contentType,body)=>{
this.twitch.rig.log(`New PubSub message!\n${target}\n${contentType}\n${body}`)
// now that you've got a listener, do something with the result...
// do something...
})
this.twitch.onVisibilityChanged((isVisible,_c)=>{
this.visibilityChanged(isVisible)
})
this.twitch.onContext((context,delta)=>{
this.contextUpdate(context,delta)
})
}
}
componentWillUnmount(){
if(this.twitch){
this.twitch.unlisten('broadcast', ()=>console.log('successfully unlistened'))
}
}
filterFloat(value) {
if (/^(\-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/
.test(value))
return Number(value);
return NaN;
}
render(){
if(this.state.finishedLoading && this.state.isVisible){
return (
<div className="App">
<div className={this.state.theme === 'light' ? 'App-light' : 'App-dark'} >
<p>Add your wallet address</p>
<input
name="wallet_address"
type="text"
onChange={this.walletAddressHandler}
value={this.state.wallet_address}>
</input>
<p>{this.props.wallet.username}</p>
<button OnClick={this.onSubmitForm}>Try it</button>
<ul>
{this.state.wallet ? String((Number(this.state.wallet.balance) * Math.pow(10, -18)).toFixed(4)) : null}
</ul>
</div>
</div>
)
}else{
return (
<div className="App">
</div>
)
}
}
}
const mapStateToProps = state => {
return {
wallet: state.wallet
}
};
export default connect(mapStateToProps, null)(ConfigPage);
thank you in advance for your help
You do not need to dispatch fetchWalletAddress while creating store, you can do that in the component. Also when you call fetchWalletAddress in the component, make sure to use the function that you make available to component via mapDispatchToProps argument of connect otherwise it wouldn't affect the redux store
Another thing you must do is to not use the updated redux value in the same function call since it takes a render cycle for it to reflect the updated change
store.js
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const store = createStore(rootReducer, applyMiddleware(thunk))
export default store;
Configpage.js
...
componentDidMount(){
this.props.fetchWalletAddress();
this.twitch.rig.log(this.props.wallet)
if(this.twitch){
this.twitch.onAuthorized((auth)=>{
this.Authentication.setToken(auth.token, auth.userId)
if(!this.state.finishedLoading){
// if the component hasn't finished loading (as in we've not set up after getting a token), let's set it up now.
// now we've done the setup for the component, let's set the state to true to force a rerender with the correct data.
this.setState(()=>{
return {finishedLoading:true}
})
}
})
this.twitch.listen('broadcast',(target,contentType,body)=>{
this.twitch.rig.log(`New PubSub message!\n${target}\n${contentType}\n${body}`)
// now that you've got a listener, do something with the result...
// do something...
})
this.twitch.onVisibilityChanged((isVisible,_c)=>{
this.visibilityChanged(isVisible)
})
this.twitch.onContext((context,delta)=>{
this.contextUpdate(context,delta)
})
}
}
...
componentDidUpdate(prevProps) {
if (!_.isEqual(prevProps.wallet, this.props.wallet)) {
this.twitch.rig.log(this.props.wallet)
}
}
...
onSubmitForm(){
this.props.fetchWalletAddress();// use action from props
}
...
const mapDispatchToProps = {
fetchWalletAddress,
}
const mapStateToProps = state => {
return {
wallet: state.wallet
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ConfigPage);
The state is empty when I try to map the state from the store to the properties of a component. I try to get a value of a string displayed in JSX but it is missing. I cant manage to get anything to display from the redux store.
Reducer:
const initialState = {
visibles: "false",
error: null,
text: ""
};
const rootReducer = (
state = initialState,
action
) => {
switch (action.type) {
case "OPEN_MODAL":
return {
...state,
visibles: "true",
error: null
};
default:
return state;
}
}
export default rootReducer;
and index.js
import {createStore } from "redux";
import {Provider } from "react-redux";
import rootReducer from "./components/Redux/Reducer";
const store = createStore(rootReducer);
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
consumer of the redux store
import React, { Component } from 'react'
import {connect} from "react-redux";
import styles from "./modal.module.css";
export class Modal extends Component {
render() {
console.log(this.props)
return (
<div className={styles.root}>
<p className={styles.title}>{this.props.visible}</p>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
visible: state.visibles
}
}
export default connect(mapStateToProps)(Modal)
Found the reason why. I had to refactor the Modal class to not use "export class" and then I could get the state from the store with connect.
class Modal extends React.Component {
render() {
console.log(this.props)
return (
<div className={styles.root}>
<p className={styles.title}>{this.props.visible}</p>
</div>
)
}}
I have a parent component and a child component. I can see that the reducer is getting an update (Please refer to screenshot) but it is not re-rendering the component. I am calling some API, and once the response comes, it will just update some variable and re-render the component.
Parent component
import React, { Component } from 'react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import {connect} from "react-redux";
import { bindActionCreators } from 'redux'
import ProfileInfo from '../components/Profile/ProfileInfo'
import Details from '../components/Profile/Details'
import UnitSpec from '../components/Profile/UnitSpec'
import * as profileActionCreators from "./../actions/profileActions";
import * as StaticData from '../utils/country'
class NewProfilePage extends Component {
constructor(props) {
super(props);
this.state = { tabIndex: 0 ,
countryList: StaticData.Country,
};
let { dispatch } = this.props
this.actions = bindActionCreators({...profileActionCreators} , dispatch);
}
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps)
}
render() {
return (
<div className="admin-holder">
<div className="row profile-row">
<div className="small-12 large-4 columns">
<ProfileInfo></ProfileInfo>
</div>
<div className="small-12 large-8 columns">
<Tabs selectedIndex={this.state.tabIndex} onSelect={tabIndex => this.setState({ tabIndex })}>
<TabList>
<Tab>Details</Tab>
<Tab>Unit Spec</Tab>
<Tab>Imaginery</Tab>
<Tab>Menu</Tab>
</TabList>
<TabPanel>
<Details updateDetails = {(userDetail) => this.actions.updateDetails(userDetail)} countryList={this.state.countryList}/>
</TabPanel>
<TabPanel>
<UnitSpec staticData={StaticData}/>
</TabPanel>
<TabPanel>
</TabPanel>
<TabPanel>
</TabPanel>
</Tabs>
</div>
</div>
</div>
);
}
}
const mapStateToProps= (state) => {
console.log('state', state)
return{
profileReducer: state.profileReducer
};
};
export default connect(mapStateToProps)(NewProfilePage);
Reducer
import * as types from '../constant';
const initialCommonState = {
countryList: [],
loadingCountryList: false,
isProfileUpdated: false
}
const profileReducer = (state=initialCommonState, action) => {
switch(action.type){
case types.UPDATE_DETAILS + "_FULFILLED":
const response = action.payload;
return Object.assign({}, state, {
isProfileUpdated: true
});
case types.UPDATE_DETAILS + "_PENDING":
return Object.assign({}, state, {
isProfileUpdated: false
});
case types.UPDATE_DETAILS + "_REJECTED":
return Object.assign({}, state, {
isProfileUpdated: false
});
default :
return state
}
}
export default profileReducer;
Please look into screenshot as well and you can see "isProfileUpdated" is changed to true
Store
import {createStore,combineReducers,applyMiddleware} from 'redux'
import logger from 'redux-logger';
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import reducers from '../reducers';
const middleware = [];
if (process.env.NODE_ENV === 'development') {
middleware.push(logger);
}
export default createStore(
combineReducers({reducers}),
{},
//applyMiddleware(logger(),thunk , promise())
applyMiddleware(logger, thunk , promise())
)
Your state contains profile
const mapStateToProps= (state) => {
console.log('state', state)
return{
profileReducer: state.profile
};
};
I am trying to create something similar to todo app with react and redux from here.I have been reading all the solutions for this problem and nothing seems to be working for my case.
Most of the solutions purpose using Provider which I am already using. Any help is much appreciated.
Edit - Few import statement might be missing in snippets, but all components are being imported whenever needed and actions are also defined in action file.
Index.js
import App from './components/App'
import reducer from './reducers/index'
const store = createStore(reducer)
const AppWithStore = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(AppWithStore, document.getElementById('root'))
Update - Combined Reducer code
import React from 'react'
import { combineReducers } from 'redux'
import TestReducer from './TestReducer'
export default combineReducers({
TestReducer,
})
App.js
import Test from './Test';
class App extends Component {
render() {
return (
<Test />
);}
}
ReactDOM.render(
(<App/>),
document.getElementById("root")
);
export default App
Test.js contains both component and container
import { connect } from 'react-redux'
import { add } from '../actions'
const mapStateToProps = state => ({
todos: state.todos,
})
class Test extends Component {
dosomething() {
const dispatch = this.props;
dispatch(add("New Note"));
}
render() {
return (
<div>
< button OnClick = { this.dosomething.bind(this)} > Test </button>
</div>
)
}
}
export default connect(mapStateToProps)(Test)
The reducer logic for Test is as given below
import React from 'react';
const TestReducer = (state = [], action) => {
const todos = state;
const {type, payload} = action;
switch(action.type){
case 'ADD': {
return {
...state,
todos:"new todo"
}
}
}
return state;
}
export default TestReducer
You should remove
ReactDOM.render(
(<App/>),
document.getElementById("root")
); from App.js file
When you call it again in App.js a new component instance independent of others is created, That's why it is not finding store.As store is not passed to it.
You can check it here https://codesandbox.io/s/vy7wwqw570. As i had remove render api call from app.js it is working now.
you need to import your reducers so that you
if your file name is TestReducer.js
import React from 'react';
export const TestReducer = (state = [], action) => {
const todos = state;
const {type, payload} = action;
switch(action.type){
case 'ADD': {
return {
...state,
todos:"new todo"
}
}
}
return state;
}
then import it in this manner
import {TestReducer} from './TestReducer.js';
const store = createStore(TestReducer);
Try replacing todos: state.todos with todos: state.TestReducer.todos
import { connect } from 'react-redux'
import { add } from '../actions'
const mapStateToProps = state => ({
//todos: state.todos,
todos: state.TestReducer.todos
})
class Test extends Component {
dosomething() {
const dispatch = this.props;
dispatch(add("New Note"));
}
render() {
return (
<div>
< button OnClick = { this.dosomething.bind(this)} > Test </button>
</div>
)
}
}
export default connect(mapStateToProps)(Test)
I've been debugging this program but with no clue, I followed this tutorial word by word trying to make a TODO app but I could not figure out why I am getting this error message.
./src/containers.js
Line 12: 'dispatch' is not defined no-undef
Line 13: 'dispatch' is not defined no-undef
components.js
import React from 'react'
class Todo extends React.Component {
render() {
const { todo } = this.props
if (todo.isDone) {
return <strike>{todo.text}</strike>
} else {
return <span>{todo.text}</span>
}
}
}
export default class TodoList extends React.Component {
render() {
const {todos, toggleTodo, addTodo } = this.props
console.log(toggleTodo)
return (
<div className="todo">
<input type="text" placeholder="Add todo"/>
<ul className='todo__list'>
{todos.map(t => (
<li key={t.id} className='todo__item'>
<Todo todo={t} />
</li>
))}
</ul>
</div>
)
}
}
containers.js
import * as components from './components.js'
import { addTodo, toggleTodo } from './actions.js'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
return {todos: state}
}
const mapDispatchToProps = (state) => {
return {
addTodo: text => dispatch(addTodo(text)),
toggleTodo: id => dispatch(toggleTodo(id))
}
}
const TodoList = connect(mapStateToProps, mapDispatchToProps)(components.TodoList)
export default TodoList
reducers.js
const reducer = (todos = [], action) => {
switch(action.type) {
case 'ADD_TODO': {
return [...todos, {id: action.id, text: action.text, completed: false}]
}
case 'TOGGLE_TODO': {
return todos.map(todo => todo.id === action.id ? {...todo, completed: !todo.completed} : todo)
}
default: {
return todos
}
}
}
export default reducer
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { TodoList } from './containers'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import reducer from './reducers'
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<TodoList />
</Provider>,
document.getElementById("app")
)
Instead of state here:
mapDispatchToProps = (state) =>
Use dispatch:
mapDispatchToProps = (dispatch) =>
In containers.js.
DOC:
container components can dispatch actions. In a similar fashion, you
can define a function called mapDispatchToProps() that receives the
dispatch() method and returns callback props that you want to inject
into the presentational component.