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).
Related
I have two separate components. I want to have a button that when clicked on will add an element to an array in my reducer and redirect to another component, this component that gets redirected to needs to render the data that was just added to the array. The page redirects to the component I want but the data does not load and the console.logs don't show anything.
This is the component that has the redirect button. On this component the console.log(socialNetworkContract.members[0]) shows the string I expect.
const Posts = () => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
window.location.href='/member'
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default Posts;
This is my reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
and this is the component that is redirected to. this just says undefined in console.log(socialNetworkContract.members[0])
const Member = () => {
const [user, setUser] = useState({});
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
useEffect(async()=>{
try {
const pro = socialNetworkContract.members[0]
console.log(socialNetworkContract.members[0])
await setUser(pro)
console.log(socialNetworkContract.members[0])
} catch (e) {
console.error(e)
}
}, [])
I have the route set in Routes.js as
<Route path="/member" exact component={Member} />
Use history.push('/') instead of window.location.href which will reload your whole page and you will lost your local state data.
const {withRouter} from "react-router-dom";
const Posts = (props) => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member');
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter( Posts );
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
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>
);
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();
}
|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...