I have created a new component in react
import React, {FC, useEffect} from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchExternalLinks } from '../../redux/reducers/appReducer'
import { getExternalLinksSelector } from '../../redux/selectors/appSelector'
type LinksType = {
title?: string | undefined
}
const ExternalLinks : FC<LinksType> = (props) => {
const {
title
} = props
const dispatch = useDispatch()
const links = useSelector(getExternalLinksSelector)
useEffect(() => {
console.log('use effect')
dispatch(fetchExternalLinks)
})
const openWindow = (path: string) => {
window.open(path, '_blank', 'toolbar=0,location=0,menubar=0');
}
return (
<>
Links:
{links.map((link) => {
return <a onClick={() => openWindow(link.path)}>{link.title}</a>
})
}
</>
)
}
export default ExternalLinks
when I try to call the fetchExternalLinks method from reducer with dispatch, it doesn't work.
In my console I can see only "use effect", but nothing from reducer
export const fetchExternalLinks = (): ThunkType => async (dispatch) => {
console.log("test")
try {
dispatch(appActions.toggleIsFetching(true))
const response = await appApi.getCustomers()
dispatch(appActions.setExternalLinks(response.data))
} catch (e) {
dispatch(appActions.toggleResponseMessage({isShown: true, isSuccess: false}))
} finally {
dispatch(appActions.toggleIsFetching(false))
}
}
Change your code from this,
dispatch(fetchExternalLinks)
to this,
dispatch(fetchExternalLinks())
Since you are only passing the reference, your async thunk is not getting executed, when the fetchExternalLinks function will be called using () rather than passing a reference you will receive an arrow function as a return value, to which react-redux will pass the dispatch function and will execute it for you
Related
I'm managing a context for the classes of my school so that i don't have to fetch on every page, for this i write this custom component and wrap my entire app in it.I whenever i fetch the classes i store them in the redux store so that i can use it again and again.
And before fetch i check the store if the classes exist there i don't hit the API instead get the classes from there. in my dependency array i put navigate, dispatch and Classes because react was complaining about that. but this cause infinite loop
here is my code
// ** React Imports
import { createContext, useEffect, useState } from 'react';
import { useDispatch, connect } from 'react-redux';
// ** Config
import { toast } from 'react-hot-toast';
// ** custom imports
import { useNavigate } from 'react-router-dom';
import { CLASSES_API_HANDLER } from 'src/redux/actions/classes/classes';
// ** Defaults
const defaultProvider = {
classes: [],
setClasses: () => null,
loading: true,
setLoading: () => null,
};
export const ClassesContext = createContext(defaultProvider);
const ClassesProvider = ({ children, CLASSES }) => {
const dispatch = useDispatch();
const navigate=useNavigate()
const [classes, setClasses] = useState(defaultProvider.classes);
const [loading, setLoading] = useState(defaultProvider.loading);
useEffect(() => {
const getClasses = async () => {
if (CLASSES.length > 0) {
setClasses(CLASSES);
setLoading(false);
return;
}
try {
const Data = await dispatch(CLASSES_API_HANDLER());
setClasses(Data.result);
setLoading(false);
} catch (ex) {
navigate("/error")
console.log(ex);
}
}
getClasses()
}, [CLASSES,navigate,dispatch]);
const values = {
classes:classes,
setClasses:setClasses,
loading:loading,
setLoading:setLoading,
};
return (
<ClassesContext.Provider value={values}>
{children}
</ClassesContext.Provider>
);
};
function mapStateToProps(state) {
return { CLASSES: state.CLASSES };
}
export default connect(mapStateToProps)(ClassesProvider);
I'm trying to fix the infinite loop
My objective is to get the customer details from a fake API through redux. I have migrated to using createSlice() to reduce the boilerplate, but I keep having that error. I have tried and researched as much as I can, but to no avail.
Here is a self contained code: https://stackblitz.com/edit/react-gbhdd9?file=src/App.js
App.js
import React, { useCallback, useState, useEffect } from 'react';
import { customerApi } from './__fakeAPI__/customerApi';
import { getCustomer as getCustomerSlice } from './slices/customers';
export default function App() {
const [customer, setCustomer] = useState(null);
const getCustomer = useCallback(async () => {
try {
//const data = await customerApi.getCustomer(); //works but not what I wanted
const data = getCustomerSlice();
setCustomer(data);
} catch (err) {
console.error(err);
}
}, []);
useEffect(() => {
getCustomer();
}, [getCustomer]);
return <div>{console.log(customer)}</div>;
}
slices/customers.js
import { createSlice } from '#reduxjs/toolkit';
import { customerApi } from '../__fakeAPI__/customerApi';
const initialState = {
customer: {}
};
const slice = createSlice({
name: 'customers',
initialState,
reducers: {
getCustomer(state, action) {
state.customer = action.payload;
}
}
});
export const { reducer } = slice;
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
};
export default slice;
You needed to dispatch your action to make it work, to access customer you can use useSelector to get access to your state :
https://stackblitz.com/edit/react-v4u5ft?file=src/App.js
You need to make a few changes to App.js to make this work.
The getCustomerSlice() method is an action creator and in your case, it returns a thunk, and to get access to the dispatch method in a thunk, you must first dispatch the action/function returned from that action creator i.e in App.js
export default function App() {
...
const getCustomer = useCallback(async () => {
try {
const data = dispatch(getCustomerSlice());
...
}, []);
...
}
To get access to the dispatch method in App.js, import the useDispatch from react-redux, useDispatch is a function that returns the dispatch method i.e
...
import { useDispatch } from "react-redux"
...
export default function App() {
const dispatch = useDispatch()
...
const getCustomer = useCallback(async () => {
try {
const data = dispatch(getCustomerSlice());
...
}, []);
...
}
When you do this redux-thunk will automatically supply dispatch, getState as parameters to any function that is returned from an action creator. This will cause your implementation to update your store properly.
Since you are expecting a return value from your dispatch method, make this modification to your customers.js file
...
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
// return the data
return data;
};
...
then in your App.js file await the dispatch function since you defined it as async
...
import { useDispatch } from "react-redux"
...
export default function App() {
const dispatch = useDispatch()
...
const getCustomer = useCallback(async () => {
try {
const data = await dispatch(getCustomerSlice());
...
}, []);
...
}
TLDR
Your App.js should now look like this.
import React, { useCallback, useState, useEffect } from 'react';
import { customerApi } from './__fakeAPI__/customerApi';
import { useSelector, useDispatch} from "react-redux"
import { getCustomer as getCustomerSlice } from './slices/customers';
export default function App() {
const dispatch = useDispatch()
const [customer, setCustomer] = useState(null);
const getCustomer = useCallback(async () => {
try {
//const data = await customerApi.getCustomer(); //works but not what I wanted
const data = await dispatch(getCustomerSlice());
setCustomer(data);
} catch (err) {
console.error(err);
}
}, []);
useEffect(() => {
getCustomer();
}, [getCustomer]);
return <div>{console.log("customers",customer)}</div>;
}
And your customers.js like this
import { createSlice } from '#reduxjs/toolkit';
import { customerApi } from '../__fakeAPI__/customerApi';
const initialState = {
customer: {}
};
const slice = createSlice({
name: 'customers',
initialState,
reducers: {
getCustomer(state, action) {
state.customer = action.payload;
}
}
});
export const { reducer } = slice;
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
return data;
};
export default slice;
it is important to note that your component has not subscribed to the store yet. so if the customers object in the store is modified by another component then this component won't re-render, to fix this look up the useSelector hook from react-redux
First of all I rarely use functional component, but this time I required to use it. So, I have this component called Login that use redux :
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getLoginData } from "../../redux/actions/LoginActions";
function Login() {
useEffect(() => {
const { getLoginData } = this.props;
getLoginData("test");
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
const mapStateToProps = (state) => ({
login: state.login,
});
const mapDispatchToProps = (dispatch) => ({
getLoginData: (value) => dispatch(getLoginData(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
It produce error since this is undefined. But, if I change it to class component like this:
import React from "react";
import { connect } from "react-redux";
import { getLoginData } from "../../redux/actions/LoginActions";
class Login extends React.Component {
componentDidMount() {
const { getLoginData } = this.props;
getLoginData("test");
}
render() {
return (
<div>
<h1>Login</h1>
</div>
);
}
}
const mapStateToProps = (state) => ({
login: state.login,
});
const mapDispatchToProps = (dispatch) => ({
getLoginData: (value) => dispatch(getLoginData(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
It will worked as expected(the redux is also worked). The question is, How can I pass this.props to functional component?
Function components get their props passed in as the argument to that function:
function Login(props) {
useEffect(() => {
props.getLoginData("test");
}, []);
// ...
}
// Or with destructuring:
function Login({ login, getLoginData }) {
useEffect(() => {
getLoginData("test");
}, []);
// ...
}
That said, if you're using a function component, then it's simpler to use hooks instead of connect:
function Login() {
const login = useSelector(state => state.login);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getLoginData("test"));
}, []);
//...
}
// Note that there is no mapStateToProps/mapDispatchToProps/connect here
export default Login;
As per the React docs, you pass a props object to the function and access the values as attributes of props.
So, for your implementation, you'd do it like this:
function Login(props) {
useEffect(() => {
props.getLoginData("test");
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
Or, you could replace function Login(props) with function Login({getLoginData}) to unwrap the value and replace props.getLoginData("test") with getLoginData("test").
I've been trying to dispatch a function that will call an async parse cloud function. It worked well in my other projects when i used them in functions. But this is the first time i'm using them in a component and when i call the dispatch from map dispatch to props, I get this error. Please help me out.
ProfileHeader.js
import React, { Component } from 'react';
import Cover_Image from './Cover_Image.jpg';
import Profile_Pic from './Profile_Pic.svg';
import './ProfileHeader.css';
import { connect } from 'react-redux';
import { fetchUserProfile } from '../../Redux/UserProfile-Redux/UserProfileActionMethods';
class ProfileHeader extends Component {
componentDidMount() {
this.props.fetchUserProfile()
}
render() {
return (
<div className="profile-header-layout"></div>
)
}
}
const mapStatetoProps = (state) => {
return {
profile: state.UserProfile
}
}
const mapDispatchtoProps = (dispatch) => {
return {
fetchUserProfile: () => { dispatch(fetchUserProfile()) }, dispatch,
}
}
export default connect(mapDispatchtoProps, mapStatetoProps)(ProfileHeader)
The action Method:
import Parse from 'parse/dist/parse.min.js';
import { FETCH_USERPROFILE_FAILURE, FETCH_USERPROFILE_REQUEST, FETCH_USERPROFILE_SUCCESS } from './UserProfileActions';
const params = { username: "prvnngrj" }
export const fetchUserProfileRequest = () => {
return {
type: FETCH_USERPROFILE_REQUEST
}
}
export const fetchUserProfileSuccess = (userprofiles) => {
return {
type: FETCH_USERPROFILE_SUCCESS,
payload: userprofiles
}
}
export const fetchUserProfileFailure = (error) => {
return {
type: FETCH_USERPROFILE_FAILURE,
payload: error
}
}
export const fetchUserProfile = () => {
return async dispatch => {
dispatch(fetchUserProfileRequest)
try {
const responsedata = await Parse.Cloud.run("GetUserProfileForUsername", params);
const userprofiles = responsedata;
dispatch(fetchUserProfileSuccess(userprofiles))
}
catch (error) {
const errorMessage = error.message
dispatch(fetchUserProfileFailure(errorMessage))
}
}
}
Please ignore parts of code which do not make it relevant, its straight from the project
You mixed up the order of your arguments, so this.props.dispatch is actually your state!
You need to change
export default connect(mapDispatchtoProps, mapStatetoProps)(ProfileHeader)
to:
export default connect(mapStatetoProps, mapDispatchtoProps)(ProfileHeader)
If you can switch to function components and the useSelector/useDispatch hooks you should. This is the current recommended approach and it's easier to use.
I am getting TypeError: functionName is not a function when I call it with useDispatch. The initial state return normal as an empty array. This is my code:
Action:
export const getTopStories = () => async (dispatch) => {
try {
const { data } = await axios.get('http://news-site.com/api');
dispatch({
type: GET_TOP_STORIES,
payload: data,
});
} catch (err) {
console.log(err)
}
};
Reducer:
const initialState = {
topStories: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_TOP_STORIES:
return { ...state, topStories: action.payload};
default:
return state;
}
};
Page:
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
const TopStories = () => {
const stories = useSelector((state) => state.stories);
const { topStories, getTopStories } = stories;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTopStories());
}, []);
You are trying to extract an action from the reducer state. If won't work like that. To get the state, you can use the useSelector hook like you are doing. But to use the action, you need to import that function and use it in the dispatch that you already have.
Something like this:
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getTopStories } from './action.js'; // Adjust the path
const TopStories = () => {
const stories = useSelector((state) => state.stories);
const { topStories } = stories;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTopStories());
}, []);