React - variable not showing in page correctly - javascript

I have a variable attackHero whose initial value is Ironman. Its showing correctly on the page. On button click I am changing it to Hulk via Redux dispatch. But the change is not reflecting in the page. Am I missing something here?
Please see the code below.
import React, { Component } from 'react';
import { createStore } from 'redux';
class ReduxDemo extends Component {
constructor(props) {
super(props);
this.initNukeAttack = this.initNukeAttack.bind(this);
this.store = null;
}
initNukeAttack() {
console.log("I am inside initNukeAttack");
this.store.dispatch({type:"GREEN-ATTACK", hero: "Hulk"});
}
render() {
let attackHero = "attack hero";
// step 2 -> reducer => require state and action
const reducer = function(state, action) {
if(action.type === "ATTACK") {
return action.hero;
}
if(action.type === "GREEN-ATTACK") {
return action.hero;
}
return {p:"peace"};
}
//Step 1
this.store = createStore(reducer, "PEACE");
//step 3 -> subscribe
this.store.subscribe(() => {
//console.log("Store is now", store.getState());
//console.log(store.getState());
attackHero = this.store.getState();
})
//step 4 -> dispatch action
this.store.dispatch({type:"ATTACK", hero: "Ironman"});
//this.store.dispatch({type:"GREEN-ATTACK", hero: "Hulk"});
return(
<div>
<div>{attackHero}</div>
<button onClick={this.initNukeAttack}>Initiate Green Attack</button>
</div>
);
}
}
export default ReduxDemo;
The rendered screen look like this.

Hi first i would recommend to properly follow react with redux with middleware for action cretors. There are abundant resources available.
Anyways you were dispatching an action in render which is wrong. Second, for updating the the variable you need to setState to re-render your component.
Here is your working code :
class ReduxDemo extends Component {
constructor(props) {
super(props);
this.initNukeAttack = this.initNukeAttack.bind(this);
this.store = null;
this.state = {
attackHero: "IronMan"
};
}
initNukeAttack() {
console.log("I am inside initNukeAttack");
this.store.dispatch({ type: "GREEN-ATTACK", hero: "Hulk" });
}
render() {
// step 2 -> reducer => require state and action
const reducer = function(state = "ironman", action) {
if (action.type === "ATTACK") {
return action.hero;
}
if (action.type === "GREEN-ATTACK") {
return action.hero;
}
return state;
};
//Step 1
this.store = createStore(reducer, "PEACE");
//step 3 -> subscribe
this.store.subscribe(() => {
//console.log("Store is now", store.getState())
const attackHero = this.store.getState();
this.setState({
attackHero
})
});
//step 4 -> dispatch action
//this.store.dispatch({ type: "ATTACK", hero: "Ironman" });
// this.store.dispatch({type:"GREEN-ATTACK", hero: "Hulk"});
return (
<div>
<div>{this.state.attackHero}</div>
<button onClick={this.initNukeAttack}>Initiate Green Attack</button>
</div>
);
}
}

But the change is not reflecting in the page
this is because React is not re-rendering the page.
To re-render you need to use state and set the state variable via setState method.
or
you need to force render the page like this.forceUpdate(); after you set the state variable

Related

React component not updating when redux state changes

I have a React component that maps state to props to get data via redux. Everything works fine with the action and the value being updated properly in the reducer. My only problem is that when the state value changes, I want my component to re render so that it is always displaying the most up to date value in the reducer. As of right now I have to call a separate function that refreshes the component, but I'd rather have it automatically re render every time that value changes in the reducer.
Action:
export const createPickup = (selected, pickups) => dispatch => {
let icon;
icon = check(selected);
pickups.icon = icon;
return API('/createPickUp/', {
...pickups,
})
.then(res => {
dispatch({type: types.CREATE_PICKUP, res});
})
.catch(err => console.log(err));
};
Reducer:
const initialState = {
pick: [],
};
export default function pickup(state = initialState, action) {
switch (action.type) {
case types.GET_PICK:
return {
pick: action.pickup,
};
case types.CREATE_PICKUP:
return {
pick: [action.res, ...state.pick],
};
case types.DEL_GAME:
return {
pick: state.pick.filter(p => p._id !== action.id),
};
case types.START_GAME:
return {
pick: state.pick.map(p =>
p._id === action.id ? {...p, start: true} : p,
),
};
case types.STOP_GAME:
return {
pick: state.pick.map(p =>
p._id === action.id ? {...p, stop: true} : p,
),
};
default:
return state;
}
}
Use useSelector hook in Functional Component as it automatically subscribes to the state and your component will re-render.
If you are using Class Component then use connect() from redux and mapStateinProps.
I am assuming you have passed the reducer to the global Store.
Now... make sure you have the up to date value in your component.. try consoling it like this...
import {useSelector} from 'react-redux';
const YourCmponent = () => {
const reduxState = useSelector(state => state);
console.log(reduxState);
return <div>Your Content</div>
}
That way you can get access to the redux store. And you don't need to make any other function for updating component You will always get updated value here.

