Either not changing state or not rerendering redux - reactjs

I am dispatching an action to a reducer to change that state, however nothing is re-rending. Below is some code however since the project is small I'm including a link to the repo on Github here
import React, {Component} from 'react';
import {connect} from 'react-redux';
import * as action from '../action';
import {addTodo} from "../action";
//this is used to get the text from the input to create a new task
let text = "";
class AddTodo extends Component
{
render()
{
return(
<div>
<input type="text" id="taskText" onChange={ () => { text = document.querySelector("#taskText").value;} }/>
<button onClick={this.props.addTodo}>+</button>
</div>
);
}
}
const mapDispatchToProps = (dispatch) =>
{
return (
{
addTodo: () =>
{
//console.log(`making action with text: ${text}`);
addTodo.payload = {text:text, completed:false};
dispatch(addTodo);
}
}
);
};
export default connect(null, mapDispatchToProps)(AddTodo);

Since you haven't shared all the related code here most people don't check your repo. In the future, try to share the related (just related code) here. If you do so, you will get faster and better answers.
I will share how you solve your problems first.
Uncomment TodoList component in the App.js file.
In TodoList component your are using the wrong state. Your todos in the todo reducer.
So:
taskList: state.list.tasks.todo.concat( state.list.tasks.completed ),
You are mutating your state in todo reducer. Don't mutate your state.
Change the related part:
case "ADD_TODO":
return { ...state, tasks: { ...state.tasks, todo: [ ...state.tasks.todo, action.payload ] } };
Other than those problems, you are using some bad practices. For example, why do you keep the text in a variable like that in your AddTodo component? Use the local state here, this is the proper React way.
Also, your action creators are not so properly defined. They are functions, returning an object. Now, your AddTodo component would be like this:
import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../action";
class AddTodo extends Component {
state = {
text: "",
}
handleChange = e => this.setState( { text: e.target.value } );
handleSubmit = () => {
const newTodo = { text: this.state.text, completed: false };
this.props.addTodo( newTodo );
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
<button onClick={this.handleSubmit}>+</button>
</div>
);
}
}
const mapDispatchToProps =
{
addTodo: actions.addTodo,
};
export default connect( null, mapDispatchToProps )( AddTodo );
Or even, you don't need here a separate mapDispatchToProps if you like. You can use the connect part like this:
export default connect( null, { addTodo: actions.addTodo } )( AddTodo );
Then, your related action creator would be like this:
export const addTodo = newTodo => ({
type: "ADD_TODO",
payload: newTodo
});
So, I suggest reading more good tutorials about Redux :) Just give yourself a little bit more time. Follow some good tutorials until you are sure that you know the best practices and proper ways. For example, if you study Redux, the first rule is not mutating your state. You are doing it everywhere :) Also, try to keep your state simple, not so nested.
Good luck.

Related

Logic in component or mapStateToProps

If MyComponent gets data from the redux store, but organises it in some way first before mapping it, should that organisation be done in the component or mapStateToProps function and why?
const MyComponent = ({ data }) => {
// IN HERE?
return (
<div>
{data.map((d) => (...))}
</div>
);
};
const mapStateToProps = (state) => {
const output = state.data
// OR HERE?
return { data: output };
};
export default connect(mapStateToProps)(MyComponent);
Hello have a nice day.
i think is better have a file with all the logic to conect with redux, so every time i need to connect with redux i create a file that name is ComponentNameContainer.jsx, this file looks like that:
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import Row from '../components/Row';
import {doSomething} from '../redux/somethingActions'
// here the imports of function from your actions
export default withRouter(connect(
(state, ownProps) => {
return {
// props that u need from redux
// example: state.sessionReducer.user
}
},
{
// functions that u need from redux
//example: doSomething
}
)(Row))
i have a folder call containers to store all the container files to keep track of the components that are connected with redux.

can't pass the state as a props from redux

