I'm learning React and Redux. And I may have a really basic question.
I want to get a single story from my backend using the Redux function mapStateToProps (#1). So I wrote the function getSingleStory which takes the id as argument and returns the story data (#2). When I log the response data of the getSingleStory in the console, it shows me the correct story fetched from the backend (#3):
However, if the console logs the story array in my component (#4), it outputs all stories from my database, not just the single story I wanted to fetch (see picture). If I want to display 'Story.title', in my render function of course it does not work.
If someone could explain to me why in the response data the single story is included and in the const story = this.props.story; all stories suddenly appear, that would help me a lot.
export class StoryDetails extends Component {
componentDidMount() { // #2
this.props.getSingleStory(this.props.match.params.id);
}
render() {
const story = this.props.story;
console.log (story); // #4
return (
<div>
<h2>{story.title}</h2>
</div>
);
}
}
const mapStateToProps = state => ({story: state.story}); //#1
export default connect(
mapStateToProps,
{ getSingleStory, deleteStory}
)(StoryDetails);
Action
// GET SINGLE STORY
export const getSingleStory = id => (dispatch, getState) => {
return new Promise((resolve, reject) => {
axios.get( apiBase + `/story/${id}/`, tokenConfig(getState))
.then(res => {
dispatch({
type: GET_SINGLE_STORY,
story: res.data
}, console.log (res.data)); //#3
resolve(res);
})
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status));
reject(err);
});
});
};
Reducer
import { GET_SINGLE_STORY } from "../actions/types.js";
export default function (state = {}, action) {
switch (action.type) {
case GET_SINGLE_STORY:
return action.story;
default:
return state;
}
};
Many Thanks in advance!
Related
I am working with plaid API and I want to view all of my transactions from my endpoint but I keep receiving a 300 error. However, When I used this endpoint earlier I went through perfectly with no errors. In fact, even when I use postman I am able to receive the data. I tried playing alot with the dependency array in the useeffect method.
Blocks.js
const Blocks = props => {
useEffect(() => {
props.blocksData()
},[props])
return (
"hi")
function mapStateToProps(state){
console.log(state)
return {
blocks:state.blockReducer.blocks,
random:"hi"
}
}
const mapDispatchToProps = {
blocksData
}
export default connect(mapStateToProps,mapDispatchToProps)(Blocks)
userBlocks.js
export function blocksData(data){
return function(dispatch) {
dispatch(blocksLoading());
return axios.get('https://lambda-budget-blocks.herokuapp.com/plaid/transactions/1')
.then(response => {
dispatch(blocksSuccess(response.data.categories))
})
.catch(error=>{
dispatch(blocksFailure(error));
})
}
}
NOTHING IS WRONG WITH THE REDUX SET UP BTW
First, I made a small application on the React.js. Using the fetch method, I take the API
And these are the main files of my application:
Index.js:(action)
export const SHOW_AIRPLANES = "SHOW_AIRPLANES";
export function showAirplanes() {
return (dispatch, getState) => {
fetch("https://api.iev.aero/api/flights/25-08-2019").then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.data });
});
};
}
airplanes.js:(reducer)
import { SHOW_AIRPLANES } from '../actions'
const initialState = {
list: []
}
export function showAirplanes(state = initialState, action) {
switch (action.type) {
case SHOW_AIRPLANES:
return Object.assign({}, state, {list: action.payload})
default:
return state
}
}
index.js(reducer):
import { combineReducers } from "redux";
import { showAirplanes } from "./airplanes";
const rootReducer = combineReducers({
user: showAirplanes
});
export default rootReducer;
First, you should use the createStore function like so:
const initialData = {}; // whatever you want as initial data
const store = createStore(reducers, initialData, applyMiddleware(thunk));
Then pass it to your provider
<Provider store={store}>
{...}
</Provider
next, when you map your reducers inside the combineReducers function, each key in this object represents a piece of your state. So when you do user: showAirplanes it means that you intend to use it in the mapStateToProps with state.user.list so I think you meant to call it airplane: showAirplanes.
Then, your reducer name is not informative enough, I would suggest to change it to airplanesReducer.
Next issue, the call to fetch returns a response that has JSON that must be resolved.
Change this:
fetch("https://api.iev.aero/api/flights/25-08-2019").then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.data });
});
To this:
fetch("https://api.iev.aero/api/flights/25-08-2019")
.then(res => res.json())
.then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.body.departure });
});
Note that I've changed the value that you need to resolve from the response as well.
Inside your App.js component you need to create a constructor and bind the renderAirplaneList function to this
// Inside the App class
constructor(props) {
super(props);
this.renderAirplaneList = this.renderAirplaneList.bind(this);
}
And finally (I hope I didn't miss anything else), you map your state in the App.js component to { airplanes: state.airplanes.list} so the name of the prop you expect inside your component is props.airplanes.
renderAirplaneList() {
if (!this.props.airplanes.length) {
return null;
}
const arr = this.props.airplanes || [];
return arr.map(airplane => {
return (
<tr key={airplane.id}>
<td>{airplane.ID}</td>
<td>{airplane.term}</td>
<td>{airplane.actual}</td>
<td>{airplane["airportToID.city_en"]}</td>
</tr>
);
});
}
Make sure you go over the documentation of React and Redux, they have all the information you need.
Good luck.
aren't you suppose to send some parameters to this call?
this.props.showAirplanes()
it seems that it has 2 parameters: state and action, although state seems to have already it's default value
Data is appending next to the datatable, not inside.
I am fetching data (array of records) from an API in actions of vuex and returning state (array) from getters to the components where datatables have been used.
import axios from "../../assets/constants";
import router from '../../router'
const state = {
users: []
}
const getters = {
users: state => state.users,
blockedUsers: state => state.blockedUsers,
user: state => state.user
}
const actions = {
async getUsers({ commit }) {
await axios.get(`user`)
.then(res => {
commit('setGetUsers', res.data)
})
.catch(err => console.log(err.response.data.message));
})
},
const mutations = {
setGetUsers: (state, newUsers) => (state.users = newUsers),
}
export default {
state,
getters,
actions,
mutations
}
<script>
import { mapGetters, mapActions } from "vuex";
export default {
methods: {
...mapActions(["getUsers"])
},
computed: mapGetters(["users"]),
created() {
this.getUsers();
$(".zero-configuration").DataTable();
}
};
</script>
Result should be as that data that I am fetching from API must show inside datatable.
As far I understand, issue that has been causing here is that
$(".zero-configuration").DataTable();
this is executing before
this.getUsers()
which shouldn't be correct explanation because I have used await with axios.
Can anyone explain why is this happening?
It turns out when I commit mutation after I get response from axios, it takes time to set the state. Since I am not using promise here, while the state is being mutate,
$(".zero-configuration").DataTable();
takes control from
this.getUsers()
and get executed before it finishes.
I encountered this problem by using promise in getUsers action
getUsers({ commit }) {
return new Promise(async (resolve) => {
await axios.get(`user`)
.then(async res => {
await commit('setGetUsers', res.data)
resolve()
})
.catch(err => console.log(err.response.data.message));
})
},
Now it works like a charm!
I'm trying to find a way to pass a state to an action or a reducer. For example
I want to be able to run the onDelete function on the action then update the state on the reducer. However, in order for this to work, i would need to filter through the posts then i would able to remove a post.
class Posts extends Component {
state = {
posts: [],
loading: true,
}
getPosts = () => {
Axios.get(process.env.REACT_APP_GET_POSTS)
.then( (res) => {
this.setState({
posts: res.data,
loading: false
})
})
// console.log(this.state.posts);
}
componentWillMount(){
this.getPosts();
}
// run this logic on the reducer or on actions.
onDelete = (id) => {
Axios.post(`/api/posts/delete/${id}`);
this.setState({
posts: this.state.posts.filter(post => post.id !== id)
})
}
render() {
const {loading, posts} = this.state;
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList DeletePost={this.onDelete} posts={posts}/>
</div>
);
}
}
Here is the attempt to make into an action, which technically works.
actions
export const DeletePost = (id) => {
return (dispatch) => {
return Axios.post(`/api/posts/delete/${id}`)
.then( () => {
dispatch({type: DELETE_POST, id});
});
}
}
Then we approach the problem of actually getting the posts on the reducer. The problem is that the reducer does not know where the posts are coming from, its undefined. So i want to know how would i pass the state to the reducer.
and will return
state.posts.filter is not a function or something along those lines.
reducer.js
import { DELETE_POST} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[]
}
export default (state = initialState, action) => {
switch (action.type) {
case DELETE_POST:
return ({
...state,
posts: state.posts.filter(post=> post.id !== action.id)
})
default:
return state
}
}
How would i get pass the state to the actions, so that i would be able to update the state on the reducer ?
I'm trying to find a way to pass a state to an action or a reduce
The way you wrote your actions code indicates you're using redux thunk, which means you can access the getState function in your action. Example usage of getState is here
export const DeletePost = (id) => {
return (dispatch, getState) => {
return Axios.post(`/api/posts/delete/${id}`)
.then( () => {
dispatch({type: DELETE_POST, id});
});
}
}
you already have access to the state in your reducer code. Its called state!
Now, the above could the end of the answer. But I'm questioning the premise of what you're doing in the class.
// run this logic on the reducer or on actions.
onDelete = (id) => {
Axios.post(`/api/posts/delete/${id}`);
this.setState({
posts: this.state.posts.filter(post => post.id !== id)
})
}
Above you're filtering for the posts after you've already filtered/deleted it from redux (i.e. you're filtering unnecessarily twice). You should instead just be getting the state directly from redux
Take a look here. For an example of this being used in a more robust setting. I would direct you to this example. For the example, look at src/containers/visibleTodoList
So really for what you're doing, posts should just live with redux and not in the class component!
Lastly for the error you saw
state.posts.filter is not a function or something along those lines.
Could you give the exact error? your reducer code seems fine.
I have the following React component that shows all the users posts through the "renderPosts" method. Below it there's a like/unlike button on whether the currently logged in user has liked the post.
However, when I click on the like button, the component does not re-render in order for the "renderPosts" method to create an unlike button and the "like string" is modified as expected. Only when I go to another component and then come back to this component does the unlike button display and vice versa.
Is there anyway that I could fix this with Redux in my app? I tried this.forceUpdate after the onClick event but still does not work...
Also I tried creating a new Reducer called "likers", according to robinsax which basically get the array of users who like a particular post and imported it as props into the component but got
"this.props.likers.includes(currentUser)" is not a function
When the app first gets to the main page (PostIndex), probably because this.props.likers is still an empty object returned from reducer
Here is the code for my action creator:
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return{
type: LIKE_POST,
payload: post
}
}
export function unlikePost(username,postId){
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/unlikePost`,request);
return{
type: UNLIKE_POST,
payload: post
}
}
And this is my reducer:
import {LIKE_POST,UNLIKE_POST} from '../actions/index.js';
export default function(state = {},action){
switch(action.type){
case LIKE_POST:
const likers = action.payload.data.likedBy;
console.log(likers);
return likers;
case UNLIKE_POST:
const unlikers = action.payload.data.likedBy;
console.log(unlikers);
return unlikers;
default:
return state;
}
}
I would really appreciate any help since I'm a beginner
import { fetchPosts } from "../actions/";
import { likePost } from "../actions/";
import { unlikePost } from "../actions/";
class PostsIndex extends Component {
componentDidMount() {
this.props.fetchPosts();
}
renderPost() {
const currentUser = Object.values(this.props.users)[0].username;
return _.map(this.props.posts, post => {
return (
<li className="list-group-item">
<Link to={`/user/${post.username}`}>
Poster: {post.username}
</Link>
<br />
Created At: {post.createdAt}, near {post.location}
<br />
<Link to={`/posts/${post._id}`}>{post.title}</Link>
<br />
//error here, with this.props.likers being an
//array
{!this.props.likers.includes(currentUser) ? (
<Button
onClick={() => this.props.likePost(currentUser,post._id)}
bsStyle="success"
>
Like
</Button>
) : (
<Button
onClick={() => this.props.unlikePost(currentUser,post._id)}
bsStyle="warning"
>
Unlike
</Button>
)}{" "}
{post.likedBy.length === 1
? `${post.likedBy[0]} likes this`
: `${post.likedBy.length} people like this`}
</li>
);
});
}
function mapStateToProps(state) {
return {
posts: state.posts,
users: state.users,
likers: state.likers
};
}
}
Seems like the like/unlike post functionality isn't causing anything in your state or props to change, so the component doesn't re-render.
You should change the data structure you're storing so that the value of post.likedBy.includes(currentUser) is included in one of those, or forceUpdate() the component after the likePost and unlikePost calls.
Please do it the first way so I can sleep at night. Having a component's render() be affected by things not in its props or state defeats the purpose of using React.
As noted in other answers, you need to use redux-thunk or redux-saga to make async calls that update you reducer. I personally prefer redux-saga. Here's is a basic implementation of React, Redux, and Redux-Saga.
Redux-Saga uses JavaScript generator functions and yield to accomplish the goal of handling async calls.
Below you'll see a lot of familiar React-Redux code, the key parts of Redux-Saga are as follows:
watchRequest - A generator function that maps dispatch actions to generator functions
loadTodo - A generator function called from watchRequest to yield a value from an async call and dispatch an action for the reducer
getTodoAPI - A regular function that makes a fetch request
applyMiddleware - from Redux is used to connect Redux-Saga with createStore
const { applyMiddleware, createStore } = Redux;
const createSagaMiddleware = ReduxSaga.default;
const { put, call } = ReduxSaga.effects;
const { takeLatest } = ReduxSaga;
const { connect, Provider } = ReactRedux;
// API Call
const getTodoAPI = () => {
return fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
return response.json()
.then(response => response);
})
.catch(error => {
throw error;
})
};
// Reducer
const userReducer = (state = {}, action) => {
switch (action.type) {
case 'LOAD_TODO_SUCCESS':
return action.todo;
default:
return state;
}
};
// Sagas, which are generator functions
// Note: the asterix
function* loadTodo() {
try {
const todo = yield call(getTodoAPI);
yield put({type: 'LOAD_TODO_SUCCESS', todo});
} catch (error) {
throw error;
}
}
// Redux-Saga uses generator functions,
// which are basically watchers to wait for an action
function* watchRequest() {
yield* takeLatest('LOAD_TODO_REQUEST', loadTodo);
}
class App extends React.Component {
render() {
const { data } = this.props;
return (
<div>
<button onClick={() => this.props.getTodo()}>Load Data</button>
{data ?
<p>data: {JSON.stringify(data)}</p>
: null
}
</div>
)
}
}
// Setup React-Redux and Connect Redux-Saga
const sagaMiddleware = createSagaMiddleware();
const store = createStore(userReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchRequest);
// Your regular React-Redux stuff
const mapStateToProps = (state) => ({ data: state }); // Map the store's state to component's props
const mapDispatchToProps = (dispatch) => ({ getTodo: () => dispatch({type: 'LOAD_TODO_REQUEST'}) }) // wrap action creator with dispatch method
const RootComponent = connect(mapStateToProps, mapDispatchToProps)(App);
ReactDOM.render(
<Provider store={store}>
<RootComponent />
</Provider>,
document.getElementById('root')
);
<script src="https://npmcdn.com/babel-regenerator-runtime#6.3.13/runtime.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/0.16.2/redux-saga.min.js"></script>
<div id="root"></div>
You need to use redux-thunk middleware in order to use async actions.
First, add redux-thunk while creating store like
import thunk from 'redux-thunk';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
then change your method like this
export function likePost(username,postId) {
return function(dispatch) {
// body...
const request = {
username,
postId
}
axios.post(`${ROOT_URL}/likePost`,request)
.then(res => {
dispatch({
type: LIKE_POST,
payload: res
});
});
}
}
and now in your component after mapStateToProps, define mapDispatchToProps,
const mapDispatchToProps = dispatch => {
return {
likePost: (currentUser,postId) => dispatch(likePost(currentUser, postId)),
// same goes for "unlike" function
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
The problem is in your action creator.
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
// this is an async call
const post = axios.post(`${ROOT_URL}/likePost`,request);
// next line will execute before the above async call is returned
return{
type: LIKE_POST,
payload: post
}
}
Because of that your state is likely never updated and stays in the initial value.
You would need to use either redux-thunk or redux-saga to work with async actions.
As they say use redux-thunk or redux-saga. If your new to redux I prefer redux-thunk because it's easy to learn than redux-saga. You can rewrite your code like this
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return dispatch => {
post.then(res => {
dispatch(anotherAction) //it can be the action to update state
});
}
}