I'm new to React. I'm displaying a list of songs and I want to allow the user to add songs to their favourites. I'm using Redux to store the favourited songs. My PlayList component looks like:
import AddSong from '../containers/AddSong'
class Playlist extends Component {
render(){
return (
<div>
<h1>Playlists</h1>
<ul className="container">
{this.state.items.map(item =>
<li key={item.track.id}>
{item.track.name} by {item.track.artists[0].name}
<img src={item.track.album.images[0].url} height="150" width="150" />
<AddSong title={item.track.name} />
</li>
)}
</ul>
</div>
);
}
...
}
So I passing the song name to AddSong with <AddSong title={item.track.name} />
And AddSong looks like:
import React from 'react'
import { connect } from 'react-redux'
import { addSong } from '../actions'
let AddSong = ({ dispatch }) => {
let input
console.log('this is ', this);
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
// GET SONG FROM PROPS AND DISPATCH
//dispatch(addSong(input.value))
}}
>
<input
ref={node => {
input = node
}}
/>
<button type="submit">
Add Song
</button>
</form>
</div>
)
}
AddSong = connect()(AddSong)
export default AddSong
However, this is an object with the property:
{
a: Connect(props, context)
}
How do I get the song title in AddSong?
EDIT
So this is what I have now, Im passing the song title to AddSong here:
<AddSong song={item.track.name} title={item.track.name} />
I'm passing the song title in as song and title to show what happens.
In AddSong, I have:
const mapStateToProps = (state) => {
const {song:song} = state; // or whatever the reducer called
return {song};
};
const mapDispatchToProps = (dispatch) => ({
addSong: (value) => dispatch(addSong(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(AddSong);
And at the top of AddSong I'm doing:
let AddSong = ({ dispatch, ...props }) => {
let input
console.log('props is ', props);
The console outputs:
props is Object {song: undefined, title: "Young Blood"}
I've changed the button to:
<button onClick={(value)=>props.addSong(value)}>
Add Song
</button>
When I click, this gives the error:
Failed prop type: Invalid prop `songs[0].text` of type `object` supplied to `SongList`, expected `string
Try to use this function
const mapStateToProps = function(store) {
return {
data: store.data
};
}
AddSong = connect(mapStateToProps)(AddSong)
I assume you have the reducer and you want to access the state via props, if this the case you can mapStateToProps e.g.
const mapStateToProps = (state) => {
const {song:song} = state; // or whatever the reducer called
return {song};
};
const mapDispatchToProps = (dispatch) => ({
addSong: (value) => dispatch(addSong(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(AddSong);
then you can just write this.props.song.
Related
I have a function in src/actions/index.js which is fetching data from an external api using axios. It uses a dispatch method and sets the payload as the data fetched from the api.
I then import the function in my src/App.js like so:
import {fetchPosts} from "./actions/index.js";
fetchPosts then gets passed as a prop to the Button component, along with configureButton.buttonText (gets correctly passed), which when clicked should trigger the function to fetch the api; however, it does not run.
const App = ({ posts }) => {
const configureButton = {
buttonText: "Get Posts",
// this is where the fetchPosts f() gets set as the emitEvent prop
emitEvent: fetchPosts,
};
return (
<div className="App">
<Header />
<section className="main">
//destructure the configureButton object
<Button {...configureButton} />
</section>
</div>
)
}
const mapStateToProps = state => {
return {
posts: state.posts,
};
};
export default connect(mapStateToProps, { fetchPosts })(App);
Button.js component:
const Button = ({ buttonText, emitEvent}) => {
const submitEvent = () => {
if(emitEvent){
emitEvent()
}
}
return (
<button onClick={submitEvent} data-test="buttonComponent">{buttonText}</button>
)
}
export default Button
Why is the fetchPosts function not being called?
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).
So, i wrote a test project to explore react, react-router and react-redux.
After i got everything working fine i laid my eyes again on Settings.jsx and i am wondering how could i make it less verbose and error prone:
import React, { Component } from "react";
import { connect } from "react-redux";
class Settings extends Component {
state = { name: this.props.settings.name };
render() {
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}/>
<button onClick={e => this.props.changeName(this.state.name)}>
Change
</button>
</div>
);
}
}
const mapState = state => ({ settings: state.settings });
const mapDispatch = dispatch => {
return {
changeName(name) {
dispatch({ type: "setName", name });
}
};
};
export default connect(
mapState,
mapDispatch
)(Settings);
My first idea was to convert it into a functional component, but it's said that they don't have state and i need the state to locally handle the input.
With #babel/plugin-proposal-decorators, connect can be used as a decorator:
import React, { Component } from "react";
import { connect } from "react-redux";
const mapState = state => ({ settings: state.settings });
const mapDispatch = dispatch => {
return {
changeName(name) {
dispatch({ type: "setName", name });
}
};
};
#connect(mapState, mapDispatch)
export default class Settings extends Component {
state = { name: this.props.settings.name };
render() {
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}/>
<button onClick={e => this.props.changeName(this.state.name)}>
Change
</button>
</div>
);
}
}
small, but imho nice simplification
also, you could use concise syntax with your mapDispatch:
const mapDispatch = dispatch => ({
changeName(name) {
dispatch({ type: "setName", name });
}
});
you can do this if you want to to add the typing text in store:
Settings.js
import React from "react";
import { changeName, typingName } from '../actions/settingsActions'
import { connect } from "react-redux";
const Settings = () => {
const { changeName, typingName, typedName, submittedName } = this.props
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={typedName}
onChange={e => typingName(e.target.value)}/>
<button onClick={changeName(submittedName)}>
Change
</button>
</div>
);
}
const mapState = state => ({
typedName: state.typedName,
submittedName: state.submittedName
});
const mapDispatchToProps = dispatch => ({
typingName: x => dispatch(typingName(x)),
changeName: x => dispatch(changeName(x))
})
export default connect(
mapState,
mapDispatch
)(Settings);
settingsActions.js
export const typingName = payload => ({
type: 'TYPING_NAME',
payload
});
export const changeName = payload => ({
type: 'CHANGE_NAME',
payload
});
settingsReducer.js
export const typingName = (state = [], action) => {
switch (action.type) {
case 'TYPING_NAME':
return [...state, action.payload];
default:
return state;
}
};
export const changeName = (state = '', action) => {
switch (action.type) {
case 'CHANGING_NAME':
return action.payload;
default:
return state;
}
};
You could maybe achieve something like this. But validating the typing state inside the component then sending the final result to the store as you did is a better idea I think, to avoid so much verbose.
Also you should of course create a constants file, but I guess you know already.
|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
};
...
I'm very new to React.js and Redux.
I'm trying to build a very simple shopping cart application.
What I want is if you hit on an item (eg :- banana) It should appear in the cart.
(It should change the state of the cartReducer.js)
But instead of pushing the item to the reducer state it pushes something else.
What is the reason for this error?
This is my code.
cartReducer
import {ADD_TO_CART} from '../actions/index'
const initialState =[
]
export default (state = initialState,action)=>{
console.log("ACTION PAYLOAD",action.payload)
switch(action.type){
case ADD_TO_CART:
return[...state,action.payload]
default:
return state
}
}
Item component
import React, { Component } from "react";
import { connect } from "react-redux";
import {addToCart} from '../../actions/index'
export class Etem extends Component {
showItems = () => {
const { items, addToCartAction } = this.props;
console.log("ITEMS", items);
return items.map(items => <div key={items.id} onClick={addToCartAction}>{items.name}</div>);
};
render() {
return (
<div>
<h1>Items</h1>
<div>{this.showItems()}</div>
</div>
);
}
}
// export default items;
const mapStateToProps = reduxState => ({
items: reduxState.items
});
const mapDispatchToProps = dispatch => ({
addToCartAction: item => dispatch(addToCart(item))
});
export default connect(mapStateToProps, mapDispatchToProps)(Etem);
Action
export const ADD_TO_CART = 'ADD_TO_CART';
export const addToCart=(item) =>{
console.log("ITEMMMMMMMMMM",item)
return(
{
type:ADD_TO_CART,
payload:item,
}
)
}
<div key={items.id} onClick={addToCartAction}> this will pass the click event to addToCartAction instead of item.
Try this:
return items.map(item => (
<div key={item.id} onClick={() => addToCartAction(item)}>
{item.name}
</div>
));
Can you change the mapStateToProps
const mapStateToProps = reduxState => ({
items: reduxState.cartReducer.items
})