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.
Related
I am using redux-saga as a middleware and i want to dispatch multiple action items to the store. As of now i am able to dispatch only one action (i.e., within the fetchData() function which i am calling in Home.js component).
I've tried adding multiple actions but its not working, Only the first action type is getting dispatched
action.js
import { FETCH_ABOUT, FETCH_CTA, FETCH_PRODUCTS} from './actionType'
//import axios from 'axios';
export const fetchData = () => (
{ type:FETCH_PRODUCTS}
)
export const fetchProducts = (products) => ({
type: FETCH_PRODUCTS,
payload: products,
})
export const fetchCta = (cta) => ({
type: FETCH_CTA,
payload: cta,
})
export const fetchAbout = (about) => ({
type: FETCH_ABOUT,
payload: about,
})
reducer.js
import { FETCH_ABOUT, FETCH_CTA, FETCH_PRODUCTS } from "./actionType"
const initialState = {
products: [],
cta:'',
about:'',
}
const productsReducer = (state=initialState,action) => {
switch(action.type){
case FETCH_PRODUCTS:
return{
...state,
products: action.payload
}
case FETCH_CTA:
return{
...state,
cta: action.payload
}
case FETCH_ABOUT:
return{
...state,
about: action.payload
}
default:
return state;
}
}
export default productsReducer;
ProductsSaga.js
import {call,fork,put,takeLatest} from 'redux-saga/effects'
import { fetchCta, fetchProducts } from '../redux/action';
import { FETCH_CTA, FETCH_PRODUCTS } from '../redux/actionType';
import { fetchAPIcall } from '../redux/api'
function* fetchData() {
try{
const { data } = yield call(fetchAPIcall);
console.log(data.data.productCopy);
yield put(fetchProducts(data.data.productCopy));
}catch(e){
console.log(e);
}
}
//watcher saga
export function* watcherSaga() {
yield takeLatest(FETCH_PRODUCTS,fetchData)
}
export const productsSaga = [fork(watcherSaga)]
ctaSaga.js
import { call,put,takeLatest,fork } from "redux-saga/effects";
import { fetchCta } from "../redux/action";
import { FETCH_CTA } from "../redux/actionType";
import { fetchAPIcall } from "../redux/api";
function* onFetchCta() {
try{
const { data } = yield call(fetchAPIcall);
console.log(data.data.cta);
yield put(fetchCta(data.data.cta));
}catch(e){
console.log(e);
}
}
//watcher saga
export function* watcherSaga() {
yield takeLatest(FETCH_CTA,onFetchCta)
}
export const ctaSaga = [fork(watcherSaga)]
Home.js
import React, { useEffect, useState } from 'react'
import './Home.css'
import {useHistory} from 'react-router-dom';
import { useSelector,useDispatch } from 'react-redux';
import {fetchData} from './redux/action'
const Home = () => {
const history = useHistory();
const {products,cta} = useSelector((state)=>(state.productsReducer));
console.log(products,cta);
const dispatch = useDispatch();
useEffect(()=>{
dispatch(fetchData());
},[])
const productDetail = (item,i) => {
history.push({
pathname:`product-detail/${i}`,
state:item
})
}
return (
<div className='container'>
<div className='product'>
{products.map((item,i) =>{
return(
<div key={item.id}>
<img src={item.Image.path} alt = {item.Image.alt}/>
<p>{item.title}</p>
<button onClick={()=>productDetail(item,i)}type='button'>{cta}</button>
</div>
)
})}
</div>
</div>
)
}
export default Home
Action creators such as fetchData will always create only a single action object. Also the dispatch function (or put effect) can always dispatch only a single action, however nothing is preventing you from dispatching multiple actions one after the other:
function* mySaga() {
yield put(firstAction())
yield put(secondAction())
yield put(thirdAction())
}
// or
function MyComponent() {
const dispatch = useDispatch()
return <div onClick={() => {
dispatch(firstAction())
dispatch(secondAction())
dispatch(thirdAction())
}}>Hello</div>
}
If you are worried about rerenders, react-redux has the batch function that allows you to wrap your dispatches making sure that react batches all the updates and rerenders only a single time. Note that this is not necessary starting from React 18 as it batches things automatically.
It is more difficult to use the batched updates with redux saga due to its internal scheduler that doesn't guarantee that everything will happen in single tick, but again starting from React 18 you don't need to worry about this. In case you really need it, there are libraries out there that allows you to do it though, check out Mark's post about it which includes links to some of these libraries: https://blog.isquaredsoftware.com/2020/01/blogged-answers-redux-batching-techniques/
One more thing, if you find yourself dispatching the same list of actions again and again, maybe it make sense to merge these together to a single action and avoid the issue entirely.
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 have a list of object stored as a array in my redux store which loads on component mount. I want to List them in a div, also to do the crud Operation. This is my implementation. Whenever I use useSelector to save the list for a constants it fectching infinite number of logs.
BranchAction.js
import axios from 'axios';
export const fetchAllBranchListOk = (branchList) => {
return {
type : 'FETCH_ALL_BRANCH_LIST_OK',
branchList
}
};
export const fetchAllBranchList = () =>{
return (dispatch) => {
return axios.get(`https://jsonplaceholder.typicode.com/posts`)
.then(response => {
dispatch(fetchAllBranchListOk(response.data));
})
.catch(error => {
throw(error);
});
}
};
BranchReducer
export default (state = [], action) => {
switch (action.type) {
case 'FETCH_ALL_BRANCH_LIST_OK' :
return action.branchList;
default:
return state;
}
};
BranchManagement.js
function BranchManagement() {
store.dispatch(BranchAction.fetchAllBranchList());
const AllBranch = useSelector(state => state.BranchReducer)
return(
<div>
</div>
)
}
export default BranchManagement;
CombinedReducer -> index.js
import {combineReducers} from 'redux'
import BranchReducer from "./Admin/BranchReducer";
const Reducers = combineReducers({
BranchReducer
});
export default Reducers;
If you want to dispatch the action to fetch the data from the backed, you should be keeping those calls in useEffect hook. The purpose of useEffect is similar to the purpose of Lifecycle methods in the class component like componentDidMount, componentDidUpdate and componentWillUnMount. To understand more about useEffect please refer this.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import BranchAction from "/path/to/BranchAction";
function BranchManagement() {
const dispatch = useDispatch();
//Since the data in the state is on `branchList`. You can directly return
//`state.branchList` which will you provide you the data you are looking for.
const branchList = useSelector(state => state.branchList)
//It'll act similar to `componentDidMount`. Since we are passing `[]`
//to `useEffect` dependencies array
useEffect(() => {
dispatch(BranchAction.fetchAllBranchList());
}, [])
//Here I'm assuming `branchList` is array of objects with `name` and `id`.
//Updated answer with branchList as[{"branchID":1,"createdBy":1,"isActive":true,"branchDetails":{"branchDetailsID":1}},{"branchID":2,"createdBy":1,"isActive":true,"branchDetails":{"branchDetailsID":1}}]
return(
<div>
{
(branchList || []).map((branch, index) => {
<div key={branch.branchID || index}>
<span>{branch.branchID}</span>
<span>{branch.createdBy}</span>
<span>{branch.isActive}</span>
<span>{branch.branchDetails.branchDetailsID}</span>
</div>
}
}
</div>
)
}
export default BranchManagement;
Hope this helps in order to resolve the issue.
import { createContext } from 'react';
import axios from 'axios'
import keys from '../../keys'
const getCollection = () => {
console.log("getCollectionCalled")
return axios.get(`${keys.url}/collection`)
.then(data => {
console.log(data)
return data
})
.catch(err => {
return {}
})
}
const SHOP_DATA = getCollection();
const CollectionsContext = createContext(SHOP_DATA);
export default CollectionsContext;
This is the code I am trying to run but context is not setting as per the data from the server, when I am using this in other component like this
const collections = useContext(CollectionsContext);
console.log("COLLECTIONS ", collections)
It is consoling it as :
COLLECTIONS PromiseĀ {<pending>}__proto__:
Promise[[PromiseStatus]]: "resolved"
[[PromiseValue]]: undefined
Kindly rectify me I am unable to think how may I implement it.
In this case you're returning a promise as initial context value, what you need to do is something between these lines:
import React, { useState, useEffect, createContext, useContext } from 'react'
import axios from 'axios'
import keys from '../../keys'
const INITIAL_STATE = {}
const CollectionsContext = createContext(INITIAL_STATE)
const Provider = ({ children }) => {
const [collection, setCollection] = useState()
useEffect(() => {
axios.get(`${keys.url}/collection`)
.then(data => setCollection(data))
.catch(() => setCollection(INITIAL_STATE))
}, [])
return (
<CollectionsContext.Provider value={collection}>
{children}
</CollectionsContext.Provider>
)
}
export const useCollection = () => useContext(CollectionsContext)
export default Provider
Then you wrap the top level (as high as it's needed, not neccessarily the highest) with the provider:
import CollectionsProvider from '.../.../somewhere'
<CollectionsProvider>...rest of components...</CollectionsProvider>
In this case, INITIAL_STATE is the value provided until value is undefined or you haven't used Provider. For example, you use useCollection outside of CollectionsProvider.
I am working on a react application using redux with hooks.
Here is my action creator below
PostAction
***********
import * as types from "./actionTypes";
import axios from 'axios';
const ROOT_URL = 'http://dotsuper.com/api'
export function fetchPosts(){
const request = axios.get(`${ROOT_URL}/post/getposts`)
return {
type: types.GETALL_POSTS,
payload: request
}
}
Here is my reducer below
PostReducer
************
import _ from 'lodash';
import * as types from "../actions/actionTypes";
export default function postReducer(state = [], action) {
switch (action.type) {
case types.GETALL_POSTS:
debugger;
console.log(action.payload.data);
return _.mapKeys(action.payload.data, 'id');
default:
return state;
}
}
Here is what my store configuration looks like
configureStore
***************
import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "./reducers";
import reduxImmutableStateInvariant from "redux-immutable-state-invariant";
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; //add support for redux dev tools.
return createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(thunk, reduxImmutableStateInvariant()))
);
}
Here is what my component looks like.
My question is when I look at my devtools, the state for posts is
completely empty. When I set a debugger, my PostReducer is not getting hit. I think I am still
missing something. I don't think you can use connect with hooks. What do I need to do below
to have data in my state and be able to hit my post reducer?
PostPage
**************
import React, { useState, useEffect } from "react";
import {fetchPosts} from "../../redux/actions/postActions";
const PostsPage = () => {
const [getPosts, setGetPosts] = useState([]);
async function fecthData(){
const res = fetchPosts()
}
useEffect( () => {
fecthData();
},[]);
return (
<div>
<h2>Posts</h2>
<p>
This page is for all the posts.
</p>
</div>
);
}
export default PostsPage;
You're calling the action generator fetchPosts() inside your component, but you actually never dispatch any change into your state. If you look closely you'll see that you're fetchPosts() returns an object commonly known as actions:
{
type: types.GETALL_POSTS,
payload: request
}
So basically when you call the fetchPosts, you fetch something and you return this object. No touching to the Redux state so far
In the next step you should actually take this object and dispatch it to your store, like this:
const action = await fetchPosts();
dispatch(action);
Which when you use connect with mapDispatchToProps the connect will take care of it for you.
Check here to get a better grasp of the concept.
When using with hooks however, you can import these from react-redux:
useDispatch instead of mapDispatchToProps and,
useSelector instead of mapStateToProps
import {useDispatch, useSelector} from 'react-redux';
import myAction from 'path/to/my/action';
const MyComponent = (props) => {
const myState = useSelector(state => state.myState);
const dispatch = useDispatch();
const handleClick = () => {
dispatch(myAction());
}
return (
...
)
}
Check inside fetchPosts method, axios.get returns promise. you need to make
the method async and handle async data.
You need to dispatch the action in order to bind the action with the redux state.
// PostAction
import * as types from "./actionTypes";
import axios from 'axios';
const ROOT_URL = 'http://dotsuper.com/api'
export function fetchPosts(){
return async (dispatch) => {
const request = await axios.get(`${ROOT_URL}/post/getposts`); // returns promise.
dispatch({
type: types.GETALL_POSTS,
payload: request
});
}
}
// PostPage
import React, { useState, useEffect } from "react";
import {useDispatch} from "react-redux";
import {fetchPosts} from "../../redux/actions/postActions";
const PostsPage = () => {
const [getPosts, setGetPosts] = useState([]);
const dispatch = useDispatch();
useEffect( () => {
dispatch(fetchPosts());
},[]);
return (
<div>
<h2>Posts</h2>
<p>
This page is for all the posts.
</p>
</div>
);
}
export default PostsPage;