I am trying to display my state (users) in my react/redux functional component:
const Dumb = ({ users }) => {
console.log('users', users)
return (
<div>
<ul>
{users.map(user => <li>user</li>)}
</ul>
</div>
)
}
const data = state => ({
users: state
})
connect(data, null)(Dumb)
Dumb is used in a container component. The users.map statement has an issue but I thought that the data was injected through the connect statement? the reducer has an initial state with 1 name in it:
const users = (state = ['Jack'], action) => {
switch (action.type) {
case 'RECEIVED_DATA':
return action.data
default:
return state
}
}
CodeSandbox
You aren't using the connected component while rendering and hence the props aren't available in the component
const ConnectedDumb = connect(
data,
null
)(Dumb);
class Container extends React.Component {
render() {
return (
<div>
<ConnectedDumb />
</div>
);
}
}
Working demo
Related
Context
The goal is to have a component with a key name being react-rendered in App.js when I press a specific key, registered in another component. The information is being passed thorugh a redux managed state.
The problem
It's simple :
I'm updating my state in my redux reducer but even when duplicating it (I can see it thanks to the redux dev tool that allows me to watch my prevState and my nextState being different)
And the question is as simple :
Why my App.js component won't re-render even after connecting to and
duplicating my state ?
I think I made sure that my state was duplicated with the spreading operation and my redux dev tool display me a good state update without having my prevState and nextState duplicated. I looked through a lot of posts and found only people that forgot to duplicate their state in their reducers, which I did not.
So what's the problem here ??
DevTool Sample
Code
Here is the code, quite simple. The interesting piece is playSound and playedKeys:
App.js :
import React from 'react'
import './App.css';
import { connect } from 'react-redux';
import KeyComponent from './Components/Key'
import SoundPlayer from './Components/Sounds'
const mapStateToProps = (state) => ({
...state.soundReducer
})
class App extends React.Component {
constructor(props) {
super(props);
}
render(){
return (
<div>
{console.log(this.props)}
{
this.props.playedKeys.map(key =>{
<KeyComponent keyCode={key}> </KeyComponent>
})
}
<SoundPlayer></SoundPlayer>
</div>
);
}
}
export default connect(mapStateToProps)(App);
Reducer
export default (state = {allSounds:{},playedKeys:[]}, action) => {
switch (action.type) {
case 'ADD_SOUND':
return reduce_addSound({...state},action)
case 'PLAY_SOUND':
return reduce_playSound({...state,playedKeys : [...state.playedKeys]},action)
default:
return state
}
}
function reduce_addSound (state,action){
let i = 0
state.allSounds[action.payload.key] = { players : new Array(5).fill('').map(()=>(new Audio())) , reader : new FileReader()}
//load audioFile in audio player
state.allSounds[action.payload.key].reader.onload = function(e) {
state.allSounds[action.payload.key].players.forEach(player =>{
player.setAttribute('src', e.target.result);
player.load();
player.id = 'test'+e.target.result+ i++
})
}
state.allSounds[action.payload.key].reader.readAsDataURL(action.payload.input.files[0]);
return state
}
function reduce_playSound(state,action){
state.playedKey = action.payload.key;
if(!state.playedKeys.includes(state.playedKey))
state.playedKeys.push(action.payload.key);
return state
}
Action
export const addSound = (key, input,player) => (dispatch,getState) => {
dispatch({
type: 'ADD_SOUND',
payload: {key : key, input : input}
})
}
export const playSound = (key) => (dispatch,getState) => {
dispatch({
type: 'PLAY_SOUND',
payload: {key : key}
})
}
The component registering the keypresses
import React from 'react'
import { connect } from 'react-redux';
import { playSound } from '../../Actions/soundActions';
const mapStateToProps = (state) => ({
...state.soundReducer
})
const mapDispatchToProps = dispatch => ({
playSound: (keyCode) => dispatch(playSound(keyCode))
})
class SoundPlayer extends React.Component {
constructor(props) {
super(props);
}
componentDidMount () {
this.playSoundComponent = this.playSoundComponent.bind(this)
document.body.addEventListener('keypress', this.playSoundComponent);
}
keyCodePlayingIndex = {};
playSoundComponent(key){
if(this.props.allSounds.hasOwnProperty(key.code)){
if(!this.keyCodePlayingIndex.hasOwnProperty(key.code))
this.keyCodePlayingIndex[key.code] = 0
this.props.allSounds[key.code].players[this.keyCodePlayingIndex[key.code]].play()
this.keyCodePlayingIndex[key.code] = this.keyCodePlayingIndex[key.code] + 1 >= this.props.allSounds[key.code].players.length ? 0 : this.keyCodePlayingIndex[key.code] + 1
console.log(this.keyCodePlayingIndex[key.code])
}
this.props.playSound(key.code);
}
render(){
return <div>
<h1 >Played : {this.props.playedKey}</h1>
{Object.keys(this.keyCodePlayingIndex).map(key =>{
return <p>{key} : {this.keyCodePlayingIndex[key]}</p>
})}
</div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SoundPlayer);
Issue
You are mutating your state object.
state.allSounds[action.payload.key] = ...
state.playedKey = action.payload.key;
Solution
Update your reducer functions to return new state objects, remembering to correctly shallow copy each level of depth that is being updated.
export default (state = { allSounds: {}, playedKeys: [] }, action) => {
switch (action.type) {
case 'ADD_SOUND':
return reduce_addSound({ ...state },action);
case 'PLAY_SOUND':
return reduce_playSound({ ...state, playedKeys: [...state.playedKeys] }, action);
default:
return state
}
}
function reduce_addSound (state, action) {
const newState = {
...state, // shallow copy existing state
allSounds: {
...state.allSounds, // shallow copy existing allSounds
[action.payload.key]: {
players: new Array(5).fill('').map(()=>(new Audio())),
reader: new FileReader(),
},
}
};
// load audioFile in audio player
newState.allSounds[action.payload.key].reader.onload = function(e) {
newState.allSounds[action.payload.key].players.forEach((player, i) => {
player.setAttribute('src', e.target.result);
player.load();
player.id = 'test' + e.target.result + i // <-- use index from forEach loop
})
}
newState.allSounds[action.payload.key]
.reader
.readAsDataURL(action.payload.input.files[0]);
return newState;
}
function reduce_playSound (state, action) {
const newState = {
...state,
playedKey: action.payload.key,
};
if(!newState.playedKeys.includes(newState.playedKey))
newState.playedKeys = [...newState.playedKeys, action.payload.key];
return newState
}
Okay I've got it, it's always the simplest stupidest thing that we don't check huh.
Clarification
So my state was properly duplicated with reduce_addSound({ ...state },action) and reduce_playSound({ ...state, playedKeys: [...state.playedKeys] and like I wrote in my question, that wasn't the issue !
Issue
As old as it can get, I wasn't returning a component in my render function.. :
in App.js :
render(){
return (
<div>
{
this.props.soundReducer.playedKeys.map(key =>{
<KeyComponent keyCode={key}> </KeyComponent> //<-- NO return or parenthesis !!
})
}
<SoundPlayer></SoundPlayer>
</div>
);
}
Answer
App.js render function with parenthesis:
render(){
return (
<div>
{
this.props.soundReducer.playedKeys.map(key =>(
<KeyComponent key = {key} keyCode={key}> </KeyComponent> //<-- Here a component is returned..
))
}
<SoundPlayer></SoundPlayer>
</div>
);
}
I have two react components namely Dashboard and singleFeature.In the dashboard I have an ionRangleSlider which takes value from redux state. And I'm rendering the singleFeature component inside the Dashboard. SingleFeature component creates a fetch network request and updates the redux state using the dispatch method.
And the ionRangeSlider which resides inside the Dashboard takes value from the redux state which gets updated by singleFeature component. But regardless of whatever I have tried it's not reflecting the ionRangeSlider. However I can see the redux state is getting updated but not reflecting in any of the component.
Codes:
Dashboard.js
<div id="slider"><IonRangeSlider ref={r => this.ionSlider = r}
skin={this.state.skin} values={this.state.values} /></div>
<SingleFeature name={this.state.name} id={this.state.id} user={this.state.user} />
componentDidUpdate(prevProps,prevState) {
if (this.props.dates !== prevProps.dates) {
console.log(`In update`)
this.ionSlider.update({ values: this.props.dates })
}
}
Which then goes to singleFeature and runs a function and updated the redux state.
singleFeature.js
fetch(`http://api/dates`)
.then(data => data.json())
.then(res => {
for(let i in res){
let dates = res[i]["dates"];
}
this.props.updateState(dates)
})
componentDidUpdate(prevProps,prevState) {
if (this.props.dates !== prevProps.dates) {
console.log(`In update`)
console.log(this.props.dates)
}
}
And both components are connected by { connect } by react-redux which runs these methods.
//fetch from redux store
const FetchFromStore = (state) => {
return {
dates: state.dates
}
}
//update redux store functions
const UpdateStore = (dispatch) => {
return {
updateState: (dates) => dispatch(
{
type: 'UPDATE_DATES',
payload: dates
})
}
}
And the reducer file,
const stateActions = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_DATES':
state.dates = [...state.dates,...action.payload];
console.log(state.dates) //which is updating
return state;
}
return state;
}
None of the componentdidUpdate method working after state update.
Thanks for all the comments. It appears to be I'm updating the state in a wrong way.
//redux payload actions
const stateActions = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_DATES':
// state.dates = [...state.dates,...action.payload];
let dates_arr = [...state.dates];
state.dates = [...dates_arr,...action.payload]
return {...state}
}
return state;
}
Which is working fine and updating all the components.
It is my first react-redux project (list of courses) and I have some troubles with redux. I need to do the search by course name, I based my code on this answer I can see the action in redux-devtool, but not on ui. Please help me to understand what I am doing wrong
//action
export function search (value) {
return {type: SEARCH, value};
}
//reducer
import { COURSES } from '../../constants';
const initialState = COURSES;
export default function reducer(state = initialState, action) {
switch(action.type) {
case SEARCH: {
const {value} = action;
const course = state.filter((val) => val.includes(value));
return {...state, value, course};
}
default:
return state;
}
}
//search component
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {deleteCourse, search} from '../../redux/actions';
class SearchInput extends Component {
render() {
const {search, value} = this.props;
return (
<div className={classes.SearchInput}>
<input
placeholder='Search'
onChange={(e) => search(e.target.value)}
value={value}
/>
</div>
)
}
}
export default connect (state => ({
courses: state.courses,
}), {search})(SearchInput);
//Courses Component
import Form from '../Form/Form';
import { deleteCourse, search } from '../../redux/actions';
const Courses = ({ courses, deleteCourse }) => {
console.log(courses)
return (
<div className={classes.Courses}>
<SearchInput />
<Button title='Add course' />
<CourseList
courses={courses}
deleteCourse={deleteCourse}
search={search}
/>
<Form />
</div>
)
}
export default connect (state => ({
courses: state.courses,
}), {deleteCourse})(Courses);
image-from-redux-devtool
There's a few things that may be causing it
export function search (value) {
return {type: SEARCH, value};
}
Unless you didn't add your entire action code above (which judging by the dev tool photo may be the case) you're passing an undefined variable as type
import { COURSES } from '../../constants';
const initialState = COURSES;
export default function reducer(state = initialState, action) {
switch(action.type) {
case SEARCH: {
const {value} = action;
const course = state.filter((val) => val.includes(value));
return {...state, value, course};
}
default:
return state;
}
}
In your reducer it looks like your initial state is an array (since you're able to filter it directly), but then you iterate that array as properties of a return object.
const initialState = {courses: [], value: '', matches: []}
const matches = state.courses.filter((val) => val.includes(value));
return {...state, matches: matches};
Your initial state structure shouldn't be restructured on a return; instead it should look like the above, with matches filtered from state.courses. Storing search value in state also doesn't appear to be of any use, so it can be removed.
Overall, I don't think it's necessary to do search through redux as the parent component can manage its own rendering. You just need to create a state property to filter courses passed in by redux connect.
See working example:
https://codesandbox.io/s/zen-boyd-9cgfo?file=/src/App.js
My react component is not re-rendering despite its props being updated and I don't understand why.
Here's my component
import { fetchLocations } from 'state/locations/actions';
class Event extends React.Component {
componentDidMount() {
this.props.fetchLocations();
}
render() {
const { locations } = this.props;
return <span>{locations.map((l) => {return <span>{l}</span>;})}</span>;
}
}
const mapStateToProps = (state) => ({
locations: state.locations
})
export default connect(
mapStateToProps,
{ fetchLocations },
)(Event);
Here is my locations action file
export const fetchLocations = () = (dispatch) => {
axios.get('/api/locations')
.then(response => {
const locations = response.data;
dispatch({ type: FETCH_LOCATIONS_SUCCESS, payload: locations });
});
}
And my entities reducer
function entities(state = { locations: {} }, action) {
switch (action.type) {
case FETCH_LOCATIONS_SUCCESS:
return Object.assign({}, state, {
locations: action.payload
})
default:
return state
}
}
After this, my Event component should re-render. It doesn't. Using the react dev tools chrome extension I see that the locations are indeed there as props, but they do not show on the UI.
If I unmount the component by going to a different page and re-mount it, the locations show up properly.
It looks like everything works fine except the re-render is not triggering. componentDidUpdate is never fired.
If I manually do a setTimeout to forceUpdate an arbitrary second later, they show up.
Why isn't my component re-rendering?
Please, try to add key prop to span element of the render method. locations.map((l,key)=> <span key={key} >{l} </span>
i am trying to create a toggle for favouriting a Card Component so that it renders in my Favorites.js component. I am using Redux to store the state but when i dispatch an action to add or remove them from the store the components are not rendering. I think i am mutating the state of the array.
Here's the reducer:
export function rootReducer(state = [], action) {
switch(action.type) {
case 'ADD_FAVORITE':
return state.concat(action.data);
case 'SUB_FAVORITE':
return state.filter(state => state.name !== action.data.name);
default:
return state;
}
}
I tried using Object.assign to a create a new Array but since the data passed into my state is in a array itself, i can't use store.getState() to map them into my component. The array becomes nested within itself.
This is the function that i am running onClick to dispatch the actions:
toggleFavorites(e) {
if
(store.getState().includes(this.props.data))
{
console.log(this.props.data.name + ' removed from favorites');
store.dispatch({type: 'SUB_FAVORITE', data: this.props.data});
}
else{
console.log(this.props.data.name + ' added to favorites');
store.dispatch({type: 'ADD_FAVORITE', data: this.props.data});
}
This.props.data is passed from referencing an array in a JSON object and mapping it into my Card Component
Here's the Card Component that i am rendering:
render() {
let {name, description, url , screenshot} = this.props.data;
return (
<div className="cardComponent">
<div className="CTA-container">
<div className="CTA-wrapper">
<div onClick={() => this.toggleFavorites(this.props.data)}className="CTA-icon"><IconComponent icon="favorite"/></div>
<IconComponent icon="link"/>
</div>
</div>
<img src={screenshot} alt="" className="cardComponent_img"/>
{name}
<p className="cardComponent_description">{description}</p>
</div>
I am rendering these Card Components into the Favorites.js Component like this:
class Favorites extends Component {
constructor(props) {
super(props);
}
render() {
let cardComps = store.getState().map(data => {
return (
<CardComponent data = {data} />
)
})
return (
<div>
<div className="array-component">{cardComps}</div>
export default Favorites;
I am fairly new to React and Redux so i am not sure if i did something wrong when setting up the components. I just need the component to re-render when the user adds or remove it from their Favourites.
Redux do shallow comparison of reference for updated state and based on that decide whether to update component or not.
Both Array#concat and Array#filter return new array with same referenced elements.So,state comparison return false and no rendering happening.
Assuming action.data is a one dimensional array.
This should work.
switch(action.type) {
case 'ADD_FAVORITE':
return [...state,action.data];
case 'SUB_FAVORITE':
return [...state.filter(state => state.name !== action.data.name)]
default:
return state;
}
You also need to use the connect method fro react-redux library to listen to the store updates. For reference I have included the code.
In Reducer
switch(action.type) {
case 'ADD_FAVORITE':
return [...state,action.data];
case 'SUB_FAVORITE':
return [...state.filter(state => state.name !== action.data.name)]
default:
return state;
}
In Favorites.js Components
import { connect } from 'react-redux';
class Favorites extends Component {
constructor(props) {
super(props);
this.state = {
storeData: []
}
}
componentWillReceiveProps(nextProps){
if(nextProps.storeData !== this.state.storeData){
this.setState({
storeData: nextProps.storeData
})
}
}
render() {
const { storeData } = this.state;
let cardComps = storeData.map(data => {
return <CardComponent data = {data} />;
})
return (
<div className="array-component">{cardComps}</div>;
);
}
}
const mapStateToProps = state => {
return {
storeData: state
};
};
const connectedFavorites = connect(mapStateToProps)(Favorites);
export default connectedFavorites;