React renders my Component before it actually has Data - javascript

So I'm hard stuck on this Problem... normally I would just do a "ComponentDidMount" but since I'm trying to avoid using classes and only use react hooks I got stuck with the Problem.
My Component renders before it gets any Data from the API, so my .map function won't work as it has not recieve any data.
Shop.js
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { listShops } from "../../Redux/actions/shopActions";
const Shop = () => {
const userShop = useSelector(state => state.shop);
const auth = useSelector(state => state.auth);
const dispatch = useDispatch();
useEffect(() => {
dispatch(listShops(auth));
}, []);
console.log("Look at my userShop",userShop.shop)
return (
<div>
{userShop.map(shop=>(<div>{shop}</div>))}
{console.log("How often do I Render?")}
</div>
);
};
export default Shop;
ShopAction.js
import {GET_SHOPS} from "./types";
export const listShops = userData => async dispatch =>{
const userId = userData.user.id;
await axios.get(`/api/shops/shops/user/${userId}`)
.then(
res => {
const user = res.data;
dispatch({
type: GET_SHOPS,
payload: user.shops
})})
}
shopReducer.js
const initialState = {}
export default function(state = initialState, action) {
switch (action.type) {
case GET_SHOPS:
return {
...state,
shop:action.payload
}
default:
return state;
}
}

if(!userShop){
return <h1>loading<h1>;
}
return (
<div>
{userShop.map(shop=>(<div>{shop}</div>))}
</div>
);

Return an empty array if state.shop is undefined using short-circuit evaluation:
const userShop = useSelector(state => state.shop || []);

return (
<div>
{userShop && userShop.map(shop=>(<div>{shop}</div>))}
</div>
);

Related

Redux - thunkMiddleware - dispatch as an extra argument doesn't work

Why isn't dispatch read in fetchit()?
thunkMiddleware is added to applyMiddleware
introduce dispatch to fetchit in mapDispatchToProps
dispatch is added as an extra argument in fetchit()
I can go around this and make it work in other ways like for example, initializing a global variable "dispatch" and in the component -> dispatch = useDispatch() ... and multiple other ways ...
But I found online that doing it in this particular way should work, so I was wondering what is missing here...
import React from 'react';
import { createStore, applyMiddleware} from "redux";
import loggerMiddleware from "redux-logger";
import { connect } from 'react-redux'
import { useRef } from 'react';
import thunkMiddleware from "redux-thunk";
const ADD_TASK = "addTask";
const addTask = (task) => {return ({
type: ADD_TASK,
payload: task
})}
const reducer = (state=[], action) => {
switch (action.type){
case ADD_TASK:
return [...state, ...action.payload];
default:
return state;
}
}
export const store = createStore(reducer, applyMiddleware(thunkMiddleware,loggerMiddleware));
const fetchit = (newTask="") =>(dispatch)=>{
console.log(">>>>>>>>>>>>>>>",dispatch); // <<<<<<<<<<<< dispatch is not read
if (!newTask){
fetch("/get")
.then(response => response.json())
.then(result => {
dispatch(addTask(result.tasks));
})
.then(()=>{
document.documentElement.scrollIntoView(false);
})
}
else {
fetch("/add", {
credentials: "include",
method: "POST",
mode: "same-origin",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
//"X-CSRFToken": csrf
},
body: JSON.stringify(newTask),
})
.then(response => response.json())
.then(result => {
if (result.task)
dispatch(addTask([result.task]));
})
.then(()=>{
document.documentElement.scrollIntoView(false);
})
}
}
const Tasks = (props) => {
const {state} = props;
return [state.map(task => (
<li key={task.id} style={{color:task.done?"blue":"red"}}>
{task.task} <section style={{color:"black"}}>{task.description}</section>
<section style={{color:"lightgray"}}>{task.dateTime}</section>
</li>
))]
}
var firstFetch = true;
const App = (props) => {
if (firstFetch){
firstFetch = false;
fetchit();
}
const inputTask = useRef(null);
const inputDescription = useRef(null);
const fetchNewInput = () => {
let bodydict = {"task": inputTask.current.value, "description": inputDescription.current.value};
fetchit(bodydict);
inputTask.current.value = "";
inputDescription.current.value = "";
}
return <>
<h1>Tasks</h1>
<ol>
<Tasks state={props.state || []}/>
</ol>
<div>
<input ref={inputTask}/>
<textarea ref={inputDescription}></textarea>
<button onClick={fetchNewInput}>ADD</button>
</div>
</>
}
const mapStateToProps = (state) => {
return {state: state};
}
const mapDispatchToProps = (dispatch) => {return {
fetchit:(something)=> dispatch(fetchit(something)),
addTask: (something) => dispatch(addTask(something)),
}}
export default connect(mapStateToProps, mapDispatchToProps)(App);
The missed step --> extracting functions from props
const App = ({fetchit}) => {}
In this section:
const fetchNewInput = () => {
let bodydict = {"task": inputTask.current.value, "description": inputDescription.current.value};
fetchit(bodydict);
inputTask.current.value = "";
inputDescription.current.value = "";
}
you are calling the thunk itself, while you should dispatch fetchit. you have access to dispatching fetchit via props.fetchit, as you defined in mapDispatchToProps
props.fetchit(bodydict);
I guess the same naming got you confused.
also, you can dispatch addTask like this:
props.addTask(task);

