I am completely new at react and I am following a tutorial. For this tutorial I have the following component called user-list.js:
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
class UserList extends Component {
createListItems() {
return this.props.users.map((user) => {
return (
<li>{user.first}</li>
);
});
}
render() {
return (
<ul>
{this.createListItems()}
</ul>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
};
}
export default connect(mapStateToProps)(UserList);
And here is my reducer-users.js
export default function() {
return [
{
id: 1,
first: 'Bucky',
last: 'Roberts',
age: 71,
description: 'Bucky is a React developer anbd Youtuber',
thumbnail: 'http://i.imgur.com/7yUvePI.jpg'
},
{
id: 2,
first: 'Joby',
last: 'Wasilenko',
age: 27,
description: 'Joby loves the Packers, cheese and turtles',
thumbnail: 'http://i.imgur.com/52xRlm8.jpg'
},
{
id: 3,
first: 'Madison',
last: 'Williams',
age: 24,
description: 'Madi likes her dog but it is really annoying.',
thumbnail: 'http://i.imgur.com/4EMtxHB.jpg'
}
]
}
And now I am getting an error in the console:
Uncaught TypeError: Cannot read property 'map' of undefined
I don't understand what I am doing wrong, I removed the map function and returned any other data and it works fine, except when it tries to map the data.
When using the ES6 syntax for React Components, in your constructor function you need to bind any custom bethods you define on your class.
add the following to your UserList definition
constructor(props) {
super(props);
this.createListItems = this.createListItems.bind(this);
}
and you should be good to go. If you don't like doing this, you can also revert to the React.createClass({}) method for creating your component class.
you should have something like this for your code to work correctly.
// user reducer file
const initialState = [
{
id: 1,
first: 'Bucky',
last: 'Roberts',
age: 71,
description: 'Bucky is a React developer anbd Youtuber',
thumbnail: 'http://i.imgur.com/7yUvePI.jpg'
},
{
id: 2,
first: 'Joby',
last: 'Wasilenko',
age: 27,
description: 'Joby loves the Packers, cheese and turtles',
thumbnail: 'http://i.imgur.com/52xRlm8.jpg'
},
{
id: 3,
first: 'Madison',
last: 'Williams',
age: 24,
description: 'Madi likes her dog but it is really annoying.',
thumbnail: 'http://i.imgur.com/4EMtxHB.jpg'
}
];
export default function usersReducer(state, action) {
return state;
}
// root reducer file
import { combineReducers } from 'redux';
import usersReducer from 'your path to users reducer file'
export default function rootReducer() {
return combineReducers({
users: usersReducer,
});
}
// store file
import { createStore } from 'redux';
import rootReducer from 'path to root reducer file';
const store = createStore(rootReducer());
export default store;
Related
I am new to React-Redux and i am struggling with a proof-of-concept project (list of movies) in order to learn the basics of redux. I am quite good at React, but Redux is another deal...
So, in my repo and specifically in Smart Component MoviesList.jsx (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/MoviesList.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) i am wondering how to dispatch the correct action to change the rating (increment, decrement) in each SingleMovieItem (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/SingleMovieItem.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) and what would be the correct reducer to achieve that. Could anyone help me with this?
In other words, to show a real example of what i mean...
Lets say that there is a main App and we make use of the
<Provider store={store}>
<App />
</Provider>
And then the App contains the following:
import React, { Component } from 'react';
import MoviesList from './MoviesList'
class App extends Component {
render() {
return (
<div>
<MoviesList />
</div>
)
}
}
export default App;
And then, the MovieList.jsd contains the following:
import React, { Component } from 'react';
import { addRating } from './actions';
import SingleMovieItem from './SingleMovieItem';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
allMovies: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(addRating(id))
};
};
class MovieList extends Component {
constructor(props) {
super(props);
}
handleIncrement = id => {
}
render() {
return (
<div>
<ul className="moviesList">
{this.props.allMovies.movies.map(movie => {
return (
<SingleMovieItem
key={movie.id}
movie={movie}
onIncrement={this.handleIncrement(movie.id)} />
);
})}
</ul>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MovieList);
What would be the reducer to increment rating of each singlemovie
if initialState is
const initialState = {
movies: [
{
id: "1",
title: "Harry Potter 1",
url: "harry-1.jpg",
rating: "2"
},
{
id: "2",
title: "Harry Potter 2",
url: "harry-2.jpg",
rating: "3"
}
]
};
Thanks in advance.
I have used observables in Angular, but I am having an issue in finding good examples on how to uses observables with React using Javascript. My end goal is to use hard coded data in a service.ts example
import { of, Observable } from 'rxjs';
export interface Volunteer {
firstName: string;
lastName: string;
totalHoursLogged: number;
}
const tempVolunteers: Volunteer[] = [
{ firstName: 'John', lastName: 'Smith', totalHoursLogged: 85 },
];
export const getAllVolunteers = (): Observable<Volunteer[]> => of(tempVolunteers);
I want to make a list component to build the mock data as it is push from the observable.
I would export the react component into the App.tsx and display the
mock data
If anyone knows of good resources or has any advice, please let me know.
Thanks
You can try this example (without Typescript for simplicity):
source file:
import { of } from 'rxjs';
const tempVolunteers = [
{ firstName: 'John', lastName: 'Smith', totalHoursLogged: 85 },
];
export const getAllVolunteers = of(tempVolunteers);
React app file:
import React, { Component } from 'react';
import {
getAllVolunteers,
} from '../RxJS';
class App extends Component {
state = {
list: [],
};
componentDidMount() {
getAllVolunteers.subscribe((item) => {
this.setState({
list: [...this.state.list, ...item],
});
});
}
render() {
return (
<div>
<ul>
{ this.state.list
.map(({ firstName, lastName, totalHoursLogged }) =>
(
<div key={lastName}>
{`${firstName} ${lastName} - ${totalHoursLogged}`}
</div>
),
)
}
</ul>
</div>
);
}
}
export default App;
I tried every possible variation of this code, but I don't really manage to get whatever the API fetched into my data store. I am absolutely stuck and would appreciate some help.
I think I just don't get the essential part of this construct and I would really like to understand how it works properly.
The data looks like this - it's basically a simple JSON (from a django restframework API) with some nested elements:
EDIT 2 (changed JSON to screenshot of axios API/ Redux action)
My Redux action - works perfectly fine. console.log pulls exactly the data from above (with correct inputs) :
// ./action/plan.js
import axios from 'axios';
export function fetchBudgets(){
return function(dispatch){
axios.get("/api/budgets/")
.then((response) => {
console.log(response)
dispatch({ type: "FETCH_BUDGETS", budgets: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_DATA_REJECTED", budgets: err})
})
}
}
So until now, everything seems fine. The problems starts with the reducer - as I am not sure how to model the reducer to use the nested data.
My reducer:
// ./reducer/plan.js
const initialState = {}
export default function budgets(state=initialState, action) {
switch (action.type) {
case 'FETCH_BUDGETS':
console.log(action)
return {
...state,
id: action.budgets.id,
value_jan: action.budgets.value_jan,
value_feb: action.budgets.value_feb,
value_mar: action.budgets.value_mar,
value_apr: action.budgets.value_apr,
value_may: action.budgets.value_may,
value_jun: action.budgets.value_jun,
value_jul: action.budgets.value_jul,
value_aug: action.budgets.value_aug,
value_sep: action.budgets.value_sep,
value_oct: action.budgets.value_oct,
value_nov: action.budgets.value_nov,
value_dec: action.budgets.value_dec,
p_version: action.budgets.p_version,
entry_time: action.budgets.entry_time,
campaign: {
...state.campaign, ...action.budgets.campaign
},
segment: {
...state.segment, ...action.budgets.segment
},
touch_point: {
...state.touch_point, ...action.budgets.touch_point
},
year: {
...state.year, ...action.budgets.year
},
user: {
...state.user, ...action.budgets.user
}
}
default:
return state
}
}
I already cannot display data in here - so this.props.fetchBudgets() doesn't seem to fetch any data.
My .jsx App
//./container/PlanContainer.jsx
import React, { Component } from 'react';
import {connect} from 'react-redux';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
import 'jquery';
import 'popper.js'
import 'bootstrap';
import 'underscore'
import _ from 'lodash'
import {plan} from "../actions";
const columns = [
{ dataField: 'id', text: 'ID', hidden: true},
{ dataField: 'year', text: 'Year', editable: false},
{ dataField: 'segment', text: 'Segment', editable: false},
{ dataField: 'campaign.name',text: 'Campaign', editable: false},
{ dataField: 'touch_point',text: 'Touchpoint', editable: false},
{ dataField: 'value_jan',text: 'Jan'},
{ dataField: 'value_feb',text: 'Feb'},
{ dataField: 'value_mar',text: 'Mar'},
{ dataField: 'value_apr',text: 'Apr'},
{ dataField: 'value_may',text: 'May'},
{ dataField: 'value_jun',text: 'Jun'},
{ dataField: 'value_jul',text: 'Jul'},
{ dataField: 'value_aug',text: 'Aug'},
{ dataField: 'value_sep',text: 'Sep'},
{ dataField: 'value_oct',text: 'Oct'},
{ dataField: 'value_nov',text: 'Nov'},
{ dataField: 'value_dec',text: 'Dec'},
{ dataField: 'user',text: 'User'},
];
const RemoteCellEdit = (props) => {
const { columns, data, keyField } = props
const cellEdit = {
mode: 'click',
errorMessage: props.errorMessage,
blurToSave: true
};
return (
<div>
<BootstrapTable
remote={ { cellEdit: true } }
keyField = { keyField }
data={ data }
columns={ columns }
/>
</div>
);
};
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
console.log(this.props.fetchBudgets())
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets }
columns = { columns }
keyField = 'id'
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
budgets: state.budgets,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchBudgets: () => {
dispatch(plan.fetchBudgets());
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PlanContainer);
Finally, my store - according to the console.log nothing is beeing passed:
// .Planning.jsx
import React from "react"
import { hot } from 'react-hot-loader'
import { render } from "react-dom"
import {
createStore,
compose,
applyMiddleware,
combineReducers,
} from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import PlanContainer from "./containers/PlanContainer"
import reducerApp from "./reducers";
import Sidebar from "./components/Sidebar"
import axios from 'axios';
import axiosMiddleware from 'redux-axios-middleware';
let store = createStore(reducerApp, applyMiddleware(thunk, axiosMiddleware(axios)));
console.log(store)
class Planning extends React.Component {
render() {
return (
<Sidebar>
<Provider store={store}>
<PlanContainer />
</Provider>
</Sidebar>
)
}
}
render(<Planning />, document.getElementById('Planning'))
Again, I would appreciate as I've been stuck on this issue for quite some time and I really want to understand how to do this properly.
Edit:
Here's a screenshot of my browser: 1st element is the store, second in the .jsx app, 3rd of the action (that looks perfectly fine) and 4th of the action in the reducer.
PlanContainer is messed up. Here's how:
componentDidMount() {
this.budgets = this.props.fetchBudgets();
}
this.budgets is pointing to the value returned by this.props.fetchBudgets() which, in this case, is a Promise, and not the actual data.
state = {
data: this.budgets
};
state now holds the promise, not the data.
render() {
return (
<div>
<RemoteCellEdit
data={ this.state.data }
...
}
So data here is not the actual data but the promise.
The confusion is happening because you are mixing redux state with react state. Use one or the other, not both (there are expcetions to this but not in this particular scenario).
There are some more issues with PlanContainer which are not clear as to whether they are real issues, or just a result of code ommission in OP.
See annotations below:
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
}
constructor(props) {
... // removed for brevity, use the same code as you have right now
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets}
columns = { this.columns }
keyField = 'id'
errorMessage={ /* should come from props.data or similar - it's not in state */ }
/>
<tbody>
{this.props.budgets} /* not sure what this is for - I assumed RemoteCellEdit is the one rendering the data */
</tbody>
</div>
);
}
}
Fixing these should set you on the correct course. Good luck!
I have dashboard that have a sections which is separated by react-router.
Sections are:
Categories
Exams
Reports
Lets take a look at Exam section. we have this state as the initial state.
{
ID: null,
RandomizeQuestions: false,
RandomizeAnswers: false,
Pages: [
{
ID: 1,
Title: 'Page one',
Blocks: [
{
ID: 1,
Title: 'Block One',
Questions: [
{
ID: 1,
Title: "Quesiton title is here",
Answers: [
{
ID: 1,
Title: "Answer 1"
},
{
ID: 2,
Title: "Answer 2"
}
]
}
]
}
]
}
]
}
I know that the state should be flatten and normalized when I use redux. but that is my exact problem. if i try to normalize my state it should be like this :
{
Exam: {
ID: null,
RandomizeQuestions: false,
RandomizeAnswers: false
},
ExamPages: [
{
ExamId: 1,
ID: 1,
Title: 'Page One'
}
],
ExamBlocks: [
{
ID: 1,
Title: 'The New Block',
Questions: []
}
],
ExamQuestions: [],
ExamAnswers: []
}
lets assume that its ok and I wont have problem with it. but when happen to the other sections ?
Categories have its own states which is big too and report too. ( there are other section that I didn't mention )
and then my combineReducer would be looks like this :
import {combineReducers} from 'redux'
import Exam from './redc.exams.jsx'
import ExamBlocks from './redc.blocks.jsx'
const rootReducer = combineReducers({
Exam,
ExamBlocks
})
export default rootReducer
and of course I should have a reducer for every array in that object.
so my main question is how can I at lest have this structure:
const initialState = {
Exams: {
Blocks: [],
Questions: [],
Answers: []
},
Categories: {
Menus: [],
Users: []
},
Report: {
MainReports: [],
SideReports: []
}
}
remember that I want to have separate reducers for each of them.
a reducer for Blocks , reducer for Questions and so on...
UPDATE #1
I tried #Tom Fenech said in the answers but cannot combine reducers before final combineReducers.
right now my root reducers is looks like this:
import {combineReducers} from 'redux'
//Exams
import Exams from './ExamsReducers/redc.exams.jsx'
import Blocks from './ExamsReducers/redc.blocks.jsx'
//Categories
import Categories from './CategoriesReducers/redc.categories.jsx'
const rootReducer = combineReducers({
Exams,
Categories,
Blocks,
})
export default rootReducer
I tried with what he says and here is my code after the changes:
import {combineReducers} from 'redux'
//Exams
import Exams from './ExamsReducers/redc.exams.jsx'
import Blocks from './ExamsReducers/redc.blocks.jsx'
//Categories
import Categories from './CategoriesReducers/redc.categories.jsx'
const examRedc = combineReducers(Exams,Blocks )
const catsRedc = combineReducers(Categories)
const rootReducer = combineReducers(examRedc, catsRedc)
export default rootReducer
i get this error :
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
and when i log the state in one of the components like this
var mapDispatchToProps = function (dispatch) {
return {
AddBlock: function () {
dispatch(addBlock());
}
}
};
const mapStateToProps = function (state) {
console.log(state); //////HERE
return {Blocks: state.Blocks};
}
export default connect(mapStateToProps, mapDispatchToProps)(Blocks)
I get this state which is not what I want finally.
I don't think there's any need to have Blocks, Questions and Answers inside of Exams. I would suggest something like this at the top level:
{
Exams: {
[ID]: {
ID: string,
RandomizeQuestions: boolean,
RandomizeAnswers: boolean,
Blocks: string[] // array of block IDs
}
}
Blocks: {
[ID]: {
ID: string,
Title: string,
Questions: string[] // array of question IDs
}
},
Questions: // etc.
}
Now, instead of storing the nested objects, we just store their ID to keep track of ownership.
This state would be created with:
state = combineReducers({
examsReducer,
blocksReducer,
// ...more reducers
})
If you want to split the overall state into sections, you could use something like this:
const examStuff = combineReducers({
examsReducer,
blocksReducer,
// ...
});
const categoryStuff = combineReducers({
categoriesReducer,
menusReducer,
// ...
});
const state = combineReducers({
examStuff,
categoryStuff
});
Then state would be:
{
examStuff: {} // the object defined at the top of question,
categoryStuff: {},
otherStuff: {}
}
You can have nested reducer structure like
// block_reducer.js
export const reducer = (state = [], action) => {
switch (action.type) {
case BLOCK_ACTION:
return [{a:1}, {b:2}]
}
return state
}
// exam_reducer.js
import {combineReducers} from 'redux'
import {reducer as BlockReducer} from './block_reducer'
export const reducer = combineReducers({
Blocks: BlockReducer,
Sections: .....,
Answers: .......
})
// root_reducer.js
import {combineReducers} from 'redux'
import {reducer as examReducers} from './exam_reducers'
export const reducer = combineReducers({
Exams: examReducers,
Categories: .....,
Report: ......
})
For my component, I set a context
ManageCoursePage.contextTypes = {
router: PropTypes.object
};
How does my class method know that I am referencing react router to automatically redirect me to another URL?
this.context.router.push('/courses');
Here is my component code:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as courseActions from '../../actions/courseActions';
import CourseForm from './courseForm';
class ManageCoursePage extends React.Component {
constructor(props, context) {
super(props, context);
// set up local state
this.state = {
errors: {},
course: Object.assign({}, this.props.course)
};
this.updateCourseState = this.updateCourseState.bind(this);
this.saveCourse = this.saveCourse.bind(this);
}
updateCourseState(event) {
const field = event.target.name;
let course = this.state.course;
course[field] = event.target.value;
return this.setState({
course: course
});
}
saveCourse(event) {
event.preventDefault();
this.props.actions.saveCourse(this.state.course);
this.context.router.push('/courses');
}
render() {
return (
<CourseForm
allAuthors={ this.props.authors }
onChange={ this.updateCourseState }
onSave={ this.saveCourse }
course={ this.state.course }
errors={ this.state.errors }
/>
);
}
}
ManageCoursePage.propTypes = {
// myProp: PropTypes.string.isRequired
course: PropTypes.object.isRequired,
authors: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
// Pull in the React Router context so router is available on this.context.router
// basically a global variable to make it easy for other components to get data easily
ManageCoursePage.contextTypes = {
router: PropTypes.object
};
function mapStateToProps(state, ownProps) {
// empty course
let course = {
id: "",
watchHref: "",
title: "",
authorId: "",
length: "",
category: ""
};
const authorsFormattedForDropDown = state.authors.map(author => {
return {
value: author.id,
text: author.firstName + " " + author.lastName
};
});
return {
course: course,
authors: authorsFormattedForDropDown
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(courseActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ManageCoursePage);
Very interesting question. :)
It works because router is defined as a childContext type in the react-router library. getChildContext will make this accessible in inside the application if you map contextTypes in a component.
This is helpful in many ways to avoid deeply passing the props from a parent component to deep child component.
Refer this in react-router library https://github.com/reactjs/react-router/blob/master/modules/RouterContext.js#L36
And also the documentation https://facebook.github.io/react/docs/context.html