I have two files. One is "actions.js" and the other one is "Profile.js". The first file has a function that calls an API that will get information about an user based on his/her id.
The code for "actions.js" is:
import axios from "axios";
export const getPerson = (id, history) => async dispatch => {
try {
const res = await axios.get(`http://localhost:6969/users/getUsers/${id}`);
const { email } = res.data;
console.log(email);
dispatch({
type: GET_PERSON,
payload: res.data,
});
} catch (error) {
history.push("/profile");
}
};
The code for my "Profile.js" page is:
import React, { Component } from 'react'
import { getPerson } from '../actions/personActions';
import * as PropTypes from 'prop-types'
import { connect } from "react-redux";
render() {
this.props.getPerson(id, this.props.history);
----------- Followed by render method
}
Profile.propTypes = {
getPerson: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
errors: state.errors
});
export default connect(
mapStateToProps,
{ getPerson }
)(Profile);
Problem is that I cannot show those responses in my Profile page by even localstorage if I decide to put the respective value in it. Tried with variety of ways but it shows up in the action.js page if I see from console using inspect log however it shows undefined in the Profile.js page. Please tell me where did I made it wrong. Thanks.
So, I think you're issue is that you're not dispatching anything in catch here:
} catch (error) {
dispatch({
// or something like this
type: GET_PERSON,
payload: error,
});
history.push("/profile");
}
Because of this, state is not updated with new error and state.errors is probably null or undefined.
Also, If you're trying to render anything other than the error from the state, don't forget to add it to mapStateToProps.
Other than that, I would recommend against calling api function in render. That's a really bad idea, that's because you might get a lot of useless api calls or even a loop of renders. Either call it in componentDidMount or use useEffect hook.
Generally I would advise that you go through couple of more react/redux tutorials.
Related
I am using the redux action pattern (REQUEST, SUCCESS, FAILURE) along with redux saga. I made a watcher and worker saga just like that:
import axios from 'axios';
import { put, call, takeEvery } from 'redux-saga/effects';
import * as actionTypes from 'constants/actionTypes';
import * as actions from 'actions/candidates';
const { REQUEST } = actionTypes;
// each entity defines 3 creators { request, success, failure }
const { fetchCandidatesActionCreators, addCandidateActionCreators } = actions;
const getList = () => axios.get('/api/v1/candidate/');
// Watcher saga that spawns new tasks
function* watchRequestCandidates() {
yield takeEvery(actionTypes.CANDIDATES[REQUEST], fetchCandidatesAsync);
}
// Worker saga that performs the task
function* fetchCandidatesAsync() {
try {
const { data } = yield call(getList);
yield put(fetchCandidatesActionCreators.success(data.data));
} catch (error) {
yield put(fetchCandidatesActionCreators.failure(error));
}
}
const postCandidate = params => axios.post('/api/v1/candidate/', params).then(response => response.data).catch(error => { throw error.response || error.request || error; });
// Watcher saga that spawns new tasks
function* watchAddCandidate() {
yield takeEvery(actionTypes.ADD_CANDIDATE[REQUEST], AddCandidateAsync);
}
// Worker saga that performs the task
function* AddCandidateAsync({ payload }) {
try {
const result = yield call(postCandidate, payload);
yield put(addCandidateActionCreators.success(result.data));
} catch (error) {
yield put(addCandidateActionCreators.failure(error));
}
}
export default {
watchRequestCandidates,
fetchCandidatesAsync,
watchAddCandidate,
AddCandidateAsync,
};
My reducer has two flags: isLoading and success. Both flags change based on the request, success and failure actions.
The problem is that I want my component to render different things when the success action is put on the redux state. I want to warn the component every time a _success action happens!
The flags that I have work well on the first time, but then I want them to reset when the component mounts or a user clicks a button because my component is a form, and I want the user to post many forms to the server.
What is the best practice for that?
The only thing I could think of was to create a _RESET action that would be called when the user clicks the button to fill up other form and when the component mounts, but I don't know if this is a good practice.
You need to assign a higher order component, also called a Container, that connects the store with your component. When usgin a selector, your component will automatically update if that part of the state changes and passes that part of the state as a prop to your component. (as defined in dspatchstateToProps)
Down below i have a Exmaple component that select status from the redux state, and passes it as prop for Exmaple.
in example i can render different div elements with text based on the status shown in my store.
Good luck!
import { connect } from 'react-redux'
const ExampleComponent = ({ status }) => {
return (
<div>
{status === 'SUCCESS' ? (<div>yaay</div>) : (<div>oh no...</div>)}
</div>
)
}
const mapStateToProps = state => {
return {
status: state.status
}
}
const mapDispatchToProps = dispatch => {
return {}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExampleComponent)
I have an async thunk that fetches some information from a web service, it can dispatch three types of actions
FETCH_REQUESTED
FETCH_SUCCEEDED
FETCH_FAILED
Finally, if it's succeeded; it returns the actual response, or an error object.
I have a component that should detect whether the operation has failed or not, preferably by subscribing to the FETCH_FAILED action and displaying an error message based on the type of the error (404/401 and other status codes)
export const fetchData = () => {
return async (dispatch, getState) => {
const appState = getState();
const { uid } = appState.appReducer;
await dispatch(fetchRequested());
try {
const response = await LookupApiFactory().fetch({ uid });
dispatch(fetchSucceeded(response));
return response;
} catch (error) {
dispatch(fetchFailed());
return error;
}
}
}
I'm quite new to redux and react, so I'm a bit unsure if I'm heading in the right direction, any help would be appreciated.
To implement a proper redux call back and storage mechanism you should have a store to keep all your data,
const store = createStore(todos, ['Use Redux'])
then, you dispatch data to store,
store.dispatch({
type: 'FETCH_FAILED',
text: reposnse.status //Here you should give the failed response from api
});
Then you can get the value from the store in any of your components using a subscribe function. It will be called any time an action is dispatched, and some part of the state tree may potentially have changed.
store.subscribe(()=>{
store.getState().some.deep.property
})
This is a simple implementation of Redux. As your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state using combineReducers. You can get more information from redux.js site
The most common approach is to use connect function from react-redux library. This is a HoC which subscribes to state changes. Take a look at this library, additionally it allows you to bind your action creators to dispatch, what gives you an ability to dispatch your actions from component.
You can use it like this:
import React from 'react';
import { connect } from 'react-redux';
const MyComponent = ({ data, error }) => (
<div>
{error && (
<span>Error occured: {error}</span>
)}
{!error && (
<pre>{JSON.stringify(data, null, 2)}</pre>
)}
</div>
);
const mapStateToProps = (state) => ({
data: state.appReducer.data,
error: state.appReducer.error
});
export default connect(mapStateToProps)(MyComponent);
You can use conditional rendering inside your jsx as I've shown above, or use guard clause, like this:
const MyComponent = ({ data, error }) => {
if (error) {
return (
<span>Error occured: {error}</span>
);
}
return (
<pre>
{JSON.stringify(data, null, 2)}
</pre>
);
}
Assuming reducers,
for FETCH_FAILED action,you can put some meaningful flag indicating
there are some failure.Based on that flag you can show error messages or do other action.
const testReducers =(state,actione)=>{
case 'FETCH_FAILED' : {
return {
...state,{ error_in_response : true }
}
};
default : return state;
}
In your container,you can get that flag and passed it to your component.
Assuming combineReducers used to combine reducers;
const mapStateToProps=(state)=>{
return {
error_in_response : state.testReducers.error_in_response
}
}
connect(mapStateToProps)(yourComponent)
In your component, this can be accessed using this.props.error_in_response
I'm very new to react and redux.
Now I want to rewrite my post request with a redux process.
my current request looks like this:
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
this.setState({
shortenInfos: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
now I created a store:
export default function url(state = 0, action) {
switch (action.type) {
case 'CREATE_URL':
// maybe axios request?!
return `${action.url}/test`
case 'CREATED_URL':
return `${action.url}/created`
default:
return state
}
}
so where I must use my store.dispatch()? Should I make my _handleSubmit something like this?
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
store.dispatch({
type: 'CREATED_URL',
url: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
I think this is wrong? And where I must use mapStateToProps method? Or should I do the axios-request in my CREATE_URL in my reducer?
Introduction
Using React with Redux gives you high freedom on how you can do things. The downside of this is that it can be hard to find out how things should be done properly, mainly because there is no standard or comprehensive guide to the use of the many dependency you need for a properly implemented project. This answer will guide you through the basics with links to references that will help you to find out wheres next and how to deeper your knowledge.
Reducer
Reducers should be pure, meaning that they have no side effects (like making axios requests) and they should always return a new object/array/value instead of changing the previous state. It is also a good practice to use action types as constants. You can place action types wherever you want, but for simplicity I will put them into the reducer's file, but there are better ways to organize them like using ducks.
export const CREATED_URL = 'CREATE_URL';
export default const url = (state = '', action) => {
switch (action.type) {
case CREATED_URL:
return action.url;
default:
return state;
}
};
Asynchronous actions
Everything that causes a side effect should be an action, so XHR should happen there. Because XHR should be asynchronous it is recommended to use a middleware: redux-thunk and redux-saga are two popular solutions. I will go with thunk so install it first.
First (because const has temporal dead zone) you need an action that will "load" the result of the XHR to the store:
import { CREATED_URL } from './reducer';
const createdUrl = url => ({
type: CREATED_URL,
url, // ES6 trailing comma for prettier git diffs
});
Then you can create the action that will fire the XHR, wait for the response then load it to the store using the action created previously. We need to return a function that will receive dispatch as the parameter. This technique is used in functional programming and is called currying.
export const createUrl = url => dispatch => { // with only 1 parameter the parentheses can be omited
axios
.post('/createUrl', { url }) // ES6 Shorthand property name in { url }
.then(response => {
dispatch(createdUrl({
url: response.data,
})
})
.catch(error => {
// #TODO dispatch an action that will show a message
// notifying the user that the request failed
console.log(error);
});
}
Usage in the React component.
Preparation
For ease of use, you need to connect your React component with Redux. react-redux comes to the rescue. Read the API documentation and add the <Provider> component to the root of your React component tree.
Now, in the top of your React component's file, import all the necessary stuff:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createUrl } from './reducer';
mapStateToProps and mapDispatchToProps
Then create the two helper functions for connect:
const mapStateToProps = store => ({ url: store.url })
const mapDispatchToProps = dispatch => bindActionCreators({ createUrl }, dispatch)
With the help of mapStateToProps you can subscribe to store updates and inject the important parts of the Redux store to your components props. mapStateToProps should return an object that will be merged to the component's props. Usually we just do something like store => store.url but because our example is so simple that the reducer returns a plain string instead of something more complex in an object, we need to wrap that string into an object over here.
mapDispatchToProps with the help of bindActionCreators will inject the passed actions to the component's props so we can call and pass them down to subcomponents with ease: this.props.createUrl().
The component itself
Now we can create the component itself. I will use an ES6 class to show an example with componentDidMount, but if you don't need that and you have a stateless component, you can go with a function too.
class Example extends React.Component {
componentDidMount() {
// If you need to load data from a remote endpoint place the action call here, like so:
// this.props.createUrl('your-url');
}
render() {
return (
<div>
<div>URL injected from the store, automatically refreshed on change: {this.props.url}</div>
<div onClick={event => {this.props.createUrl('your-url');}}>Click me to fetch URL</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Example)
I've got more or less the same question as this one: React-Router: how to wait for an async action before route transition
Current situation:
When I'm for example on my Home page, I click a post (e.g. /news/news-article). I immidiately navigate to the route, and I'm showing a Loader untill my Redux action has Fetched the posts content, and then I load the content. (This is also what 'jmancherje' answerred in the other question)
But this means that I have to show a loader on EVERY page my user visits, and this is not what I want.
So what I want:
When I'm on the Home, and I click to another post, I want to wait with navigation to the next route, untill my action has finished (or failed) loading the content.
This seems possible with React-Router-Redux, but I can't find out how to achieve this.
Updated question:
My action looks like this:
export function fetchPage(post_type, postSlug) {
return function (dispatch) {
dispatch({type: FETCH_PAGE_START});
axios.get(`${url}/wp-json/wp/v2/${post_type}?slug=${postSlug}`)
.then(response => {
dispatch({
type: FETCH_PAGE,
payload: response.data
});
dispatch(push('/')) // do the routing here
})
.catch(function (error) {
dispatch({
type: FAILED_PAGE
});
});
}
}
My store looks more or less like this:
const appliedMiddleware = applyMiddleware( thunk, createLogger(), routerMiddleware(history));
export default createStore(combinedReducers, appliedMiddleware);
So I think i'm on the right way, but still I can't get it to work. It still navigates immediately instead of a delay.
This is pretty much covered in details here regarding react-redux async action. But in short, one of the way is you can use redux-thunk middleware as such:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Once you have redux-thunk middleware, in your action_post.js you can do something like this:
export function fetchPost(id) {
return dispatch => {
dispatch(fetchPostRequest())
axios.get(`/api/post/${id}`)
.then(res => {
dispatch(fetchPostSuccess(res.data))
dispatch(push('/postView')) // do the routing here
})
.catch(err => {
dispatch(fetchPostFailure(err))
})
}
}
function fetchPostRequest() {
return {
type: "FETCH_POST_REQUEST"
}
}
function fetchPostSuccess(data) {
return {
type: "FETCH_POST_SUCCESS",
data
}
}
function fetchPostFailure(err) {
return {
type: "FETCH_POST_FAILURE",
err
}
}
I'm just getting started with React. I successfully used axios to get data from http and use an action to push the data. I can output the data at mapStateToProps but it does not set the data as a prop in the class. Here's my code with comments about the availability of the data.
import React from 'react';
import { connect } from 'react-redux';
import { fetchCountries } from '../../actions/actions';
import _ from 'lodash';
class TheClass extends React.Component
{
constructor(props)
{
super(props);
}
componentDidMount()
{
this.props.fetchCountries();
console.log('Fetching', this.props.countries); // !! UNDEFINED !!
}
}
function mapStateToProps(state)
{
console.log('Countries:', state.countries) // -> I get the data
return { countries: state.countries }
}
export default connect(mapStateToProps, { fetchCountries })(TheClass);
actions.js
import axios from 'axios';
export const FETCH_COUNTRIES = `fetch_countries`;
const COUNTRIES_URL = `http://api.stagingapp.io/location/v1/public/country`;
export function fetchCountries()
{
const request = axios.get(COUNTRIES_URL);
console.log(request); // -> I get the data
return {
type: FETCH_COUNTRIES,
payload: request
}
}
fetchCountries is an asynchronous operation so you can't expect the result just after calling fetchCountries as you are trying to do in componentDidMount.
If you are getting the result in connect function, then you will get the result in render function after successful network call.
Put your console here:
render() {
console.log('Fetching', this.props.countries);
}
I'd imagine that state.countries gets populated by whatever response you get from your asynchronous HTTP request in fetchCountries().
Only once this request resolves, should you get the country data. When you call fetchCountries() and immediately afterwards try to print out the value of countries, the request has not yet resolved (gotten a response), which is why you wont get any data.
Your fetch countries request in Asynchronous request, so you can't expect countries to be in store just after calling the fetchCountries() function. You will get countries data when react will re render on arrival of countries data from api.
Your function getCountries return an object with payload = a Promise return by axios, so you don't have your data when you call the function.
To make Async request you should add redux-thunk middleware, after that in your component file create a function
const mapStateToProps = (dispatch) => ({
fetchCountries: bindActionsCreator(fetchCountries, dispatch)
})
and pass this function in 2nd argument to your connect function.
In your actions.js change your function getCountries like so:
export const fetchCountries = () => (dispatch) => {
dispatch({type: FETCH_START})
axios.get(COUNTRIES_URL)
.then(response => response.data)
.then(data => dispatch({type: FETCH_COUNTRIES, payload: data})
.catch(errors => dispatch({type: FETCH_ERRORS})
}
With that, in your reducer you can set a variable loading to true when request start and pass this variable to false when Promise is resolved/rejected and after that you can create a condition to your component to be sure you have your data!