Hello I am using thunks to get data from my backend
but I am unsure how to do it in my combine reducer
my types:
export const FETCH_SUCESS = 'FETCH_SUCESS';
export const FETCH_FAIL = 'FETCH_FAIL';
export const FETCH_LOADING = 'FETCH_FAIL';
export const FILTER_PRODUCT = 'FILTER_PRODUCT';
my action:
import api from '../../services/api';
import {FETCH_SUCESS,FETCH_FAIL,FETCH_LOADING} from '../constants/fetchTypes';
const fetchSucess = data => ({
type: FETCH_SUCESS,
payload: {
...data
}
});
const fetchStarted = () => ({
type: FETCH_LOADING
});
const fetchFailed = error => ({
type: FETCH_FAIL,
payload: {
error
}
});
export const fetchProduct = () => {
console.log('action')
return dispatch => {
dispatch(fetchStarted());
api
.get('/products')
.then(res => {
dispatch(fetchSucess(res.data));
})
.catch(err => {
dispatch(fetchFailed(err.message));
});
};
};
my reducer:
import {
FETCH_SUCESS,
FETCH_FAIL,
FETCH_LOADING,
} from '../constants/fetchTypes';
const initialState = {
loading: false,
data: [],
error: null
};
export default function productReducer(state = initialState, action) {
switch (action.type) {
case FETCH_LOADING:
return {
...state,
loading: true
};
case FETCH_SUCESS:
return {
...state,
loading: false,
error: null,
data: [...state.data, action.payload]
};
case FETCH_FAIL:
return {
...state,
loading: false,
error: action.payload.error
};
default:
return state;
}
}
my combiner:
import { combineReducers } from 'redux'
import productReducer from './productsFetch.reducer';
export default combineReducers({
});
my store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
my home.js
class HomeProducts extends Component {
componentDidMount() {
this.props.fetchData();
}
render() {
const productItems = this.props.products.map( product => (
<div className="col-md-4 pt-4 pl-2">
<div className = "thumbnail text-center">
<a href={`#${product.id}`} onClick={(e)=>this.props.handleAddToCard(e,product)}>
<p>
{product.name}
</p>
</a>
</div>
<b>{util.formatCurrency(product.price)}</b>
<button className="btn btn-primary" onClick={(e)=>this.props.handleAddToCard(e,product)}>Add to Cart</button>
</div>
)
)
return (
<div className="container">
<div className="row">
{productItems}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
console.log(state);
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch(fetchProduct())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeProducts);
I have doubt what to use in my combiner
to get the date and the mistakes How I have my loading,data, error
I don't know how I will do it in meu combine redux
I also don't know if I had the best practices in my action and my reducer
In your combiner file just add your reducers as key value pairs like so:
import { combineReducers } from 'redux'
import productReducer from './productsFetch.reducer';
// import anotherReducer from './yourPath';
export default combineReducers({
products: productReducer,
// anotherState: anotherReducer
});
Ideally you should import your actions and pass it your component through your connect method like so then you will be able to access it from your component as props.
import fetchProduct from './pathToYourActionFile';
const mapStateToProps = (state) => {
console.log(state);
};
const mapActionsToProps = {
fetchProduct: fetchProduct
};
export default connect(mapStateToProps, mapActionsToProps)(HomeProducts);
import thunkInject from 'redux-thunk-inject';
const mockStore = configureMockStore([thunkInject()]);
const store = mockStore(mockStore);
const wrapper = mount(<Provider store={store} />);
expect(wrapper).toMatchSnapshot();
to mock a store with thunk, you can inject it as a prop in a component. Or in a reducer, e.g.
import productReducer from '../productReducer';
import {
FETCH_SUCESS,
FETCH_FAIL,
FETCH_LOADING,
} from '../constants/fetchTypes';
describe('product reducer', () => {
it('Should handle FETCH_SUCCESS', () => {
expect(productReducer(store, FETCH_SUCCESS)
).toEqual({
loading: true
});
expect(productReducer(store, FETCH_FAIL).toEqual({
loading: false,
error: action.payload.error})
});
Related
I have just completed Learn Redux on Codecademy and want to that knowledge in practice. But I have an error. When I create extraReducers for updating the state to actual promise status it does not add information.
getUserSlice.js
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { fetchUserInfo } from '../../api';
export const loadUser = createAsyncThunk("getUser/loadUser",
async (arg, thunkAPI) => {
return await fetchUserInfo();
}
});
const sliceOptions = {
name: 'getUser',
initialState: {
info: [],
isLoading: false,
hasError: false,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(loadUser.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(loadUser.fulfilled, (state, action) => {
state.info.push(action.payload)
state.isLoading = false;
state.hasError = false;
})
.addCase(loadUser.rejected, (state, action) => {
state.isLoading = false;
state.hasError = true;
})
},
};
export const getUserSlice = createSlice(sliceOptions);
console.log(getUserSlice);
export const selectUserInfo = (state) => {
console.log(state);
return state;
};
export default getUserSlice.reducer;
api.js
export const fetchUserInfo = async () => {
const user = await fetch('http://localhost:5000/api/user');
const json = user.json();
return json;
}
App.js
import React from 'react';
import './App.css';
import {Container} from 'react-bootstrap';
import Achievement from './components/Achievement/Achievement';
import { useSelector } from 'react-redux';
import { selectUserInfo } from './features/getUser/getUserSlice';
const colors = ['#010626','#4d6396', '#5d1a87', '#5d1a87', '#5d1a87'];
function App() {
let color= colors[0];
const user = useSelector(selectUserInfo)
function changeColor() {
const newColor = `rgb(${Math.round(Math.random() *256)}, ${Math.round(Math.random() *256)}, ${Math.round(Math.random() *256)})`;
color = newColor;
}
return (
<div className="App" style={{ background: color }}>
<Container>
<h1 id="whoAmI">
Witaj na moim portfolio
{user}
</h1>
<button onClick={changeColor}>
Zmień kolor tła
</button>
<div className="col-lg-4 col-md-6 col-sm-12">
<Achievement title="Ukończenie The Web Developer Bootcamp" picture="https://res.cloudinary.com/syberiancats/image/upload/v1630317595/k9g0nox2fyexxawg8whu.jpg" />
</div>
</Container>
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import reportWebVitals from './reportWebVitals';
import { store } from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
reportWebVitals();
store.js
import { configureStore } from "#reduxjs/toolkit";
import getUserReducer from "./features/getUser/getUserSlice";
export const store = configureStore({
reducer: {
getUser: getUserReducer
}
})
Console.log of getUserSlice and state in the selector
Maybe you can use (builder) => {} function in extraReducer and you edit your loadUser because your Api.js already return json like code below:
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { fetchUserInfo } from '../../api';
export const loadUser = createAsyncThunk('getUser/loadUser', async (arg, thunkAPI) => {
return await fetchUserInfo();
});
const sliceOptions = {
name: 'getUser',
initialState: {
info: [],
isLoading: false,
hasError: false,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(loadUser.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(loadUser.fulfilled, (state, action) => {
state.info.push(action.payload)
state.isLoading = false;
state.hasError = false;
})
.addCase(loadUser.rejected, (state, action) => {
state.isLoading = false;
state.hasError = true;
})
},
};
export const getUserSlice = createSlice(sliceOptions);
console.log(getUserSlice);
export const selectUserInfo = (state) => {
console.log(state);
return state;
};
export default getUserSlice.reducer;
and you maybe forgot to add await before fetch, you should edit your fetching data in api.js into this below:
export const fetchUserInfo = async () => {
const user = await fetch('http://localhost:5000/api/user');
const json = user.json();
return json;
}
You can implicitly return and bypass the Promise result. Something like:
(arg, thunkAPI) => fetchUserInfo();
However, I would take the "by-the-book" way:
export const loadUser = createAsyncThunk("getUser/loadUser",
async (arg, tunkApi) => {
try {
const response = await fetchUserInfo();
return response;
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}
});
I'm new to redux and I can't figure out what I'm doing wrong
accessTokenActions.js file
import { getAccessToken } from '../../utils/spotifyAuth';
import * as types from '../consts/types';
export const fetchAccessTokenRequest = () => ({
type: types.FETCH_ACCESS_TOKEN,
});
export const fetchAccessTokenSuccess = (data) => ({
type: types.FETCH_ACCESS_TOKEN_SUCCESS,
payload: {
data,
},
});
export const fetchAccessTokenError = (error) => ({
type: types.FETCH_ACCESS_TOKEN_ERROR,
payload: {
error,
},
});
export const fetchAccessToken = () => async (dispatch) => {
dispatch(fetchAccessTokenRequest());
try {
const response = await getAccessToken();
if (!response) {
throw Error();
}
return dispatch(fetchAccessTokenSuccess(response.data));
} catch (err) {
return dispatch(fetchAccessTokenError(err.response.data));
}
};
accessTokenReducer.js file
import * as types from '../consts/types';
const initialState = {
accessToken: null,
isLoading: false,
error: null,
};
const accessTokenReducer = (state = initialState, action) => {
switch (action.type) {
case types.FETCH_ACCESS_TOKEN:
return {
...state,
isLoading: true,
};
case types.FETCH_ACCESS_TOKEN_SUCCESS:
return {
...state,
accessToken: action.payload.access_token,
};
case types.FETCH_ACCESS_TOKEN_ERROR:
return {
...state,
accessToken: null,
isLoading: false,
error: action.payload.error,
};
default:
return state;
}
};
export default accessTokenReducer;
index.js file
import { combineReducers } from 'redux';
import accessTokenReducer from './accessTokenReducer';
const rootReducer = combineReducers({
accessToken: accessTokenReducer,
});
export default rootReducer;
configureStore.js file
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index';
const configureStore = () => {
const middlewares = [thunk];
const store = createStore(
rootReducer,
compose(
applyMiddleware(...middlewares),
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
),
);
return store;
};
export default configureStore;
const/types file
export const FETCH_ACCESS_TOKEN = 'FETCH_ACCESS_TOKEN';
export const FETCH_ACCESS_TOKEN_SUCCESS = 'FETCH_ACCESS_TOKEN_SUCCESS';
export const FETCH_ACCESS_TOKEN_ERROR = 'FETCH_ACCESS_TOKEN_ERROR';
I also have a selectors file(which I think doesn't affect the code cause I don't call any functions from there)
I keep getting TypeError: Cannot read property 'type' of undefined error in my switch statement at the reducer function, I'm currently studying the redux documentation but I can't figure out what's going on
Any help would be greatly appreciated
I am learning react-redux, so now I am trying to create react-redux crud app, here is ny solution
Here is repo : repo demo
The button
<span className="delete_info" onClick={() => deleteComment(comment.id) }>Delete</span>
The action creator to delete element
export const removeComment = id =>{
return{
type: ActionTypes.DELETE_COMMENTS,
payload:id
}
}
// delete comments
export const deleteComment = id =>{
console.log('ids', id);
return dispatch =>{
dispatch(fetchCommentsRequest())
axios.delete(`/api/v1/todo/${id}`)
.then(response =>{
console.log('yeees mom', response.data)
dispatch(removeComment(id))
})
.catch(error =>{
const erroMsg =error.message;
console.log('eeeror', erroMsg)
dispatch(fetchCommentsFailure(erroMsg))
})
}
}
Here is my reducer
import * as ActionTypes from '../action-types'
const initialState ={
data:[],
error:'',
comments:[],
loading:false,
editing:false
}
const reducer = (state= initialState, action) => {
switch (action.type) {
case ActionTypes.FETCH_COMMENTS_REQUEST:
return{
...state,
loading: true,
}
case ActionTypes.FETCH_COMMENTS_SUCCESS:
return{
...state,
loading:false,
comments:action.payload,
error:''
}
case ActionTypes.FETCH_COMMENTS_FAILURE:
return{
...state,
loading:false,
error:action.payload
}
case ActionTypes.ADD_COMMENTS:
return{
...state,
comments:state.comments.concat(action.payload)
}
case ActionTypes.DELETE_COMMENTS:
return{
...state,
comments: state.comments.filter(comment =>comment.id !==action.payload)
}
case ActionTypes.EDIT_COMMENTS:
return{
...state,
comments: state.comments.map(comment =>comment.id === action.payload?{
...comment,
editing:!editing
}:comment)
}
default: // need this for default case
return state
}
}
export default reducer
Now when I click delete, I see on the console the ID from action creators, but the element is not removed
and no errors, what is wrong here?
There's a few knots; I made this simplified sandbox (mocking an api call) of how it should work:
https://codesandbox.io/s/wonderful-minsky-99xi4?file=/src/App.js:0-799
index
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunkMiddleware from "redux-thunk";
import rootReducer from "./rootReducer";
import App from "./App";
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
App
import React from "react";
import "./styles.css";
import { connect } from "react-redux";
import deleteRequest from "./deleteRequest";
const mapStateToProps = state => {
return {
comments: state.comments
};
};
const mapDispatchToProps = {
deleteRequest: deleteRequest
};
let App = ({ comments, deleteRequest }) => {
const makeDeleteRequest = id => {
deleteRequest(id);
};
return (
<div className="App">
{comments.map(comment => {
return (
<div key={comment.id}>
<p>{comment.text}</p>
<button onClick={() => makeDeleteRequest(comment.id)}>
Delete
</button>
</div>
);
})}
</div>
);
};
App = connect(
mapStateToProps,
mapDispatchToProps
)(App);
export default App;
reducer
const initialState = {
data: [],
error: "",
comments: [{ id: 1, text: "test1" }, { id: 2, text: "test2" }],
loading: false,
editing: false
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case "DELETE_COMMENT":
return {
...state,
comments: state.comments.filter(comment => comment.id !== action.id)
};
default:
return state;
}
}
export default rootReducer;
async action
import deleteComment from "./deleteComment";
const mockAPI = new Promise(function(resolve, reject) {
setTimeout(() => resolve("deleted"), 2000);
});
const deleteRequest = id => {
return dispatch => {
const makeDeleteRequest = async () => {
await mockAPI;
dispatch(deleteComment(id));
};
makeDeleteRequest();
};
};
export default deleteRequest;
delete comment action
export default function deleteComment(id) {
return { type: "DELETE_COMMENT", id };
}
It looks like you're not actually dispatching the action, you're just returning an async action creator (deleteComment function). In order your code to work, you need to first add redux-thunk middleware to your redux store (so that you can use async action creators) and then, in your component, when you're calling deleteComponent, you have to wrap the call using redux dispatch.
If you're using a function component, you can add useDispatch hook and have something like:
import {useDispatch} from "react-redux";
// ...
function MyComponent() {
const dispatch = useDispatch();
// ...
return <span className="delete_info" onClick={() => dispatch(deleteComment(comment.id))}>Delete</span>
}
or you can just use the connect function to create a HOC and pass the dispatch function from the provider's context:
const ConnectedComponent = connect(undefined, dispatch => ({dispatch}))(MyComponent);
function MyComponent({dispatch}) {
return <span className="delete_info" onClick={() => dispatch(deleteComment(comment.id))}>Delete</span>;
}
I don't know how to load the data of the fetchLatestAnime action in the react app.js file.
My mission is to show the endpoint data that I am doing fetch.
I have already implemented the part of the reducers and action, which you can see in the part below. The only thing I need is to learn how to display the data.
App.js
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
</div>
);
}
export default App;
actions/types.js
export const FETCHING_ANIME_REQUEST = 'FETCHING_ANIME_REQUEST';
export const FETCHING_ANIME_SUCCESS = 'FETCHING_ANIME_SUCCESS';
export const FETCHING_ANIME_FAILURE = 'FETCHING_ANIME_FAILURE';
actions/animesActions.js
import{
FETCHING_ANIME_FAILURE,
FETCHING_ANIME_REQUEST,
FETCHING_ANIME_SUCCESS
} from './types';
import axios from 'axios';
export const fetchingAnimeRequest = () => ({
type: FETCHING_ANIME_REQUEST
});
export const fetchingAnimeSuccess = (json) => ({
type: FETCHING_ANIME_SUCCESS,
payload: json
});
export const fetchingAnimeFailure = (error) => ({
type: FETCHING_ANIME_FAILURE,
payload: error
});
export const fetchLatestAnime = () =>{
return async dispatch =>{
dispatch(fetchingAnimeRequest());
try{
let res = await axios.get('https://animeflv.chrismichael.now.sh/api/v1/latestAnimeAdded');
let json = await res.data;
dispatch(fetchingAnimeSuccess(json));
}catch(error){
dispatch(fetchingAnimeFailure(error));
}
};
};
reducers/latestAnimeReducers.js
import {
FETCHING_ANIME_FAILURE,
FETCHING_ANIME_REQUEST,
FETCHING_ANIME_SUCCESS
} from '../actions/types';
const initialState = {
isFetching: false,
errorMessage: '',
latestAnime: []
};
const latestAnimeReducer = (state = initialState , action) =>{
switch (action.type){
case FETCHING_ANIME_REQUEST:
return{
...state,
isFetching: true,
}
case FETCHING_ANIME_FAILURE:
return{
...state,
isFetching: false,
errorMessage: action.payload
}
case FETCHING_ANIME_SUCCESS:
return{
...state,
isFetching: false,
latestAnime: action.payload
}
default:
return state;
}
};
export default latestAnimeReducer;
reducers/index.js
import latestAnimeReducers from './latestAnimeReducers'
import {combineReducers} from 'redux';
const reducers = combineReducers({
latestAnimeReducers
});
export default reducers;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import resolvers from './redux/reducers/index';
import {createStore , applyMiddleware} from 'redux';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
const REDUX_DEV_TOOLS = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(resolvers , REDUX_DEV_TOOLS)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
Ideally, this is how your app.js should look like. I created a working codesandbox for you here. Your initial latestAnime state was an empty array but the action payload you set to it is an object, so remember to pass payload.anime like i have done in the sandbox.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchLatestAnime } from "./redux/actions/animesActions";
const App = props => {
const { fetchLatestAnime, isFetching, latestAnime, errorMessage } = props;
useEffect(() => {
fetchLatestAnime();
}, [fetchLatestAnime]);
console.log(props);
if (isFetching) {
return <p>Loading</p>;
}
if (!isFetching && latestAnime.length === 0) {
return <p>No animes to show</p>;
}
if (!isFetching && errorMessage.length > 0) {
return <p>{errorMessage}</p>;
}
return (
<div>
{latestAnime.map((anime, index) => {
return <p key={index}>{anime.title}</p>;
})}
</div>
);
};
const mapState = state => {
return {
isFetching: state.latestAnimeReducers.isFetching,
latestAnime: state.latestAnimeReducers.latestAnime,
errorMessage: state.latestAnimeReducers.errorMessage
};
};
const mapDispatch = dispatch => {
return {
fetchLatestAnime: () => dispatch(fetchLatestAnime())
};
};
export default connect(
mapState,
mapDispatch
)(App);
I'm developing a react-redux app and I can get access to the reducers via routes. Now I'm facing the trouble of getting access to a specific reducer without using routes.
Here is my reducers.js:
const initialState = {
loading: false,
topics: []
};
export default createReducer(initialState, {
[LOADING_DATA]: (state, action) => {
return Object.assign({}, state, {
loading: action.loading
});
}
});
This is my actions.js:
export function loading (loading) {
return {
type: LOADING_DATA,
payload: {loading}
};
}
And this is what I have on my component:
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux';
import * as moduleActionCreators from '...';
import * as actionCreators from '...';
class MyComponent extends Component {
...
render () {
return (<div>
...
</div>;
}
}
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(Object.assign({}, moduleActionCreators, actionCreators), dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Normally in the mapStateToProps I reference the reducer variables as loading: state['my_reference_to_reducer'].loading but I can't figure it out how to tell the component to reference my reducers.js in order to get loading as props.
I would appreciate a light on this.
You need to set up the state in mapStateToProps function in order to access it:
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
Then you should be able to use it as this.props.loading in MyComponent.
Your reducer can look like this:
export default function reducer(state = {}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
I recommend you to use redux ducks pattern as it keeps action creators and reducers at the same file, saves you time and makes it easier to read and use. For example:
loading.js
// Actions
const LOADING_DATA = 'LOADING_DATA'
// Action Creators
export const loadingData = (data) => {
return {
type: LOADING_DATA,
payload: {
loading: data
}
}
}
// Reducer
export default function reducer(state = {
loading: 'DATA zeroed'
}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
default:
return state
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from './MyComponent';
import configureStore from './configureStore'
const store = configureStore()
ReactDOM.render(
<MyComponent store={store}/>,
document.getElementById('root')
);
configureStore.js
import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import loadingData from './loading'
const configureStore = () => {
return createStore(loadingData, composeWithDevTools())
}
export default configureStore
MyComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadingData } from './loading';
class MyComponent extends Component {
constructor(props){
super(props)
this.onLoadingData = this.onLoadingData.bind(this)
}
componentDidMount() {
this.props.loadingData('no more undefined')
}
onLoadingData() {
this.props.loadingData('DATA')
}
render() {
console.log(this.props.loading)
return (
<div>
<h2>MyComponent</h2>
<button onClick={this.onLoadingData}>Load Data</button>
<p>{this.props.loading}</p>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
const mapDispatchToProps = {
loadingData
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent)