I have a snippet of code that I want to use to call an api, but before show a loading screen. For some reason in my code below I cannot seem to get the action REQUEST_GAMES to hit my combined gameReducer. I have included all the code below. Any reason as to why the action type is not being picked up by the reducer? I am not sure what i am doing wrong. Have i not connected it to my component correctly? The redux logger is showing that action is being called.
AddGame.js (component)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
import { getIsFetching, getVisibleGames } from '../reducers'
class AddGame extends Component{
constructor(props){
super(props);
this.state = {term: ''}
this.onInputChange = this.onInputChange.bind(this)
this.onFormSubmit = this.onFormSubmit.bind(this)
}
onInputChange(event){
this.setState({term:event.target.value})
}
onFormSubmit(event){
const { requestGames, fetchGames } = this.props;
event.preventDefault();
// we need to go fetch weather data
requestGames();
fetchGames(this.state.term);
this.setState({term:'' })
}
renderContent(){
const { isFetching , games} = this.props
if (isFetching && !games.length){
return <p>Loading</p>
} else if (games){
return <div>{games.map(this.getGame)}</div>
}
}
styleCSS = {
padding:'20px'
};
getGame(data){
return(
<div>
<pre key={data.id}>{data.name}</pre>
<img src ={data.cover.url} alt = "" />
</div>
)
}
render(){
return(
<div style={this.styleCSS}>
<form onSubmit={this.onFormSubmit}>
<input
value={this.state.term}
onChange={this.onInputChange}/>
<button type="submit">
Search
</button>
{this.renderContent()}
</form>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isFetching :getIsFetching(state),
games:getVisibleGames(state),
}
}
export default connect(mapStateToProps,actions)(AddGame);
index.js (reducer)
import { combineReducers } from 'redux';
import authReducers from './authReducer';
import { reducer as formReducer } from 'redux-form';
import gameReducer, * as fromGames from './gameReducer';
const allReducers= combineReducers({
auth: authReducers,
form: formReducer,
game: gameReducer
});
export default allReducers;
export const getIsFetching = (state) => fromGames.getIsFetching(state.game);
export const getVisibleGames = (state)=> fromGames.getGames(state.game)
gameReducer.js
import { REQUEST_GAME, FETCH_GAME } from '../actions/types';
import { combineReducers } from 'redux';
const gameReducer = () => {
const games = (state=[], action) => {
console.log(action);
switch(action.type){
case FETCH_GAME:
return action.payload || false;
default:
return state;
}
};
const isFetching = (state = false, action) => {
switch (action.type) {
case REQUEST_GAME:
return true;
case FETCH_GAME:
return false;
default:
return state;
}
};
return combineReducers({
games,
isFetching
});
};
export default gameReducer;
export const getIsFetching = state => state.isFetching
;
export const getGames = state => state.games;
actions.js
import axios from 'axios';
import { FETCH_USER, REGISTER_USER, FETCH_GAME, REQUEST_GAME } from './types';
export const fetchUser = ()=> async dispatch=>{
const res = await axios.get('/api/current_user');
dispatch({type:FETCH_USER, payload:res.data});
console.log('fetchuser:',res.data)
};
export const fetchGames = (search)=> async dispatch=>{
const proxy = 'https://still-eyrie-36200.herokuapp.com/'
const res = await axios.get(`${proxy}https://api-2445582011268.apicast.io/games/?search=${search}&fields=name,category,genres,game_modes,cover,first_release_date,summary`,{
headers: {
'user-key':'18430b84d6bfaab720b08eeda8f2810d',
'Accept':'application/json',
'Content-Type':'application/json',
}
})
dispatch({type:FETCH_GAME, payload:res.data});
console.log('gamedata:',res.data)
};
export const requestGames = () =>({
type: REQUEST_GAME
})
The REQUEST_GAME action is probably processed by the reducer. After requestGame(), fetchGames() is called immediately. fetchGames() changes the state back to false. And both these actions happen in the same function block. So, there is no chance for the prop changes to cause a component re-render.
Related
I am creating a react app with redux for state management, I am facing issues when trying to dispatch and action, action is showing in redux devtools but it's not storing data to redux store not sure why it's happening, very unusual
If anyone knows why this happens please do let me know
My component is below
import axios from "axios";
import React, { Component } from "react";
import { connect } from "react-redux";
import { SETDATA } from "./store";
class Hello extends Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
this.firstdispatch();
}
firstdispatch = () => {
axios.get("https://jsonplaceholder.typicode.com/users").then((r) => {
console.log("data fetched", r.data);
this.props.setdata(r.data);
});
};
render() {
return (
<div>
{" "}
fff
{/* <button onClick={this.props.setdata}>getdata</button>
<button onClick={this.props.removedata}>decriment</button> */}
{/* <button onClick={props.push}>push</button>
<button onClick={props.pop}>pop</button> */}
{console.log(this.props)}
{this.props.users &&
this.props.users.map((m, i) => (
<div key={i}>
{m.title} {` - - - -`} {m.email}
</div>
))}
</div>
);
}
}
const mapstatetoprops = (state) => {
return {
users: state.users.users || [],
};
};
const mapDispatchTopProps = (dispatch) => {
return {
setdata: (users) => {
dispatch({ type: SETDATA, users });
},
};
};
export default connect(mapstatetoprops, mapDispatchTopProps)(Hello);
Actions reducers and store is below
updated
import * as redux from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
export const SETDATA = "users";
export const DELETEDATA = "data/deletedata";
const initSst = {
users: [],
};
const users = (state = initSst, action) => {
switch (action.type) {
case SETDATA:
return { ...state, ...action.data };
case DELETEDATA:
return { data: null };
default:
return state;
}
};
const rootReducer = redux.combineReducers({
users,
});
const store = redux.createStore(
rootReducer,
composeWithDevTools(
redux.applyMiddleware(thunk)
// other store enhancers if any
)
);
export default store;
Just update "SETDATA" to SETDATA in the switch/case
case SETDATA:
return { ...state, ...action.data };
once I updated the initial state to empty array its working
redux, actions, store
import * as redux from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
export const SETDATA = "users";
export const DELETEDATA = "data/deletedata";
const users = (state = [], action) => {
switch (action.type) {
case SETDATA:
return [...action.payload];
default:
return state;
}
};
const rootReducer = redux.combineReducers({
users: users,
});
const store = redux.createStore(
rootReducer,
composeWithDevTools(
redux.applyMiddleware(thunk)
// other store enhancers if any
)
);
export default store;
component
import axios from "axios";
import React, { Component } from "react";
import { connect } from "react-redux";
import { SETDATA } from "./store";
class Hello extends Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
this.firstdispatch();
}
firstdispatch = async () => {
await axios.get("https://jsonplaceholder.typicode.com/users").then((r) => {
// console.log("data fetched", r.data);
this.props.setdata(r.data);
});
};
render() {
return (
<div>
fff {console.log(this.props.users, "fff")}
{(this.props.users || []).map((m, i) => (
<div key={i}>
{m.title} {m.email}
</div>
))}
</div>
);
}
}
const mapstatetoprops = (state) => {
return {
users: state.users,
};
};
const mapDispatchTopProps = (dispatch) => {
return {
setdata: (users) => {
dispatch({ type: SETDATA, payload: users });
},
};
};
export default connect(mapstatetoprops, mapDispatchTopProps)(Hello);
I use the API to get Quotes, but I get an Unhandled Rejection error "(TypeError): this.props.message.map is not a function" https://ibb.co/dWqhjXK, I used debugger checked props saw that I get answers https://ibb.co/wM3mLb9 what then could be the reason? why am i getting this error? here is my code
Message.jsx
import React from 'react';
export class Message extends React.Component {
render() {
const list = this.props.message.map((item, index) => {
return <div key={index}>
<p>{item.content}</p>
</div>
});
return (
<div>
<p>{list}</p>
</div>
);
}
}
MessageContainer.js
import React from 'react';
import {connect} from "react-redux";
import {Message} from "./Message";
import {getMessageThunk} from "../../redux/message-reducer";
class MessageContainer extends React.Component {
componentDidMount() {
this.props.getMessageThunk();
}
render() {
return <Message {...this.props} />
}
}
const mapStateToProps = (state) => ({
message: state.message.users
})
export default connect(mapStateToProps, {getMessageThunk})(MessageContainer);
message-reducer.js
import {messageAPI} from "../Api/Api";
const MESSAGE = 'MESSAGE';
let initialState = {
users: [],
};
export const messageReducer = (state = initialState, action) => {
switch (action.type) {
case MESSAGE: {
return {...state, users: action.users}
}
default:
return state;
}
}
export const messageCreator = (users) => {
return {
type: MESSAGE, users
}
};
export const getMessageThunk = (users) => (dispatch) => {
messageAPI.getMessageAPI(users).then(response => {
dispatch(messageCreator(response.data));
})
}
Api.js
import * as axios from "axios";
const instance = axios.create({
withCredentials: true,
url: 'https://quotes15.p.rapidapi.com/quotes/random/',
headers: {
'x-rapidapi-key': 'bf490d72a0msh3bf159a87e0c27fp107a51jsn062ca1b9b00e',
'x-rapidapi-host': 'quotes15.p.rapidapi.com'
}
});
export const messageAPI = {
getMessageAPI() {
return instance.get(`https://quotes15.p.rapidapi.com/quotes/random/`)
},
};
Your message is not an array but an object. You do not have anything to iterate over there you could either simply return this.props.message.content but this is not a list.
const item = <p>{this.props.message.content}</p>
I'm new to React/Redux. I'm making an app using an API but the code doesn't work. When I run the code it says "this.props.recipes.map is not a function" and doesn't render anything.
If I change payload to: "payload: response.data.recipes" then the error changes to "Given action "FETCH_RECIPE", reducer "recipes" returned undefined." but no errors on screen (only in console). I thought writing "(state = [], action)" would solve the problem but it seems not. What's the problem and how do I fix this error?
Action Creator
import recipe from '../apis/recipe';
export const fetchRecipe = () => async dispatch => {
const response = await recipe.get('');
dispatch({ type: 'FETCH_RECIPE', payload: response.data })
};
Reducer
import { combineReducers } from 'redux';
const recipeReducer = (state = [], action) => {
switch(action.type) {
case 'FETCH_RECIPE':
return action.payload;
default:
return state;
}
};
export default combineReducers({
recipes: recipeReducer
});
import React from 'react';
import { connect } from 'react-redux';
import { fetchRecipe } from '../actions';
class Recipe extends React.Component {
componentDidMount() {
this.props.fetchRecipe();
console.log("This doesn't work", this.props.recipes)
}
renderList() {
return this.props.recipes.map(recipe => {
return (
<div>
<p>{recipe.publisher}</p>
</div>
)
})
}
render() {
console.log("First loaded: empty, second time: data fetched", this.props.recipes)
return (
<div>
{this.renderList()}
</div>
);
}
}
const mapStateToProps = (state) => {
return { recipes: state.recipes }
};
export default connect(mapStateToProps,{
fetchRecipe
})(Recipe);
API Request
import axios from 'axios';
import { key } from './config';
export default axios.create({
baseURL: `https://cors-anywhere.herokuapp.com/https://www.food2fork.com/api/search?key=${key}&q=pizza`
});
I recently started using redux for a new personal project. It worked pretty well until I started using "combineReducers". Whenever I click "Fetch todos" both my user as well as my todo reducer get updated and even though they have different data field names both get the same data. Now I probably did some wrong encapsulation here. But no matter how often I went over the docs, I just cannot see what I am doing wrong.
My store initialization script:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import toDoReducer from './todos/reducer';
import userReducer from './users/reducer';
const rootReducer = combineReducers({
todosSlice: toDoReducer,
usersSlice: userReducer
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
gets injected into index:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/app/App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
ReactDOM.render(<Provider store={ configureStore }><App /></Provider>, document.getElementById('root'));
serviceWorker.unregister();
My app hold the logic for the todo container
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as todoActions from '../../store/todos/actions';
import UserContainer from '../usersContainer/UserContainer';
class App extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
let loading = '';
let error = '';
let todos = [];
// check whether the component is fetching data
this.props.loading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.error && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the todos in the desired html markup.
todos = this.props.todos.map( todo => {
return <div key={todo.id}> name: {todo.title} </div>
});
return (
<div className="App">
{/* <UserContainer /> */}
{loading}
{error}
<p onClick={() => this.props.onFetchTodos()}>Fetch Todos</p>
{todos}
</div>
);
}
}
const mapStateToProps = state => {
return {
error: state.todosSlice.error,
loading: state.todosSlice.loading,
todos: state.todosSlice.todos
}
}
const mapDispatchToProps = dispatch => {
return {
onFetchTodos: () => dispatch(todoActions.fetchTodos())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Which has the following actions:
import axios from 'axios';
export const FETCH_TODOS = 'FETCH_TODOS';
export const GET_TODOS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(getTodoStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/teams/')
.then(result => {
dispatch(fetchTodosSucces(result));
}).catch(error => {
dispatch(fetchTodoFailure(error));
});
}
}
const getTodoStarted = () => ({
type: GET_TODOS_STARTED
});
const fetchTodosSucces = todos => ({
type: FETCH_TODOS_SUCCESS,
payload: {
...todos
}
});
const fetchTodoFailure = error => ({
type: FETCH_TODOS_FAILURE,
payload: {
error
}
});
export const fetchTodos = () => {
return (dispatch => {
dispatch(fetchRequest());
});
}
And it's reducer
import * as actions from './actions';
const initialState = {
error: null,
loading: false,
todos: []
}
const todosReducer = (state = initialState, action) => {
switch(action.type) {
case actions.GET_TODOS_STARTED: {
console.log('fetch todo state', state)
return {
...state,
loading: state.loading = true
};
}
case actions.FETCH_TODOS_SUCCESS: {
const todos = action.payload.data;
return {
...state,
loading: false,
todos: state.todos = todos
};
}
case actions.FETCH_TODOS_FAILURE: {
const error = action.payload.error;
return {
...state,
loading: false,
error: state.error = error
};
}
default: {
return state;
}
}
}
export default todosReducer;
The Users Component
import React from 'react';
import { connect } from 'react-redux';
import * as userActions from '../../store/users/actions';
class UserContainer extends React.Component {
render () {
let loading = '';
let error = '';
let users = [];
// check whether the component is fetching data
this.props.usersLoading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.usersError && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the users in the desired html markup.
users = this.props.users.map( user => {
return <div key={user.id}> name: {user.title} </div>
});
return (
<div className="Users">
{loading}
{error}
<p onClick={() => this.props.onFetchUsers()}>Fetch Users</p>
{users}
</div>
);
}
}
const mapStateToProps = state => {
return {
usersError: state.usersSlice.error,
usersLoading: state.usersSlice.loading,
users: state.usersSlice.users
}
}
const mapDispatchToProps= (dispatch) => {
return {
onFetchUsers: () => dispatch(userActions.fetchUsers())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
the user actions:
import axios from 'axios';
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(fetchUsersStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/me')
.then(result => {
dispatch(fetchUsersSuccess(result));
}).catch(error => {
dispatch(fetchUsersFailure(error));
});
}
}
export const fetchUsersSuccess = (users) => {
return {
type: FETCH_USERS_SUCCESS,
payload: {
...users
}
}
}
export const fetchUsersStarted = () => ({
type: FETCH_USERS_STARTED
});
export const fetchUsersFailure = (error) => {
return {
type: FETCH_USERS_FAILURE,
payload: {
error
}
}
}
export const fetchUsers = () => {
return dispatch => {
dispatch(fetchRequest())
}
};
And it's reducer:
import * as actions from './actions';
const initialState = {
error: '',
loading: false,
users: []
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case actions.FETCH_USERS_STARTED: {
console.log('fetch users state', state)
return {
...state,
loading: state.loading = true
}
}
case actions.FETCH_USERS_SUCCESS: {
const users = action.payload.data;
return {
...state,
loading: false,
users: state.users = users
}
}
case actions.FETCH_USERS_FAILURE: {
const error = state.payload.error;
return {
...state,
loading: false,
error: state.error = error
}
}
default: {
return state;
}
}
}
export default userReducer;
Now when I run my DEV server I only see the fetch todo button. I commented out the users on click handler to see if it was an event bubble going up. Bu t this wasn't the case.
Once the app load redux dev tools shows the state as follows:
but once i click the fetch todo's handler. Both todos and users get filled.
I appreciate anyone who read though so much (boilerplate) code. I probably made a problem encapsulating my state. but again after reading many tutorials I still cannot find my issue.
You have a copy/paste issue. You changed the names of the constants for your "USERS" actions, but left the values the same as the "TODOS" actions.
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
I assume you meant to have:
export const FETCH_USERS = 'FETCH_USERS';
export const FETCH_USERS_STARTED = 'FETCH_USERS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
**Hello! my problem is my state is not uploading, is always empty altough my actions brings data correct. Can anyone give me some help of what am I doing wrong ?
I think is something with the name or the combine reducers part.
Maybe I am not accesing data correct with my reducer or something like that **
The object I receive from the api call has this format {categories: Array(4), items: Array(50)}
Component
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ItemList from '../components/ItemList/ItemList';
import { getItems } from './actions'
class ItemListContainer extends PureComponent {
async componentDidMount() {
const { getItems } = this.props;
await getItems()
console.log(this.props)
}
render() {
return <ItemList />;
}
}
const mapStateToProps = state => (
{
items: state.items.items,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getItems,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(ItemListContainer);
actions.js
export const GET_ITEMS = 'GET_ITEMS';
export const GET_ITEM = 'GET_ITEM';
export const GET_ITEM_DESCRIPTION = 'GET_ITEM_DESCRIPTION';
export function getItems(query) {
return async function (dispatch) {
// const res = await fetch(`http://localhost:3000/api/items?q=${query}`)
const res = await fetch(`http://localhost:3000/api/items?q=ipad`)
const items = await res.json()
return dispatch({
type: 'GET_ITEMS',
items: items.items,
})
}
}
reducer.js
import { GET_ITEMS } from './actions';
const initialState = {
items: [],
itemsLoaded: false,
};
export default function(state = initialState, action) {
const { type, data } = action;
switch (type) {
case GET_ITEMS:
return {
...state,
items: data,
itemsLoaded: true,
};
default: {
return {
...state
}
}
}
}
I was accessing { data} in the reducer which of course it was empty. The correnct action was items.