I'm stacked for a few hours trying to make a todo app by using react + redux.
I was trying on like a hundreds ways and, it looks like I can't get it
Mainly, I'm trying to get my state as a props in my component and I can't everytime when I try to display sth, im receving "undefined"
`
import React from 'react'
import { connect } from 'react-redux'
export const TodoList = () => {
return (
<div>
<h1>There's going to be a list of todos.</h1>
<h2>{this.props.todos}</h2>
</div>
)
}
const mapStateToProps = (state) => ({
todos: state.todos
})
export default connect(mapStateToProps)(TodoList)
`
code looks like that, and here's the reducer aswell.
const todosReducer = ( state = [{
id: 1,
content: 'asdads',
isDone: false
}], action) => {
switch(action.type){
case 'ADD_TODO' :
return [
...state,
{
id: action.id,
content: action.content,
isDone: action.isDone
}
]
case 'DELETE_TODO':
return state.filter(todo => {
return todo.id !== action.id
})
default:
return state;
}
}
export default todosReducer
and then here's the combineReducer function , because I want to add some filters later on.
And actually im marking it as todos but it won't work
import { combineReducers } from 'redux';
import todosReducer from './todos';
const rootReducer = combineReducers({
todos: todosReducer
})
export default rootReducer
Can anyone of you good boys explain a correct way for me ?
you need to pass props as an argument to the functional component,
otherwise the component has no idea where the identifier is declared thus it is undefined.
Also you need to use props.todos instead of this.props.todos as you are using a functional component instead of a class component.
export const TodoList = (props) => {
return (
<div>
<h1>There's going to be a list of todos.</h1>
<h2>{props.todos}</h2>
</div>
)
}
Hope this helps.
Edit: as pointed by #Shubham Khatri you also need to map over the array and render the list
For example:
{props.todos.map((todo) => {
return <h2>{todo.content}</h2>
})}
You can also use the short hand syntax to auto return the value.

React, Redux - pass function from component A to other components

import React from "react";
import OtherComponent from "./OtherComponent";
class Main extends React.Component {
constructor(props) {
super(props);
this.runMyFunction = this.runMyFunction.bind(this);
this.myFunction = this.myFunction.bind(this);
}
runMyFunction(event) {
event.preventDefault();
this.myFunction();
}
myFunction() {
return console.log("I was executed in Main.js");
}
render() {
return (
<div>
<OtherComponent runMyFunction={this.runMyFunction} />
</div>
);
}
}
export default Main;
import React from "react";
class OtherComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.runMyFunction();
}
render() {
return (
<div>
<button onClick={this.handleClick} />Click me to execute function from Main </button>
</div>
);
}
}
export default OtherComponent;
I'm new in redux and don't know how to pass and run that function in other component. It was easy not using redux, just pass as props like in example above.
I have folder with actions, components, containers and reducers.
Now I have Main.js where I have
import React from "react";
const Main = ({data, getData}) => {
const myFunction = () => {
return "ok";
};
return (
<div>
<p>This is main component</p>
</div>
);
};
export default Main;
In MainContainer.js I got:
import Main from "../../components/Main/Main";
import { connect } from "react-redux";
import {
getData
} from "../../actions";
function mapStateToProps(state) {
return {
data: state.main.data
};
}
const mapDispatchToProps = (dispatch) => {
return {
getData: () => dispatch(getData())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Main);
So how I can run function myFunction() in OtherComponent.js:
import React from "react";
const OtherComponent = ({executeFunctionInMainComponent}) => {
return (
<div>
<button onClick={executeFunctionInMainComponent}>run action</button>
</div>
);
};
export default OtherComponent;
I need to just run, not pass whole function, just execute myFunction in Main.js but action to run this function will came from OtherComponent.
So first i have to mention that i believe that you have a misconception of redux. This isn't to allow for functions created in components to be reused in different locations. This is to move that logic to a reducer outside of your function which would allow it to be used wherever you wired it with {connect} from react-redux. So you will need a couple of files (for clarity). First you're going to need an action file which we'll name myReturnOkAction.
export const myReturnOkAction = (/*optional payload*/) => {
return {
type: 'PRINT_OK',
}
}
Redux Actions
This is what you're going to call in your mapDispatchToProps function where you're going to trigger this event. You're going to have to import it into your OtherComponent so import {myReturnOkAction} from "/*wherever the files exists*/" and to include it in your mapDispatchToProps as okFunction: () => dispatch(myReturnOkAction())
Once you have your action your connect Higher Order Component (HOC) wrapping your main component is going to need a Reducer to modify your current store state as well as do any actions.
export const myReturnOkReducer = (state, action) => {
if(action.type === 'PRINT_OK'){
/*This is where you update your global state*/
/*i.e. return {...store, valueWanted: ok}*/
}else{
return state
}
}
Redux Reducers
So the way that this is going to move is that you're function, somewhere is going to call the action. Once the action is called its going to trigger the reducer and make any changes to the store which you need. Once the reducer has updated the store with new values its then going to update any components which are connected to it through the connect HOC which will cause them to re-render with new information.
Also my favorite image to describe how redux works.
I hope this helps.
I found an answer:
I still can pass as props in redux but I can't do this in this way: OtherComponent = ({executeFunctionInMainComponent}) => {}. I need to do in this way: OtherComponent = (props) => {} and then inside that component I have an access via props.executeFunctionInMainComponent

Retrieving a Redux state, but also change another Redux state in same React Native component?

I have a React component that currently just retrieves a state from Redux. Here is the general layout:
const mapStateToProps = state => {
return { stuff: state.stuff };
};
class MyComponent extends React.Component {
// use 'stuff' from redux to build the Views
}
export default connect(mapStateToProps)(MyComponent);
But now, what if I want to add a button that changes another Redux state called other?
To save the new Redux state, I know we have to create a dispatch to the action. ie,
const mapDispatchToProps = dispatch => {
....
};
Then finally connect them:
connect(null, mapDispatchToProps)(MyComponent);
But my confusion is if I am already connecting with mapStateToProps, how can I also map it to mapDispatchToProps so that I can update the Redux state in the same component?
You can use both ;-)
For example :
Action.js
export const textChanged = (newText) => {
return { type: "TEXT_CHANGED", newText }
};
HomeScene.js :
import { textChanged } from "../actions;
...
render () {
const { myText } = this.props;
<TextInput
value={myText}
onChangeText={(newText) => this.props.textChanged(newText)}
/>
}
function mapStateToProps(state) {
return {
myText: state.appContent.myText
};
}
export default connect(mapStateToProps, { textChanged })(HomeScene);
Reducer.js
case "TEXT_CHANGED":
return {
...state,
myText: action.newText
};
Hope it helps !
Hm, looks like I asked too early. I did a bit of reading and the parameters in connect() actually accepts both.
So like this:
connect(mapStateToProps, mapDispatchToProps)(MyComponent)