React Component not updating even after duplication of state in Redux reducer

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>
);
}

React component does not re-render when updating state from Context

I am trying to rerender a component in React.
The setup:
I am using React Context to fetch some data from a Firestore database. So the fetching is Async.
My component is then fetching the data using: static contextType = MyContext and accessing via this.context
I store this context data on the components state to try to trigger a rerender whenever this data is changed.
I pass the data to a child component where it renders a list based on this data.
The problem:
I manage to update the state and even when debugging I can see my state updating to the correct data BUT the component does not rerender either the childcomponent or the list.
The expected list shows as soon as I click anything on the page so my guess is that the data is trapped in some kind of middle stage.
What I've tried:
I tried using the componentDidUpdate to make a check if the context is different than the current state and trigger a function that sets the state (I have even tried with setState function directly after the check) => Still state updates but no rerender is triggered (I can see the new data on state)
I tried using the getDerivedStateFromProps on the child component to do a check if the Props have changed and also tried storing the props in the child components own state => Still same result as before.
I am not sure what else to try, I thought that React triggers a rerender everytime state chages but probably I am doing something really wrong.
Here is my parent:
import React, { Component } from 'react';
import styles from './artistdropdown.module.css';
import { returnCollection } from '../../utils/Firebase.js';
import MyContext from '../../utils/MyContext.js';
import ArtistSelected from './ArtistSelected.js';
import ArtistsList from './ArtistsList';
export class ArtistDropdown extends Component {
static contextType = MyContext;
constructor(props) {
super(props);
this.state = {
artists: [],
currentArtist: {
id: null,
name: null
}
};
this.fetchArtist = (aId, artists) => {
const state = {
id: null,
name: null,
};
artists && artists.forEach((a) => {
if (a.id === aId) {
state = {
...state,
id: a.id,
name: a.name,
}
}
})
return state;
}
this.loadToState = (state) => {
this.setState({
...this.state,
...state,
})
}
this.click = (id) => {
this.context.handleArtistSelection(id);
this.props.handleBandDropdown();
}
}
componentDidMount() {
const aId = this.context.state.user.last_artist;
const artists = this.context.state.user.artists;
const currentArtist = this.fetchArtist(aId, artists);
const state = {
artists: artists,
currentArtist: currentArtist,
}
this.loadToState(state);
}
componentDidUpdate(props, state) {
if (this.state.artists !== this.context.state.user.artists) {
const aId = this.context.state.user.last_artist;
const artists = this.context.state.user.artists;
const currentArtist = this.fetchArtist(aId, artists);
const state = {
artists: artists,
currentArtist: currentArtist,
}
this.loadToState(state);
}
}
render() {
const bandDropdown = this.props.bandDropdown;
return (
<>
<ArtistSelected
currentBand={this.state.currentArtist.name}
handleDropdown={this.props.handleBandDropdown}
expanded={bandDropdown}
/>
<ul className={bandDropdown ? styles.band_options + ' ' + styles.expanded : styles.band_options}>
<ArtistsList artists={this.state.artists} />
</ul>
</>
)
}
}
export default ArtistDropdown
and here is my child:
import React, { Component } from 'react';
import MyContext from '../../utils/MyContext.js';
import ArtistItem from './ArtistItem.js';
export class ArtistsList extends Component {
static contextType = MyContext;
constructor(props) {
super(props);
this.state = {
artists: [],
};
this.loadToState = (state) => {
this.setState({
...this.state,
...state,
}, () => { console.log(this.state) })
}
}
componentDidMount() {
const artists = this.props.artists;
const state = {
artists: artists,
}
this.loadToState(state);
}
componentDidUpdate(props, state) {
if (state.artists !== this.state.artists) {
this.loadToState(state);
}
}
static getDerivedStateFromProps(props, state) {
if (props.artists !== state.artists) {
return {
artists: props.artists,
}
} else {
return null;
}
}
render() {
// const artistList = this.state.artists;
const artistList = this.state.artists;
const list = artistList && artistList.map((a) => {
return (<ArtistItem key={a.id} onClick={() => this.click(a.id)} name={a.name} />)
})
return (
<>
{list}
</>
)
}
}
export default ArtistsList

Why am I getting the error data.findIndex is not a function in my React with Redux project?

