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.
Related
I've been battling this all day long and I'd appreciate any help.
I have a redux store built with Redux Toolkit and createSlice that looks like so:
const initialState = {
analiticaNumber: "",
animal: {},
tests: [{ key: "G9116", value: "DERMATOFITOS PCR/ MUESTRA" }],
};
const PeticionSlice = createSlice({
name: "peticion",
initialState,
reducers: {
addTest: (state, action) => {
state.tests.push(action.payload);
},
},
});
export const { addTest: addTestActionCreator } = PeticionSlice.actions;
export const testsArray = (state) => state.Peticion.tests;
export default PeticionSlice.reducer;
I also have a root reducer that imports the rest of the slices and names them as such
import { combineReducers } from "redux";
import NavigationSlice from "./NavigationSlice";
const RootReducer = combineReducers({
Peticion: PeticionSlice,
});
export default RootReducer;
When I add tests to the tests array it works fine and shows in the redux devtools.
The promblem comes that react does not see the change in the store and won't update the child component:
import { testsArray } from "./Store/PeticionSlice";
That's how I import namely the testsArray to call with the useSelector.
The tradicional way of const { tests } = useSelector( (state) => state.Peticion) doesn't work either.
function App() {
const tests = useSelector(testsArray);
useEffect(() => {
console.log("tests");
}, [tests]);
return (
<StylesProvider injectFirst>
<div className="App">
<nav>
<Navbar />
</nav>
{tests.map((test) => (
<p>{test.key}</p>
))}
</div>
</StylesProvider>
);
}
I belive it has to do something with the mutability of the state, but I thought the toolkit took care of that, and for the life of me I don't know how to solve this.
Any help??? Thanks a lot.
** UPDATE **
I believe it has to do with the way I dispatch the actions. Because I needed to add several boundaries to what the app does, I decided to have an external function that filters and dispatches accordingly. It is not a react component.
import { configureStore } from "#reduxjs/toolkit";
import { addTestToList, addTestActionCreator } from "../Store/PeticionSlice";
import RootReducer from "../Store/RootReuder";
const PruebasToSubmitArray = [];
const store = configureStore({
reducer: RootReducer,
});
const handleTestList = (test, action) => {
const anatomia = "A";
const microbiologia = "M";
function oneBiopsia() {
while (test.key.toString().charAt(0) === anatomia) {
return PruebasToSubmitArray.some(
(pruebaInArray) => pruebaInArray.key.toString().charAt(0) === anatomia
);
}
return false;
}
if (!oneBiopsia() && action === "add") {
switch (test.key.toString().charAt(0)) {
case anatomia:
// console.log("Open pdf for anatomia");
store.dispatch(addTestActionCreator(test));
break;
case microbiologia:
// console.log("Open pdf for micro");
store.dispatch(addTestActionCreator(test));
break;
default:
// console.log("add test to the list, ", test);
store.dispatch(addTestActionCreator(test));
break;
}
} else if (action === "remove") {
// console.log("remove test from the list, ", test);
} else if (oneBiopsia()) {
console.log("Only one biopsia per peticion, ", newState);
}
return null;
};
export default handleTestList;
I added a button on App component and it worked as expected (i showed the updated state), as is right now redux updates the state but the component won't reflect it.
Code SandBox as complete as I can
Very odd behavior in my case.
I did
state = action.payload
and that didn't work.
Once I switched to
state.viewer = action.payload.viewer
everything worked!
Multiple Instances of Store
You create a store variable in your index.js file and pass that store to the react-redux Provider component. This is the store instance which all react-redux useSelector and useDispatch hooks will interact with.
In your HandleTestList.js file you create a different store variable. You then dispatch actions to that store, but those actions won't be reflected in your React app because this isn't the store that your React app uses.
handleTestList needs to either A) import the same global store variable. You will want to move this out of index.js and into store.js to avoid circular dependencies. or B) accept dispatch as an argument.
I am making a React-Native app in which I have a navigator from React Navigation and I also want to implement Redux. I am trying to create a global counter that updates based on an argument.
Here is the actions:
export const setFlags = (value) => {
return {
type: 'SETFLAGS',
value
}
}
export const setNonFlags = (value) => {
return {
type: 'SETNONFLAGS',
value
}
}
Here is the reducer, because its two things that have identical functionality I thought one would work (I am new to Redux):
const initialState = {
flags:0,
nonFlags:0,
}
const AllFlagReducer = (state = initialState, action) =>{
switch(action.type){
case 'SETFLAGS':
return state.flags = state.flags + action.value
case 'SETNONFLAGS':
return state.nonFlags = state.nonFlags + action.value
}
return state
}
export default AllFlagReducer
And here is the button where I would like to send the local state of the "flag" and "nonFlag" to the redux global states. After which I reset the local states and move to the next screen.
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags(in redux):this.state.flags
// dispatch nonFlags(in redux): this.state.nonFlags
() =>{this.resetAll();
navigation.navigate('Specific Scams')
}}>
Help would be greatly appreciated.
UPDATE 1:
The entire component:
class ScamTree extends React.Component {
constructor(props){
super(props)
this.state = {
flags : 0,
nonFlags: 0,
qAnswered:0
}
}
functions that might matter:
resetAll = () =>{
this.setState({flags:0})
this.setState({nonFlags:0})
this.setState({qAnswered:0})
}
the button, (I did not make a separate component for just the button):
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags:this.state.flags
// dispatch nonFlags: this.state.nonFlags
() =>{this.resetAll();store.dispatch({type:"SETFLAGS",value:5})
navigation.navigate('Specific Scams')
}}>
<Text style={{paddingHorizontal:40}}>NEXT</Text>
</TouchableOpacity>
the export to make React Navigation Work:
export default function(props) {
const navigation = useNavigation();
return <ScamTree {...props} navigation={navigation} />;
}
In your .js class you have to bind your action like this
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags(in redux):this.state.flags
// dispatch nonFlags(in redux): this.state.nonFlags
() =>{
this.resetAll();
this.props.commanAction.setFlags(your value);
this.props.commanAction.setNonFlags(your value);
navigation.navigate('Specific Scams')
}}>
const mapDispatchToProps = dispatch => {
return {
commanAction: bindActionCreators(commanAction, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Your .js className);
Other code are looks good.
If I understand a comment of yours in another answer, and the snippet of your question update, I believe you need to decorate this functional component:
export default function(props) {
const navigation = useNavigation();
return <ScamTree {...props} navigation={navigation} />;
}
You want to decorate this component with the connect HOC to connect it to your redux store.
First I'd convert that function to a new withNavigation HOC:
const withNavigation = WrappedComponent => props => {
const navigation = useNavigation();
return <WrappedComponent{...props} navigation={navigation} />;
};
withNavigation.displayName = `withNavigation(${WrappedComponent.displayName || 'Component'})`;
Now you can decorate ScamTree as follows:
export default withNavigation(ScamTree);
But now you also need to connect your action creators to your redux store, decorate it with react-redux's connect HOC:
const mapDispatchToProps = {
setFlags,
setNonFlags,
};
export default withNavigation(
connect(null, mapDispatchToProps)(ScamTree),
);
Note: I see it seems you store in local state some flag values, not sure if that is related, but you can map in initial state for this with a mapStateToProps as the first parameter for connect instead of null.
By now you've probably noticed that each new HOC creates some nesting, and this will get worse with the more HOC's used. The solution is to use redux's compose HOC. That's right, it's not just for composing middleware for the store. It can compose all the decorators into a single HOC to wrap your exported component, or in other words, it flattens/eliminates the nesting.
Tips
All compose does is let you write deeply nested function
transformations without the rightward drift of the code. Don't give it
too much credit!
...
import { compose } from 'redux';
...
class ScamTree extends Component { ... }
const mapDispatchToProps = {
setFlags,
setNonFlags,
};
export default compose(
withNavigation,
connect(null, mapDispatchToProps),
)(ScamTree);
Note: Your reducers need to always return new state object references and never mutate existing state. Also, although your returns do work correctly, the reducer pattern is to return unhandled action types in the default switch case:
const AllFlagReducer = (state = initialState, action) => {
switch(action.type){
case 'SETFLAGS':
return { ...state, flags: state.flags + action.value };
case 'SETNONFLAGS':
return { ...state, nonFlags: state.nonFlags + action.value };
default:
return state;
}
}
In your reducer cases right now you writing new state over the whole state instead just in the fit place at state object, it can be written like
const AllFlagReducer = (state = initialState, action) =>{
switch(action.type){
case 'SETFLAGS':
return { ...state, flags: state.flags + action.value }
case 'SETNONFLAGS':
return { ...state, nonFlags: state.nonFlags + action.value }
default: return state
}
}
export default AllFlagReducer
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.
I'm new to React-Redux and I'm trying to build a binding-like form using a Material-ui TextField and display his content in a div when any changes happened. There is my code:
TextField Component:
handleHead = event => {
store.dispatch(newTitle(event.target.value));
}
render() {
return (
<div>
<div style={MDStyle}>
<TextField style={dblock}
floatingLabelText='Title'
onChange={this.handleHead}
fullWidth={true}/>
</div>);
}
Reducer
import { combineReducers } from 'redux';
function markdown(state = [], action) {
switch (action.type) {
case 'NEW_TITLE':
return Object.assign({}, state, {
title: action.text
});
default:
return state;
}
}
export default combineReducers({ markdown });
Store
import { createStore } from 'redux';
import reducers from '../reducers/Reducers';
const initialState = {
markdown: {
title: ''
}
};
const store = createStore(reducers, initialState);
export default store;
The problem comes when I try to type something in the Input, it loses focus and I can't keep typing anything. Seems like the component re-renders. What's the easiest way to solve this? Considering that these kind of problems have been solved before in react-form or even refs, Can you give me some guidelines or best practices for solving this?
This is my Selector , I can able to get data with in the selector but don't know how to call this into view (Component) ,
import {todos} from '../reducers/todos';
import { createSelector } from 'reselect'
var visibilityFilter='SHOW_ALL';
var getVisibilityFilter = (state) => visibilityFilter;
var getTodos = (state) => todos;
export const getVisibleTodos = createSelector(
[ getVisibilityFilter, getTodos ],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
)
export default getVisibleTodos;
I have Tried in Component
<button onClick={()=>props.getVisibleTodos(props.SHOW_ALL , props.experimentData.lights)}> SHOW_COMPLETED</button>
Error
Uncaught Error: Actions must be plain objects. Use custom middleware
for async actions.
Blockquote
Help me Out ...
You should call the selector on the connect function like this:
import { connect } from 'react-redux';
import getVisibleTodos from 'your/selector/file';
function YourComponent({ visibleTodos }) {
// You can access visibleTodos inside your component
// because now it's on the props
return (
<div>
//...
</div>
);
}
const mapping = (state, props) => ({
visibleTodos: getVisibleTodos(state, props),
});
connect(mapping)(YourComponent);
Inside the mapping function, you have access to the state and props for the current component. Keep in mind that all selectors must receive the redux store in order to query the data.
Good luck!
I expect that your Redux store state looks something like this:
{
todos: [
{
id: 1,
text: 'Buy milk',
completed: false
},
...
],
visibilityFilter: 'SHOW_ALL'
}
If it is so, then you have to rewrite your getVisibilityFilter and getTodos selectors.
const getVisibilityFilter = (state) => state.visibilityFilter;
const getTodos = (state) => state.todos;
Previously you weren't accessing the values from your state, using this edited functions you are. See how I am using dot notation to access the keys of state, which is nothing more than a JavaScript Object.
Also, when u want to use a selector, you should use it in a container component, where u can access the store's state using mapStateToProps function.
The container could look something like this:
import React from 'react';
import { connect } from 'react-redux';
import { getVisibleTodos } from './selectors.js';
import TodosList from './TodosList.jsx';
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
const VisibleTodosList = connect(
mapStateToProps
)(TodosList);
export default VisibleTodosList;
Where the TodosList component is your own component that displays the todos. It will receive all visible todos using props (this.props.todos).
In my opinion, selectors aren't called from your view (presentational) components, they are meant to be used in containers, where you can access the application's data.
If you want to learn more about containers and presentational components, take a look at this article, it's worth reading.