Uncaught TypeError: this.props.dispatch is not a function

I am trying to dispatch an action when I submit a form but I'm getting this:
Uncaught TypeError: this.props.dispatch is not a function
This is my class:
/**
*
* CatalogPage
*
*/
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { Form, Control } from 'react-redux-form/immutable';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import makeSelectCatalogPage from './selectors';
import reducer from './reducer';
import saga from './saga';
export class CatalogPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
handleSubmit = (user) => {
this.props.dispatch({ type: 'test action' });
}
render() {
return (
<Form
model="user"
onSubmit={(user) => this.handleSubmit(user)}
>
<label htmlFor=".firstName">First name:</label>
<Control.text model=".firstName" id=".firstName"/>
<label htmlFor=".lastName">Last name:</label>
<Control.text model=".lastName" id=".lastName"/>
<button type="submit">
Finish registration!
</button>
</Form>
);
}
}
CatalogPage.propTypes = {};
const mapStateToProps = createStructuredSelector({
catalogpage: makeSelectCatalogPage(),
});
function mapDispatchToProps(dispatch) {
return {
dispatch,
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'catalogPage', reducer });
const withSaga = injectSaga({ key: 'catalogPage', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(CatalogPage);
I thought that the compose function at the bottom would connect my component to the store and thus have access to the dispatch function through this.props.dispatch. But it's not working, what am I missing?
Thanks!
EDIT: I've changed handleSubmit to arrow function but problem still persists
handleSubmit = (user) => {
this.props.dispatch({ type: 'test action' });
}
EDIT: The problem resolved itself
Something to mention is that react-boiler-plate is not as user-friendly as one might expect. There's alot of weird things that are happening and took me a long time to debug.
The problem here is the misunderstanding of a class method and the way that React manages instances.
You can do three things to avoid this problem:
1) Convert the (handleSubmit) function to an arrow function so in that case, it won't have its own this.
handleSubmit = (user) => { // ...logic here }
2) Create a constructor inside the component and do the next step:
this.handleSubmit = this.handleSubmit.bind(this)
In this case, you attach this to the function each time an instance is created.
3) When you call the method inside the render use the .bind() to bind this:
onSubmit={(user) => this.handleSubmit.bind(this, user)}
You don't need mapDispatchToProps if you just want to inject dispatch into your component. You would use that when you want to bind your action creators before injecting them into your component. Just pass mapStateToProps with no second param and you should be good.
You also need to do what Jose Gomez suggests below. Basically you need to bind this. The easiest way is to just change handleSubmit to an arrow function
handleSubmit = user => {
...
}

Resources