I'm doing a small project with Redux and I'm running into the issue of an undefined action.type and I can't seem to find out what is causing it. I already cut out a lot of code to simply try to dispatch the 'LIKE' action.
My reducer looks like this:
import blogService from '../services/blogs'
const blogReducer = (state = [], action) => {
switch (action.type) {
case 'LIKE': {
// const newState = state.filter(x => x)
// const likedBlogIndex = state.findIndex(x => x.id === action.data.id)
// newState[likedBlogIndex] = { ...state[likedBlogIndex], likes: newState[likedBlogIndex].likes + 1 }
return action.type
}
case 'INIT':
return action.data
default: return state
}
}
export const initBlogs = () => {
return async dispatch => {
const blogs = await blogService.getAll()
dispatch({
type: 'INIT',
data: blogs.sort((a, b) => (b.likes - a.likes))
})
}
}
export const likeBlog = () => {
return async dispatch => {
// const likedBlog = await blogService.update(blog)
dispatch({
type: 'LIKE',
// data: { id: likedBlog.id, votes: likedBlog.votes }
data: ''
})
}
}
export default blogReducer
And the component from where the function is called that dispatches the action looks like this:
/* eslint-disable linebreak-style */
import React from 'react'
import Togglable from './Togglable'
import { useSelector, useDispatch } from 'react-redux'
import likeBlog from '../reducers/blogReducer'
const BlogList = ({ handleDelete }) => {
const blogs = useSelector(state => state.blogs)
const dispatch = useDispatch()
return(
blogs.map(blog =>
<div key={blog.id}>
<div className='blog'>
{blog.title} {blog.author}
</div>
<Togglable showLabel='show' hideLabel='hide'>
<p>
<b>url:</b> {blog.url}
</p>
<p>
<b>likes:</b> {blog.likes} <button onClick={() => dispatch(likeBlog())}>Like</button>
</p>
<p>
<b>id:</b> {blog.id}
</p>
<button onClick={(e) => handleDelete(e, blog)}>remove</button>
</Togglable>
</div>
)
)
}
export default BlogList
For the record: the error I'm facing is that action.type at line 4 of my reducer is undefined. I really hope someone spots the mistake. Thanks.
In this line you take the default export from the blogReducer file and name it likeBlog
import likeBlog from '../reducers/blogReducer'
But the default export is the reducer, so you are renaming the reducer and then using it as an action creator.
What you really want is import the named export from that file, like
import { likeBlog } from '../reducers/blogReducer'
that should work.
PSA: you are writing a pretty outdated style of redux here, it is very possible that you are following an outdated tutorial and will learn a style of redux that required multiple times the amount of code modern redux would requre. Please follow the official redux tutorials found at https://redux.js.org/tutorials/index
Related
I am trying to trying to get my textbox to update the word count when the user types something in the box. But the setWordCount action is not getting passed to the reducer. I am at a loss for why this isn't working.
In troubleshooting, I confirmed that the component is pulling the initial word count off state the way it's supposed to. I also confirmed that setWordCount is getting called when the user types something. This should trigger off the action which passes the updated word count to state, but it's not firing. I am not getting any errors in the console and none of my middleware loggers is firing.
This is my component.
import React from 'react';
import { connect } from 'react-redux';
import { setWordCount } from '../../redux/writing/writing.actions';
import { UpdateWordCount, UpdatePercentage } from '../../redux/writing/writing.utils';
import './writing-text-box.styles.scss';
const WritingTextBox = ({wordCount, goal}) => {
var updatedPercentage = UpdatePercentage(wordCount, goal);
var percent = updatedPercentage + '%';
return (
<div className='writing-box-container'>
<textarea className='writing-box' type='text' onChange={
(e) => {
setWordCount(UpdateWordCount(e.target.value));
}
}
/>
<div id='Progress'>
<div id='Bar' style={{width: percent}}></div>
</div>
<p key='writing-box-word-count' className='wordcount' >
{wordCount} / {goal}</p>
</div>
)}
const mapStateToProps = state => ({
wordCount: state.writing.wordCount,
goal: state.writing.goal,
percentage: state.writing.percentage
});
const mapDispatchToProps = dispatch => ({
setWordCount: ({wordCount}) => dispatch(setWordCount(wordCount)),
// setPercentage: percentage => dispatch(setPercentage(percentage)),
});
export default connect(mapStateToProps, mapDispatchToProps)(WritingTextBox);
This is my actions file, which is nearly copy-pasted from another app that works:
import WritingTypes from "./writing.types";
export const setWordCount = wordCount => ({
type: WritingTypes.SET_WORD_COUNT,
payload: wordCount,
});
and my reducer:
import WritingTypes from "./writing.types";
const INITIAL_STATE = {
wordCount: 0,
goal: 124,
percentage: 0
}
const writingReducer = (currentState = INITIAL_STATE, action) => {
switch(action.type) {
case WritingTypes.SET_WORD_COUNT:
return {
...currentState,
wordCount: action.payload
};
default:
return currentState;
}
}
export default writingReducer;
and my root Reducer:
import { combineReducers } from "redux";
import writingReducer from "./writing/writing.reducer";
const rootReducer = combineReducers ({
writing: writingReducer
})
export default rootReducer;
You need to be a little more careful with the namings. currently, setWordCount is the name of:
the action creator, which simply creates the action object.
export const setWordCount = wordCount => ({
type: WritingTypes.SET_WORD_COUNT,
payload: wordCount,
});
the prop that dispatches the action.
setWordCount: ({wordCount}) => dispatch(setWordCount(wordCount)),
in here it should be the second one:
<textarea className='writing-box' type='text' onChange={
(e) => {
setWordCount(UpdateWordCount(e.target.value));
}
}
/>
to make it work, destructure it from props:
const WritingTextBox = ({wordCount, goal,setWordCount}) => {
now it points to the prop, and works as expected.
I am try to create React app and connect with Redux but there is an error when i try to update state after i fetch data from service api.
Please HELP.
Error description:
I share the 3 files that i think is importent to see for solving this issue.
questionReducers.ts file
import {createAction, createReducer} from '#reduxjs/toolkit';
import {fetchInitData} from './questionAPI';
import {AnswerInterface} from "../types/AnswerInterface";
import {Questions} from "../types/Questions";
import {RootState} from "./store";
interface QuestionsState {
data: Questions,
status: 'loading' | 'loaded' | 'error'
}
const initialState = {
data: {
question: {
text: '',
imageURL: ''
},
answers: [],
},
status: 'loading',
} as QuestionsState
export const appState = (state: RootState) => state;
export const initData = createAction('app/init')
export const addAnswer = createAction<AnswerInterface>('question/addAnswer')
export const questionReducer = createReducer(initialState, (builder) => {
builder
.addCase(initData, (state) => {
fetchInitData().then((res) => {
state.data = res;
state.status = 'error';
}).catch(() => {
state.status = 'error';
});
})
.addCase(addAnswer, (state, action) => {
state.data.answers.push(action.payload);
localStorage.setItem('data', JSON.stringify(state.data));
})
})
questionAPI.ts file
export function fetchInitData() {
return fetch('https://example.com/data.json')
.then(response => response.json())
.then(response => {
return response[0];
})
}
App.tsx file
import React, {useEffect, useState} from 'react';
import styles from './App.module.css';
import { useAppSelector, useAppDispatch } from './app/hooks'
import {initData, appState} from "./app/questionRedeucer";
const App:React.FC<any> = () => {
const state = useAppSelector(appState);
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(initData());
});
return (
<div className={styles.App}>
{state.status === 'loading' && <div>Loading...</div>}
{state.status === 'loaded' && <div>Loaded...</div>}
</div>
);
}
export default App;
You're trying to do async work in a reducer, which is wrong - a reducer must never contain any async logic!
Technically the localStorage.setItem() is also a side effect and does not belong in a reducer, although in practice it's not a big deal.
Please move that async logic out into a thunk instead:
https://redux.js.org/style-guide/style-guide#reducers-must-not-have-side-effects
https://redux.js.org/tutorials/essentials/part-5-async-logic
Hi im new to redux and im trying to create a movie app using the API from www.themoviedb.org. I am trying to display the popular movies and im sure the API link works since ive tested it in postman but i cant seem to figure out why redux doesnt pick up the data.
//action
import { FETCH_POPULAR } from "./types";
import axios from "axios";
export const fetchPopularMovies = () => (dispatch) => {
axios
.get(
`https://api.themoviedb.org/3/movie/popular?api_key=${API}&language=en-US`
)
.then((response) =>
dispatch({
type: FETCH_POPULAR,
payload: response.data
})
)
.catch((err) => console.log(err));
};
//reducer
import { FETCH_POPULAR } from "../actions/types";
const initialState = {
popular: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POPULAR:
return {
...state,
popular: action.payload,
};
default:
return state;
}
}
import React from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
export default connect(mapStateToProps)(FetchedPopular);
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.poster_path}`}
/>
</div>
);
};
export default Popular;
I cant really tell what I'm missing can someone help?
Next to mapStateToProps you need to create mapDispatchToProps. After that, you will be able to call your Redux action from your React component.
I suggest you the mapDispatchToProps as an Object form. Then you need to use this mapDispatchToProps as the second parameter of your connect method.
When you will have your action mapped to your component, you need to call it somewhere. It is recommended to do it for example on a component mount. As your React components are Functional components, you need to do it in React useEffect hook.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
import { fetchPopularMovies } from 'path_to_your_actions_file'
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
useEffect(()=> {
// call your mapped action (here it is called once on component mount due the empty dependency array of useEffect hook)
props.fetchPopularMovies();
}, [])
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
// create mapDispatchToProps
const mapDispatchToProps = {
fetchPopularMovies
}
// use mapDispatchToProps as the second parameter of your `connect` method.
export default connect(mapStateToProps, mapDispatchToProps)(FetchedPopular);
Moreover, as I wrote above in my comment, your Popular does not have the prop poster_path but it has the prop popular which probably has the property poster_path.
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.popular.poster_path}`}
/>
</div>
);
};
export default Popular;
I'm learning redux hooks from library "react-redux" because I need to apply Redux also in the functional components of my project.
So far I don't understand how can be used the same project structure of the redux HOC with connect that I use for the class components.
Specifically I have a separate action file which invoke my API with axios:
FoosActions.js
import axios from "axios";
import {
GET_FOO,
} from "./Types";
};
export const getFoo = () => async (dispatch) => {
const res = await axios.get("/api/v1/foos");
dispatch({
type: GET_FOO,
payload: res.data,
});
};
FooList.js:
import { connect } from "react-redux";
import { getFoos } from "../../actions/FoosActions";
class FoosList extends Component {
constructor() {
super();
this.state = {
errors: {},
};
}
componentDidMount() {
this.props.getFoos();
}
render() {
const { data } = this.props.foo;
return (
<div className="container">
<h2>foo data fetched from API endpoint : </h2>
<ul>
{data.map((foo) => {
return (
<li>
{foo.id} - {foo.name}
</li>
);
})}
<ul>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({
foo: state.foo,
errors: state.errors,
});
export default connect(mapStateToProps, { getFoos })(FooList);
FooReducer,js
import { GET_FOO} from "../actions/Types";
const initialState = {
foos: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_FOO:
return {
...state,
foos: action.payload,
};
Now instead in my Functional Component:
FooListFC.js
import { useDispatch, useSelector } from "react-redux";
import { getFoo } from "../../actions/FoosActions";
const Mapping = (props) => {
const [foo, setFoo] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getFoo());
const fooRetrieved = useSelector((state) => state.foo);
setFoo(fooRetrieved);
}, []);
return (
<div className="container">
<h2>foo data fetched from API endpoint : </h2>
<ul>
{foo.map((foo) => {
return (
<li>
{foo.id} - {foo.name}
</li>
);
})}
</ul>
</div>
)
}
How can I reproduce the same behavior of fetching data from API in class component with actions in a different file and using redux hooks (my code in the functional component is not working) ?
Is it a bad practice having both approaches in the same project?
you are able to reproduce the same behaviour, in the function component you can use the selector only instead of both useSelector and useState:
const Mapping = (props) => {
const foo = useSelector((state) => state.foo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getFoo());
}, []);
...
I am trying to learn the react and for that I am trying to create a sample todo app. I have a python flask backend which servers as REST server and react as web server.
Everything works find an I am able to show todos and delete particular todo as well. However now I have started learning Redux, and that seems really confusing.
I am not sure how to make call to my rest server. Following just returns promise, not sure how to get the data, rather than promise.
store.js
import axios from 'axios'
import { createStore } from 'redux'
export const ADD_TODO = 'ADD_TODO'
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const listTodo = todos => ({
type: 'LIST_TODO',
todos
})
const add_todo = (id, text) => {
return axios.post("http://localhost:5001/todos", {id:id, data:text})
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(listTodo(Response.data))
})
}
const initialState ={
todos: {},
new_todo: ''
}
function todoApp(state = initialState, action) {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
new_todo: action.text
})
default:
return state
}
}
const store = createStore(todoApp)
export default store
app.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo('testing')}>fetch_Data</button>
</div>
);
}
}
export default connect() (App)
index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>,
document.getElementById('root'));
Firstly, you should export the actions you have created which will then be imported and used in the components using the connect HOC.
You can dispatch the 'fetch_data' action to get the data in your component. Also, you can dispatch 'addTodo' action to add new todo in the list.
export const ADD_TODO = 'ADD_TODO';
export const GET_TODO = 'GET_TODO';
export const fetch_data = () => {
return (dispatch) => axios.get("http://localhost:5001/todos")
.then(response => {
dispatch({type: GET_TODO, todos: response.data});
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
});
Use the actions constants like ADD_TODO, GET_TODO to save or to update the redux state in reducers
const todoApp = (state = initialState, action) => {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
const todos = {...state.todos};
todos[action.id] = action.text;
return Object.assign({}, state, {
todos: todos
});
case GET_TODO:
return Object.assign({}, state, {
todos: action.todos
});
default:
return state
}
}
Importing the actions and then call the function you have added in the 'mapDispatchToProps' to dispatch the actions.
import React, {Component} from 'react'
import {connect} from 'react-redux';
import { addTodo, fetch_data } from "../store";
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo(todoId, 'testing')}>fetch_Data</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
todos: state.todoApp.todos
});
const mapDispatchToProps = (dispatch) => ({
addTodo: (id, text) => dispatch(addTodo(id, text)),
fetch_data: () => dispatch(fetch_data())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux is based on actions and reducers, basically reducers are pure functions which means no side effects as for example api calls, I'd advice you read more about redux and how to use redux with redux-chunk for making api calls
You make this work like this. You need to dispatch action when you have response.
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
})