Redux Toolkit State issue when sending to child component

I am creating react redux application using redux toolkit and I'm passing some props to child component, it supposed to be one post because I'm using a map in parent component and passing one data to each component.
I'm trying to do Edit button and when clicking the "Edit button" trying to send ID to redux store but there is an error. If anyone know the answer please let me know.
Below is my redux slice:
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
allPosts: [],
loading: "idle",
error: "",
currentId: "",
};
export const fetchAlltAsync = createAsyncThunk(
"allposts",
async (_, thunkAPI) => {
try {
const response = await axios.get("http://localhost:5000/posts/");
// The value we return becomes the `fulfilled` action payload
return response.data;
} catch (error) {
throw thunkAPI.rejectWithValue({ error: error.message });
}
}
);
export const postsingleAsync = createAsyncThunk(
"postsingleAsync",
async (post, { dispatch }) => {
const response = await axios.post("http://localhost:5000/posts/", post);
return response.data;
}
);
export const idsingleAsync = createAsyncThunk(
"idsingleAsync",
async (id, updatedpost) => {
const response = await axios.patch(
`http://localhost:5000/posts/${id}`,
updatedpost
);
return response.data;
}
);
export const postSlice = createSlice({
name: "posts",
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
// Use the PayloadAction type to declare the contents of `action.payload`
newsetcurrentId: (state, action) => {
state.currentId = action.payload;
},
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder.addCase(fetchAlltAsync.pending, (state) => {
state.allPosts = [];
state.loading = "Loading";
});
builder.addCase(fetchAlltAsync.fulfilled, (state, action) => {
state.allPosts = action.payload;
state.error += "Loaded";
});
builder.addCase(fetchAlltAsync.rejected, (state, action) => {
state.allposts = "data not loaded";
state.loading = "error";
state.error = action.error.message;
});
builder.addCase(idsingleAsync.fulfilled, (state, action) => {
state.currentId = action.payload;
});
},
});
export const { setcurrentId, newsetcurrentId } = postSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state) => state.counter.value;
// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd = (amount) => (dispatch, getState) => {};
export default postSlice.reducer;
Below is my parent component:
import React, { useEffect, useState } from "react";
import Post from "./Post";
import { useSelector, useDispatch } from "react-redux";
const Posts = ({ SETCURRENTID, CURENTID }) => {
// const dispatch = useDispatch();
const posts = useSelector((state) => state.posts.allPosts);
return (
<div>
{posts &&
posts.map(({ _id, ...rest }) => (
<Post key={_id} rest={rest} id={_id} />
))}
</div>
);
};
export default Posts;
This is my child component:
import React from "react";
import moment from "moment";
import { idsingleAsync, newsetcurrentId } from "../../features/postSlice";
import { useSelector, useDispatch } from "react-redux";
const Post = ({ rest, _id }) => {
const dispatch = useDispatch();
console.log(rest, "gff");
//const { id } = this.rest._id;
const handleClick = () => dispatch(newsetcurrentId());
return (
<div>
<h1>{rest.title}</h1>
<img
style={{ maxWidth: "250px", border: "12px solid purple" }}
alt="d"
src={rest.selectedFile}
/>
<h2>{moment(rest.createdAt).fromNow()}</h2>
<button onClick={() => dispatch(newsetcurrentId(rest._id))}> edit</button>
<h5>{rest.tags.map((tag) => `#${tag} `)}</h5>
<h5 onClick={() => {}}>{rest.likeCount}</h5>
<button onClick={() => {}}>Delete</button>
</div>
);
};
export default Post;
This is the redux error:
requestId(pin):undefined
TL;DR
Instead of rest._id , try passing the id prop to your newsetcurrentId dispatch:
const Post = ({ rest, id }) => { //Change _id to id
const dispatch = useDispatch();
const handleClick = () => dispatch(newsetcurrentId());
return (
<div>
<h1>{rest.title}</h1>
<img
style={{ maxWidth: "250px", border: "12px solid purple" }}
alt="d"
src={rest.selectedFile}
/>
<h2>{moment(rest.createdAt).fromNow()}</h2>
{/* pass id here */}
<button onClick={() => dispatch(newsetcurrentId(id))}> edit</button>
<h5>{rest.tags.map((tag) => `#${tag} `)}</h5>
<h5 onClick={() => {}}>{rest.likeCount}</h5>
<button onClick={() => {}}>Delete</button>
</div>
);
};
Explanation
When you are doing this destructuring:
posts.map(({ _id, ...rest }) => ( your rest object will actually contain all the post properties apart from _id so you don't actually have rest._id which you are trying to access on your Post child.
Additionally, you are passing id={_id} as a prop from the parent to the child, so you don't actually have an _id prop on your Post component (change it to id).

Add a selector using reselect in redux

I created an application what filters data according to user input.
// ui component
import React from 'react';
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "../store/actions/search";
const Search = () => {
const dispatch = useDispatch();
const selector = useSelector(s => s.search);
const search = (e) => {
const txt = e.target.value;
dispatch(searchPersonAction(txt));
};
return (
<div>
<input onChange={search} placeholder="search"/>
<ul>
{
selector.name.map(p => <li key={p.name}>{p.name}</li>)
}
</ul>
</div>
);
};
export default Search;
// action
import { SEARCH } from './actionTypes';
import { persons } from "../../mock__data";
export const searchPersonAction = (person) => {
const personSearched = persons.filter(p => p.name.toLowerCase().includes(person.toLowerCase()));
console.log(personSearched);
return {
type: SEARCH.SEARCH_PERSON,
payload: personSearched,
}
};
//reducer
import { SEARCH } from '../actions/actionTypes';
import { persons } from "../../mock__data";
const initialState = {
name:persons
};
export const search = (state = initialState, { type, payload }) => {
switch (type) {
case SEARCH.SEARCH_PERSON:
return {
...state,
name: payload
};
default:
return state;
}
};
Above I filter using: const personSearched = persons.filter(p => p.name.toLowerCase().includes(person.toLowerCase())); and I get on ui using above Search component.
Question: How to use reselect library in my example?
The examples in the Reselect documentation will get you there. The filtering you mentioned would become your reselector:
import { createSelector } from 'reselect'
import { persons } from "../../mock__data";
const nameSelector = state => state.name;
export const searchedPersonsSelector = createSelector(
nameSelector,
name => persons.filter(p => p.name.toLowerCase().includes(name.toLowerCase()));
);
inside your component you can import the selector and use the useSelector hook as you are already doing:
import { searchedPersonsSelector } from "./selectors";
const Persons = () => {
const searchedPersons = useSelector(searchedPersonsSelector);
return (
...
);
};

Why is State Undefined? [Hooks/Redux]

I'm trying to use Redux via hooks but the state keeps coming back with an empty array rather than the data from the fetch request.
Actions
export const loading = payload => {
return {
type: types.LOADING,
payload
}
}
export const getBudget = payload => {
return {
type: types.BUDGET_DATA,
payload
}
}
export const budgetData = () => {
return dispatch => {
dispatch(loading(true))
const url = `${URL_BUDGET}`
fetch(url)
.then(response => dispatch(getBudget(response.data)))
.catch(err => console.log(err))
dispatch(loading(false))
}
}
Reducer
import * as types from '../types'
const initialState = {
budget: []
}
export default (state = initialState, action) => {
switch (action.types) {
case types.BUDGET_DATA:
return {
...state,
budget: action.payload
}
default:
return state
}
}
Component
const Home = () => {
useDispatch(budgetData(), categoryData())
const state = useSelector(state => state.data)
const budgets = useSelector(state => state.data.budget)
const categories = useSelector(state => state.data.category)
//console.log(this.props.dataReducer)
return (
<div>
content
</div>
)
}
export default Home
I can't seem to understand why the fetch request isn't fulfilled.
My API has the following format of data...
{"meta":{},"data":{"example":[{"timestamp":28378545,"value":5}],...}}
Is there an issue with dispatching?! Adding loading hasn't helped either!
useDispatch returns a dispatch function that subsequently needs to be called. If you want to do this just one time when the component is first rendered, you can pair it with a useEffect that has no dependencies:
const Home = () => {
const dispatch = useDispatch()
const budgets = useSelector(state => state.data.budget)
const categories = useSelector(state => state.data.category)
useEffect(() => {
dispatch(budgetData())
dispatch(categoryData())
}, [])
return (
<div>
content
</div>
)
}
export default Home

Updating redux state onClick

I have a component that displays data from the state. I'm using redux for state. I want to be able to click a button and filter the state. But I'm stuck on dispatching the action from the button.
Right now I have a button that is supposed to dispatch the action but it's not being called. I'm not sure if the mapsToDispatchProps is wrong or it's something else.
Here is the actions
import { GET_POLLS, SHOW_APPROVAL } from './types';
const URL = 'https://projects.fivethirtyeight.com/polls/polls.json';
export const getPolls = () => dispatch => {
return fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
}
export const getApproval = () => ({ type: SHOW_APPROVAL })
reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return (payload.type === "trump-approval")
default:
return state
}
}
export default pollReducer;
types
export const GET_POLLS = 'GET_POLLS';
export const POLLS_LOADING = 'POLLS_LOADING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_APPROVAL = 'SHOW_APPROVAL';
list that displays data
import React, { Component } from 'react'
import { PollCard } from '../Components/PollCard'
// import FilterLink from './FilterLink'
import * as moment from 'moment';
import { connect } from 'react-redux'
import { getPolls, getApproval } from '../actions/index';
class PollList extends Component {
componentDidMount() {
this.props.getPolls();
}
render() {
console.log("rendering list")
const { polls } = this.props
const range = 30
var dateRange = moment().subtract(range, 'days').calendar();
var filteredPolls = polls.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange)).reverse()
return (
<React.Fragment>
<button onClick={getApproval}>
Get Approval
</button>
{console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
polls: state.polls
});
const mapDispatchToProps = {
getApproval
};
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getPolls, getApproval }
)(PollList);
// export default PollList;
Your mapDispatchToProps() appears to be configured incorrectly. You need to define a function that returns an object, defining a key-value pair for each action you want to make available as a prop in your component.
const mapDispatchToProps = (dispatch) => {
return {
getApproval: () => {
dispatch(getApproval())
},
getPolls: () => {
dispatch(getPolls())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProp)(PollList);
Now getPolls is available as prop and you can use it in componentDidMount()
componentDidMount() {
this.props.getPolls();
}
You should also create an onClick handler for your getApproval action
handleClick = () => {
this.props.getApproval()
}
And then connect it to your onClick event-listener
<React.Fragment>
<button onClick={this.handleClick}>
Get Approval
</button>
console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
Action File
export const getPolls = () => dispatch => {
fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
.catch(errors => {
dispatch({ type: "GET_ERRORS", payload: errors.response.data })
})
}
Reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return state.filter((poll) => {
return poll.type === "trump-approval"
})
case "GET_ERRORS":
return payload
default:
return state
}
}
export default pollReducer;
You are not calling the action function.
// Either destructure it
const { polls, getApproval } = this.props;
<button onClick={getApproval}>
Get Approval
</button>
// Or use this.props.function
<button onClick={this.props.getApproval}>
Get Approval
</button>
// You don't need this
const mapDispatchToProps = {
getApproval
};
// You don't need this
const mapStateToProps = state => {
return {polls: state.polls};
};
export default connect(
mapStateToProps,
// Doing this is easier, cleaner & faster
{ getPolls, getApproval }
)(PollList);
Here you are doing it correctly;
componentDidMount() {
this.props.getPolls();
}

Categories

Resources