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;
Related
I am new to Next.js, So I follow some tutorials for Redux integration in Next.js. All is working fine but whenever I switch between pages, each time API make a call, and Redux lost its stored value.
The basic function is like this. Whenever a user loads a website an API call will fetch category data from the server and save that data in reducer[categoryReducer], then the user can navigate to any page and category data will fetched from the reducer. But in my case, it hits again and again
Full Code:
// Action Call
import * as Constants from '../../constant/constant';
import * as t from '../types';
import axios from 'axios';
export const loadCategoryApi = (type) => dispatch => {
axios.post(Constants.getCategories,type)
.then(function (response) {
console.log(response);
if(response && response.data && response.data.status==="200"){
dispatch({
type: t.LOAD_CATEGORY,
value: type
});
}
else if(response && response.data && response.data.status==="404"){
alert('Someting went wrong');
}
})
}
// Reducer File
import * as t from '../types';
const initialState = {
doc:null
}
const CategoryReducer = (state = initialState, action) =>{
console.log('reducer action', action.type);
switch (action.type){
case t.LOAD_CATEGORY:
console.log('slots actions',action);
return({...state, doc:action.value})
default:
return state;
}
}
export default CategoryReducer;
// Store file
import { createStore, applyMiddleware, compose } from "redux"
import thunk from "redux-thunk"
import { createWrapper } from "next-redux-wrapper"
import rootReducer from "./reducers/rootReducer"
const middleware = [thunk]
const makeStore = () => createStore(rootReducer, compose(applyMiddleware(...middleware)))
export const wrapper = createWrapper(makeStore);
// rootReducer
import { combineReducers } from "redux"
import CategoryReducer from "./CategoryReducer";
const rootReducer = combineReducers({
CategoryReducer: CategoryReducer
})
export default rootReducer;
// _app.js
import React from "react"
import { wrapper } from "../redux/store"
import Layout from '../components/Layout';
import '../styles/globals.css'
const MyApp = ({ Component, pageProps }) =>(
<Layout>
<Component {...pageProps} />
</Layout>
);
export default wrapper.withRedux(MyApp);
// Uses
import React, { useState, useEffect } from 'react';
import {connect} from "react-redux";
import {loadCategoryApi} from "../redux/actions/CategoryAction";
function navbar(props){
const { loadCategory, loadCategoryApi } = props;
useEffect(() => {
if(loadCategory===null){
console.log('navbar loading funciton');
loadCategoryFunation();
}
}, []);
const loadCategoryFunation = () =>{
var json = {
type : 'main'
};
loadCategoryApi(json);
}
}
const mapStateToProps = state => {
return { loadCategory: state.CategoryReducer.doc }
}
const mapDispatchToProps = {
loadCategoryApi
}
export default connect(mapStateToProps, mapDispatchToProps)(Navbar)
What I am doing wrong?
You have to create main reducer to handle the hydration. I explained this hydration process here.
In the file that you created the store, write main reducer
import reducers from "./reducers/reducers";
const reducer = (state, action) => {
// hydration is a process of filling an object with some data
// this is called when server side request happens
if (action.type === HYDRATE) {
const nextState = {
...state,
...action.payload,
};
return nextState;
} else {
// whenever we deal with static rendering or client side rendering, this will be the case
// reducers is the combinedReducers
return reducers(state, action);
}
};
then pass this reducer to the store
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've worked a bit with React using JS, but now I'm creating a new project to learn React with Typescript. When I was using JS and needed to use dispatch, I just imported useDispatch from react-redux:
import { useDispatch, useSelector } from 'react-redux';
const AuthAppBar = () => {
const dispatch = useDispatch();
const isUserLogged = useSelector(authSelector.isUserLogged);
const { event } = useGoogleAnalytics();
const userLogout = () => {
const userManager = authManager.getUserManager();
dispatch(authActions.setLoggingOut(true));
userManager.signoutRedirect({ state: { callbackUrl: routes.home.path } });
event('Account', 'Logout');
};
return <></>;
};
But now in this Typescript project the docs says that I need to do like this:
// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
// useGetDeviceById.ts
import { useEffect } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from 'src/hooks';
const useGetDeviceById = () => {
const dispatch = useAppDispatch();
// ...
}
Why do I need to do it this way?
You aren't required to do this, but it's a nice convenience factor, and can prevent some errors later.
Normally, you'd have to do this in every component file:
// import the RootState type
import { RootState, AppDispatch } from "app/store";
import { useSelector, useDispatch } from "react-redux";
function MyComponent() {
// Specifically mark the `state` arg as being of type RootState
const todos = useSelector( (state: RootState) => state.todos);
// Specifically mark `dispatch` as being a type that understands thunks
const dispatch : AppDispatch = useDispatch();
}
Not a huge cost, but it can be annoying to repeat that. In addition, one of the most common problems we see is people not using the store-specific version of the Dispatch type, and having TS tell them they can't dispatch thunks because those aren't plain action objects.
So, for consistency, we recommend that users always create pre-typed versions of the hooks and use them, so they don't accidentally forget to use the right types:
import { useAppSelector, useAppDispatch } from "app/hooks";
function MyComponent() {
// Already knows the state is `RootState`
const todos = useAppSelector(state => state.todos);
// Already knows that `dispatch` can accept a thunk
const dispatch = useAppDispatch();
}
So I just started learning hooks and I'm trying to use Redux with them. Ive been trying redux new api but now I can't seem to get the data that I want. When I do console.log() I see the promise getting resolved and inside the [[PromiseValue]] I see the data but how do I get it out of there into my Component.
const Main = ({props}) => {
const [cloth,setCloth]=useState([])
const items = useSelector((state) => state);
const dispatch = useDispatch()
let getItems = getAllItems(
() => dispatch({ type: 'GET_ITEMS' }),
[dispatch]
)
console.log(getItems)
here is the pic of my console.log()
first to use redux with async actions you need to use redux-thunk middleware:
npm i redux-thunk
and then use it like so:
import { createStore, applyMiddleware } from 'redux';
const store = createStore(rootReducer, applyMiddleware(thunk));
now for the actions here a simple example:
first action to fetch Items and then action to set Items in store:
//actions.js
const setItems = (payload)=>({type:SET_ITEMS,payload})
export const fetchItem = ()=>{
return dispatch =>{
axios.get("/items").then(response=>
dispatch(setItems(response.data))).catch(error=>throw error)
}
}
//reducer.js
const state = {items:[]}
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case "SET_ITEMS":
return {
...state,
items: payload
};
default:
return state;
}
}
now from react component we fetch items on mount :
import React,{useEffect} from "react"
import {useDispatch,useSelector} from "react-redux"
import {fetchItems} from "action.js"
const Items = ()=>{
const items= useSelector(state=>state.items)
const dispatch = useDispatch()
useEffect(()=> dispatch(fetchItems()),[])
return items?items.map(item =><p key={item.name}>{item.name}</p>):<p>items not available</p>
hope this what you are looking for.
i am new to react , just understanding the concept on redux without using redux thunk. please see the below code
// app.js
import React, { Component } from 'react';
import {connect} from 'react-redux';
import * as actions from './actions'
class App extends Component {
render() {
return (
<div>
<button onClick={this.props.fetchData}>Show Data</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
}
}
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(actions.fetchDataHandler)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import {createStore} from 'redux';
import Data from './reducers';
import {Provider} from 'react-redux';
const store = createStore(Data)
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
registerServiceWorker();
//actions-index.js
export const fetchDataHandler = e => dispatch => {
console.log("hit here");
}
//reducers-index.js
// default state
const defaultState = {
data: null
}
let data;
export default data = (state=defaultState, action) => {
switch(action.type){
case "FETCH_DATA":
return {
...state,
data: action.payload
}
default:
return{
...state
}
}
}
folder structure is
src
actions
index.js
reducers
index.js
app.js
i am not using redux thunk, when the button is clicked it will call the fetchData which will invoke the actions.fetchDataHandler
so on the console it should get a message as "hit here", but its not working.
sorry if i am not understanding the redux concept properly.
In a normal redux flow, Actions are supposed to be plain object, i.e an action creator must return a plain object, But in your case since you haven't need using any middleware like redux-thunk, you can not write an action like
//actions-index.js
export const fetchDataHandler = e => dispatch => {
console.log("hit here");
}
A general way to do it would be
export const fetchDataHandler = e => {
console.log("hit here");
return {
type: 'MY_ACTION'
}
}
However if you configure a middleware like redux-thunk, you can have an asynchronous action within your action creators like
//actions-index.js
export const fetchDataHandler = e => dispatch => {
console.log("hit here");
API.call().then((res) => {
dispatch({ type: 'API_SUCCESS', payload: res });
});
}
Also your mapDispatchToProps isn't calling the action in dispatch, you would either write it like
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(actions.fetchDataHandler())
}
}
or
const mapDispatchToProps = {
fetchData: actions.fetchDataHandler
}