I have an ASP.NET Core project with React and Redux, I'm also using the Kendo React UI. I'm trying to return data to one of my Kendo widgets but I'm getting an error when I try to do so and I need help identifying what I've done wrong.
When I run my application I get the error of:
1 of 2 errors on the page TypeError: data.findIndex is not a function
DropDownList/_this.renderDropDownWrapper
C:/Users/Allan/node_modules/#progress/kendo-react-dropdowns/dist/es/DropDownList/DropDownList.js:83
80 | var focused = _this.state.focused; 81 | var opened =
_this.props.opened !== undefined ? _this.props.opened : _this.state.opened; 82 | var value = _this.value;
83 | var selectedIndex = data.findIndex(function (i) { return areSame(i, value, dataItemKey); }); 84 | var text =
getItemValue(value, textField); 85 | var valueDefaultRendering =
(React.createElement("span", { className: "k-input" }, text)); 86 |
var valueElement = valueRender !== undefined ?
In the console this error shows as:
Warning: Failed prop type: Invalid prop data of type string
supplied to DropDownList, expected array.
The error makes sense, but the data I'm returning should be an array. It's not though as it doesn't appear to return anything. So I've done something wrong.
Here is my code so far, please note that my data is served from a generic repository.
components/vessels/WidgetData.js
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { actionCreators } from '../../store/Types';
import { DropDownList } from '#progress/kendo-react-dropdowns';
class WidgetData extends Component {
state = {
vesseltypes: ""
};
componentWillMount() {
this.props.requestTypes();
}
render() {
return (
<div>
<DropDownList data={this.state.vesseltypes} />
</div>
);
}
}
export default connect(
state => state.vesseltypes,
dispatch => bindActionCreators(actionCreators, dispatch)
)(WidgetData);
components/store/Types.js
const requestVesselTypes = 'REQUEST_TYPES';
const receiveVesselTypes = 'RECEIVE_TYPES';
const initialState = {
vesseltypes: [],
isLoading: false
};
export const actionCreators = {
requestTypes: () => async (dispatch) => {
dispatch({ type: requestVesselTypes });
const url = 'api/KendoData/GetVesselTypes';
const response = await fetch(url);
const alltypes = await response.json();
dispatch({ type: receiveVesselTypes, alltypes });
}
}
export const reducer = (state, action) => {
state = state || initialState;
if (action.type === requestVesselTypes) {
return {
...state,
isLoading: true
};
}
if (action.type === receiveVesselTypes) {
alltypes = action.alltypes;
return {
...state,
vesseltypes: action.alltypes,
isLoading: false
}
}
return state;
};
And finally, the reducer is defined in the store
components/store/configureStore.js
const reducers = {
vesseltypes: Types.reducer
};
I've tested the API to ensure data is there and it works, I've logged said data to the console from Types.js in the store and I can see it's returned. I'm very much new to react with redux so I'm trying to find my way here and any help is appreciated.
You need to remove the following state definition, since you want to refer to the value in the redux store, not to a local value:
class WidgetData extends Component {
state = {
vesseltypes: ""
};
Then, in your code, you need to refer to the redux store value: this.props.vesseltypes:
class WidgetData extends Component {
state = {
vesseltypes: ""
};
componentWillMount() {
this.props.requestTypes();
}
render() {
return (
<div>
<DropDownList data={this.props.vesseltypes} />
</div>
);
}
}
And you need to change the connect definition:
export default connect(
vesseltypes => state.vesseltypes,
dispatch => bindActionCreators(actionCreators, dispatch)
)(WidgetData);

Update Redux InitialValues after AJAX call

I am building a form where in some instances form elements are injected from an AJAX call (Duplicate text input for example).
Everything is working great and updating my form however I can't seem to get any default values back into the initial form state in my redux store. Below is my custom reducer that keeps track of the form elements. Can I push my new values into the initial state again?
//Schema Reducer
case "UPDATE_SCHEMA_FULFILLED":{
let s = {...state.schema}
for (let key in action.payload){
if(s.hasOwnProperty(key)){
if(key == 'values'){
s[key] = {...s[key], ...action.payload[key]}
}else{
s[key] = [...s[key], ...action.payload[key]]
}
}
}
state = { ...state,
loaded: true,
schema: {...s},
}
break;
}
My form is adding the initial values on first load as per the docs:
CustomForm = connect(
state => ({
initialValues: state.schema.schema.values
}),
dispatch => ({
onSubmit: data => dispatch(saveForm(data))
})
)(CustomForm)
This is what is generating the action:
import React from 'react'
import { addSchema } from '../actions/schemaActions'
export default class VirtualButton extends React.Component {
constructor(){
super();
this.generateNewLayout = this.generateNewLayout.bind(this)
}
generateNewLayout(e){
e.preventDefault();
this.props.dispatch(addSchema(this.props.owner));
}
render(){
return <div className="cf__virtual-action"><a href="" onClick={this.generateNewLayout}>Create New</a></div>
}
}
This seems to be working but I'm not sure if it's performant? Adding the initialize values function to the actions via props:
//My button that dispatches the initial call:
this.props.dispatch(addSchema(this.props.owner, this.props.initialize, this.props.initialValues));
export function addSchema($id, initialize, initial){
return function(dispatch) {
axios.post(config.api+'forms/schema/virtual/'+$id)
.then((response) => {
dispatch({type: 'UPDATE_SCHEMA_FULFILLED', payload: response.data})
initialize({...initial, ...response.data.values});
})
.catch((error) => {
console.log(error);
})
}
}

Categories

Resources