Basically what I wanted to do was to stop making axios calls inside of my component. So I thought; “Why not just create an action for that?”
I googled around to find a good “guide” to use Redux and this is what I’m using:
Add a constant to the constants file. Something like const GREAT_COURSE = GREAT_COURSE
Add an action creator to the actions folder. Return an action JavaScript object with a type of the constant you created.
Add a reducer to the reducers folder that handles this action creator.
So I began to create my action creator:
import axios from 'axios'
import { CUSTOMER_FETCH } from './types'
import settings from '../settings'
axios.defaults.baseURL = settings.hostname
export const customers = () => {
return dispatch => {
return axios.get('http://hejhej/customers').then(res => {
dispatch({
type: CUSTOMER_FETCH,
data: res.data
})
})
}
}
And later to add a reducer that handles my action creator:
import { CUSTOMER_FETCH } from '../actions/types'
const initial = []
const customer = action => {
return {
data: action.data
}
}
const customers = (state = initial, action) => {
switch (action.type) {
case CUSTOMER_FETCH:
customers = [...state, customer(action)]
console.log('customers as state', customers)
return customers
default:
return state
}
}
export default customers
And inside of my component I'm importing it:
import { customers } from '../../actions/customersAction'
And later using connect: export default connect(null, { customers })(Events)
And finally I'm using it inside of my component:
customers() {
this.props.customers(this.state.data)
}
So I'm wondering what I'm doing wrong, because I can't see my console.log in my dev tools. Thanks a lot for reading!
Inside of my component atm:
axios.get('http://hejhej/customers').then(res => {
this.setState({
res,
customer: res.data
})
})
Related
im writing a react app who has a default state management: View dispatch an action than change reducer state. I was able to test the view and the reducer but didn't find a way to test my actions file because return a dispatch function
Action File that need to be tested:
import {Dispatch} from 'redux'
import {AuthAction, AuthActionTypes, SetUserAction} from "../actions-types/auth-actions-types";
export const setUserAction = (user: User) => {
return async (dispatch: Dispatch<SetUserAction>) => {
dispatch({
type: AuthActionTypes.SET_USER,
payload: user
})
}
}
reducer
import {AuthAction, AuthActionTypes} from "../actions-types/auth-actions-types";
export const initialAuthState = {
auth: {},
user: null
};
const reducer = (state = initialAuthState, action: AuthAction) => {
switch(action.type) {
case AuthActionTypes.SET_USER:
return {
...state,
user: action.payload,
};
default:
return state
}
}
export default reducer
reducer Test working ok.
import authReducer, {initialAuthState} from "./auth-reducer";
import {AuthActionTypes} from "../actions-types/auth-actions-types";
describe('Auth Reducer', ()=>{
test('should return user correclty ', ()=>{
const mockPayload = {
name: 'any_name',
emaiL: 'any_email',
accessToken: 'any_tokem'
}
const newState = authReducer(initialAuthState, {
type: AuthActionTypes.SET_USER,
payload: mockPayload
})
expect(newState.user).toEqual(mockPayload);
})
})
Action File test with problems
describe('AuthAction', ()=>{
test('setUserAction', ()=>{
const user = {
name: 'any_user',
email: 'any_email',
token: 'any_token'
}
const result = setUserAction();
expect(result).toEqual(user);
})
})
Expected: {"email": "any_email", "name": "any_user", "token": "any_token"}
Received: [Function anonymous]
Writing an action creator
Here is the official documentation that shows how to create an action creator
I do not see the benefit for your action creator to do a dispatch, you can simply write it and use it in the following way:
// action.ts
import { Dispatch } from 'redux'
import { AuthAction, AuthActionTypes, SetUserAction } from "../actions-types/auth-actions-types";
export const setUser = (user: User) => ({
type: AuthActionTypes.SET_USER,
payload: user
})
// somewhere.ts
dispatch(setUser(user))
Now the redux team recommends using redux-toolkit and they provide a simple tool called createAction
And if you want to create your reducer and action creator at the same time in the easier possible way you can use createSlice
How to test a reducer and an action?
To avoid an opinionated response to this answer you have two paths:
testing reducer with your action creator
a test for the reducer and a test for the action
Testing a reducer with your action creator
The reducer test should confirm that the triggered action has the expected impact.
Here is an example of using your reducer and your action creator together:
describe('Auth Reducer', ()=>{
test('should set user correctly', ()=> {
const newState = authReducer(initialAuthState, setUser(mockPayload))
expect(newState.user).toEqual(mockPayload);
})
})
The benefit of this is that you just write one test and you assert that both action creator and reducer work well together.
How to test an action creator alone?
You do not need to test your action creator if you test your reducer with it.
An action is just an object with a type and payload basically, so you can test it in the following way
describe('AuthAction', () => {
test('setUserAction', () => {
const user = {
name: 'any_user',
email: 'any_email',
token: 'any_token'
}
const result = setUser(user);
expect(result).toEqual({ type: AuthActionTypes.SET_USER, user });
})
})
I am pretty new to redux and here I am trying to create a common dispatch function where I can call the function from multiple components but can't seem to use useDispatch() in my common component getting invalid hook call error.
import { useDispatch, useSelector } from "react-redux";
import { UPDATE_PREVIEW_DATA } from "../../redux/types";
export default function setPreviewData(event, obj, lang) {
const dispatch = useDispatch();
const previewData = useSelector((state) => state.previewData);
const dispatchFunc = () => {
dispatch({
type: UPDATE_PREVIEW_DATA,
data: {
[obj]: {
[lang]: {
...previewData[obj][lang],
[event.target.name]: event.target.value,
},
},
},
});
};
return dispatchFunc;
}
// previewData.js in action folder
import { UPDATE_PREVIEW_DATA } from "../types";
const previewData = (data) => (dispatch) => {
dispatch({
type: UPDATE_PREVIEW_DATA,
data,
});
};
export default previewData;
// previewData.js in reducers folder
import { UPDATE_PREVIEW_DATA } from "../types";
const initialState = {...};
const previewData = (state = initialState, action) => {
switch (action.type) {
case UPDATE_PREVIEW_DATA: {
return action.data;
}
default:
return state;
}
};
export default previewData;
And I am trying to make this work like
// component.jsx
setPreviewData(e, "hightlights", "en");
Hooks are intended to be used in Functional components only. As per the Rules of hooks they can be called from
React function components.
Custom Hooks
Reference -> https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions
now you might think your setPreviewData is a React Function Component, but it's just a normal js function, that's why you are getting the error.
As a result, it doesn't get wrapped in React.createElement, so it thinks the hook call is invalid.
Moreover, you are committing one more mistake here, lets's say if setPreviewData was a Function Component you still call it as if though its a normal function
rootReducer
import { combineReducers } from "redux";
import mods from "./mods.js";
export default combineReducers({
mods
})
reducers/mods.js
import { GET_MODS, GET_SPECIFC_MOD } from "../actions/types"
const initialState = {
mods: [],
currMod: []
}
export default function(state = initialState, action) {
switch(action.type) {
case GET_MODS:
return {
...state,
mods: action.payload
}
case GET_SPECIFC_MOD:
return {
...state,
currMod: action.payload
}
default:
return state
}
}
actions/mods.js
import axios from 'axios'
import { GET_MODS, GET_SPECIFC_MOD } from './types'
// get the mods
export const getMods = () => dispatch => {
axios.get('http://localhost:8000/api/mods')
.then(res => {
dispatch({
type: GET_MODS,
payload: res.data
})
}).catch(err => console.log(err))
}
// get single mod
export const getSpecificMod = (title) => dispatch => {
axios.get(`http://localhost:8000/api/mods/${title}`)
.then(res => {
dispatch({
type: GET_SPECIFC_MOD,
payload: res.data
})
}).catch(err => console.log(err))
}
components/download.js
import React from 'react'
import { useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
useEffect(() => {
const title = window.location.pathname.split('/')[3]
getSpecificMod(title)
})
return (
<></>
)
}
const mapStateToProp = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProp, getSpecificMod)(Download)
Response from backend
GET http://localhost:8000/api/mods/function(){return!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__&&a.dispatch.apply(a,arguments)}
Basically the user clicks on a mod and gets sent to the download section that is handled by 'download.js' the component ('download.js') renders it and reads the window.location to retrieve the title, with redux I want to get the mod so i made a function that takes the title and sends the request 'getMod(title)' but for some reason it is throwing horrible errors that I dont understand, any help is appreciated!
You are not dispatching the action properly in your component. Right now you are actually just calling the getSpecificMod action creator function from your imports. Your Download component doesn't read anything from props so it is ignoring everything that gets created by the connect HOC.
If you want to keep using connect, you can fix it like this:
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = ({currMod, getSpecificMod}) => {
const title = window.location.pathname.split('/')[3]
useEffect(() => {
getSpecificMod(title)
}, [title])
return (
<></>
)
}
const mapStateToProps = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProps, {getSpecificMod})(Download)
We are now accessing the bound action creator as a prop of the component. mapDispatchToProps is an object which maps the property key to the action.
But it's better to use the useDispatch hook:
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
const currentMod = useSelector(state => state.mods.currMod);
const dispatch = useDispatch();
const title = window.location.pathname.split('/')[3]
useEffect(() => {
dispatch(getSpecificMod(title));
}, [title, dispatch]);
return (
<></>
)
}
export default Download;
There might be some confusion on terminology here. Your getSpecificMod function is a function which takes dispatch as an argument but it is not a mapDispatchToProps. It is a thunk action creator.
Make sure that you have redux-thunk middleware installed in order to handle this type of action. Or better yet, use redux-toolkit.
Your useEffect hook needs some sort of dependency so that it knows when to run. If you only want it to run once you can use an empty array [] as your dependencies. If you don't specify the dependencies at all then it will re-run on every render.
Does the pathname change? If so, how do you know when? You might want to add an event listener on the window object. Or consider using something like react-router. But that is a separate question.
I have a problem, when I get my data from API and update the store the data doesn't changed. It is binded as a prop and I think it should changed, one more thing I noticed is that it doesn't call mapStateToProps after the store was updated. When i give some initial value to the store it displays it so I think it can see the store, something else is wrong obiviously but I can't figure out what.
Reducer code:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
import axios from "axios";
const initialState = {
posts: []
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
response.data.forEach(thePost => {
state.posts = [...state.posts, thePost];
});
console.log(state.posts);
return state;
});
break;
default:
return state;
}
return state;
}
index (here I am creating my store and wrapping the app component with provider):
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { combineReducers, createStore } from "redux";
import { Provider } from "react-redux";
import postReducer from "./reducers/postsReducers";
const allReducers = combineReducers(
{
post: postReducer
},
window.devToolsExtension && window.devToolsExtension()
);
const store = createStore(allReducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
Mapping it in my component like this:
const mapStateToProps = state => ({
posts: state.post.posts
});
if you guys need anything else let me know, I have a file that is a bit large so I wouldn't like to add it if it's not neccessary, I am banging my head against the wall for a couple of hours now. Thanks in advance
===EDIT===
I also mapped my action to props
const mapActionToProps = {
onDeletePost: deletePost,
onUpdatePost: updatePost,
onGetPost: getPosts
};
I have my action defined as
export const ADD_POST = "posts:addPost";
export function addPost(newTitle, newHours, newDate) {
return {
type: ADD_POST,
payload: {
id: new Date().toString(),
title: newTitle,
hours: newHours,
date: new Date().toLocaleDateString()
}
};
}
So I already have the action defined there so I am not sure I need a dispatchToAction? I am looking it up as we speak and will try to make something, just a bit confused.
==END OF EDIT==
I think that technically your problem is that your reducer returns (after all of axios) before the fetching is done. But that's not the problem you want to solve.
First of all, you have too much going on in your reducer. You shouldn't be implementing the action (fetching the data) in your reducer. I imagine in your component you're constructing an action that looks like {type: 'GET_POSTS'}, and then...dispatching it? Except you don't appear to be providing your component with a dispatch. So the action is never making it to the store. I can only assume because you haven't shown us where you're calling your action from.
You should be moving your fetching to its own async (thunk) action method:
function getPosts() {
return dispatch => {
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
const posts = response.data
dispatch({type: 'GET_POSTS', payload: posts})
});
}
}
And then simply add the posts to your state in your reducer:
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case GET_POSTS:
return { ...state, posts: payload }
default:
return state;
}
And then you'll have to connect the getPosts() function to your store using mapDispatchToProps. And you'll also have to use redux-thunk or this won't work at all.
You've got a good start with react-redux, but there's some gaps in your learning. You're going to need to look into async actions and redux thunk (or some other async action middleware). I'd suggest reviewing all the Redux documentation, mainly the advanced tutorials.
Your reducer is mutating state, and that's breaking the app.
In addition, you are making an AJAX call in a reducer, which is never allowed. All async logic happens outside reducers, and reducers only look at their state and action parameters to calculate the new state.
This why the first two "Essential" rules of the Redux Style Guide are Do Not Mutate State and Reducers Must Not Have Side Effects.
I'd strongly encourage you to use our new official Redux Toolkit package. Its configureStore() function sets up mutation detection by default, and it has functions like createSlice() which let you write simpler immutable update logic.
Beyond that, I'd suggest taking some more time to read through the Redux docs to understand how you are supposed to use Redux correctly.
I changed my action to be
import axios from "axios";
export const GET_POSTS = "posts:getPosts";
export function getPosts(theDate) {
return dispatch => {
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
const posts = response.data;
dispatch({ type: GET_POSTS, payload: posts });
});
};
}
reducer:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
const initialState = {
posts: []
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
payload.forEach(element => {
state.posts = [...state.posts, element];
});
break;
default:
return state;
}
return state;
}
in the component that I want to show posts I have:
const mapStateToProps = state => ({
posts: state.post.posts
});
Then showing it with:
render() {
return (
<div className="contentWindow">
{this.props.posts.map((post, i) => {
return (
#some displaying logic
store creation changed with middleware:
const store = createStore(
allReducers,
applyMiddleware(thunk)
);
Still doesn't update my props in my component where I am mapping state to props.
When I inspected the store with react dev tools changes are being made but some how my props weren't updated.
=====EDIT=====
I have changed my reducer code to:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
const initialState = {
posts: [123]
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
return { ...state, posts: payload };
case DELETE_POST:
state = state.filter(post => post.id !== payload.id);
break;
case UPDATE_POST:
for (let index = 0; index < state.length; index++) {
if (state[index].id === payload.theId) {
state[index].id = payload.theId;
state[index].date = payload.newDate;
state[index].hours = payload.newHours;
state[index].title = payload.newTitle;
}
}
break;
default:
return state;
}
return state;
}
Now I have a problem that it's changing its initial state every time xD, I am glad I managed to get through the previous one btw feeling so strong now :D.
I'm using React JS with Redux and have a little problem retrieving data from a request with Axios..
Here is my Axios request :
import Axios from 'axios';
class UsersApi {
static getAllUsers() {
return Axios.get('http://localhost:3001/user').then(response => {
return response;
});
}
}
export default UsersApi;
And this is where i want to use my data :
import React, {Component} from 'react';
import {connect} from 'react-redux';
class UserList extends Component {
render(){
console.log(this.props.users);
return(
<ul>
</ul>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
};
}
export default connect(mapStateToProps)(UserList);
This is the action :
import UsersApi from '../api/UsersApi';
export function loadUsers() {
return function(dispatch) {
return UsersApi.getAllUsers().then(users => {
dispatch(loadUsersSuccess(users));
}).catch(error => {
throw(error);
});
};
}
export function loadUsersSuccess(users) {
return {type: 'LOAD_USERS_SUCCESS', users};
}
And this is my reducer :
import initialState from './initialState';
export default function usersReducer(state = initialState.users, action) {
switch(action.type) {
case "LOAD_USERS_SUCCESS":
return action.users
default:
return state;
}
}
And this is what i have with console.log : Result console.log
When i try to display the password of the first user i put console.log(this.props.users.data["0"].pwd) but this is not working..
If I try to return response.data["0"].pwd in the request I can have the password with console.log(this.props.users).
But the problem is that i want the data of every User..
I need some help. :)
First of all, you need to add your users to your state. I see you are using redux, so you should dispatch an action after receiving your users and let your reducers handle it:
Axios.get('http://localhost:3001/user').then(response => {
dispatch(usersLoaded(response.data));
});
You should have an action called usersLoaded and a reducer that handles it:
const usersLoaded = users => ({ type: 'USERS_LOADED', payload: users });
const users = (state = [], action) => {
switch(action.type) {
case 'USERS_LOADED':
return action.payload;
default:
return state;
}
}
This way you'll be able to access your users in your UserList component
As a proper redux way to do things, right inside the then on your get call you need to fire an action to SET_USERS in which you will use your reducer to replace your state with your new users coming back from the api.