How to update <img/> on prop change - reactjs

Hi I am retrieving data from a Logitech Media server, title, artist, song etc and would like to know how to update the image returned by this server when the title of the song changes.
I have created a component that happily receives, displays, and updates all the relevant audio subcontrols , but I cannot get the song jpg to update on song title name change. The src of the image is set in state( and is the url of logitech media server needed to refreash the jpg)
The song title is happily updated when sent to the audioToolBar component
The title is defined in the switch statement and I thought by updating placing the songtitle in state and updating it when it changes would work, but whatever i try i get an error stating max depth of calls to setstate exceeded.
The Abbrievated (non functioning)code of component listed below
I am a newbie with react and js so any help greatly appreciated.
import React, { Component } from "react";
import { withStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import SwitchedComponent from "./switchedComponent";
import AudioToolBar from "./audioToolBar";
class AudioLightandHeat extends Component {
state = {imgSrc :"http://192.168.1.1:9000/music/current/cover.jpg?player=Bathroom"};
getControls = ()=>{
for (let index = 0; index < this.audioControls.length; index++) {
const element = this.audioControls[index];
switch (element.name) {
case "Title":
this.titleCont = element
let songAndArtist = (this.titleCont.statesValue.textAndIcon).split("/")
this.song = songAndArtist[0]
this.setState({songTitle:this.song})
break;
case "Volume":
this.volumeCont = element;
break;
case "Mode":
this.modeCont = element;
break;
case "Track":
this.trackCont = element;
break;
case "Play Pause":
this.playCont = element;
break;
}
}
}
}
}
}
}
}
render() {
const { classes, theme } = this.props;
this.getControls()
return (
<>
{this.hasAudioControl ? (
<img src = {this.state.imgSrc} width={300} height={300} />
) : null}
</>
);
}
}
export default withStyles(styles, { withTheme: true })(AudioLightandHeat);
ok so I have refactored the entire component to look like this
import React, { Component } from "react"
import { withStyles } from "#material-ui/core/styles"
import Grid from "#material-ui/core/Grid"
import AudioToolBar from "./audioToolBar"
const styles = theme => ({
grid: {
flexGrow: 1,
},
});
class AudioLightandHeat extends Component {
render() {
const { classes, theme } = this.props;
return (
<>
{console.log(" in render")}
{this.props.componentProps ? (
<Grid container className={classes.grid}>
<img src = "http://192.168.1.1:9000/music/current/cover.jpg?player=Bathroom" width={300} height={300} />
</Grid>
) :null}
{this.props.componentProps ? (
<AudioToolBar title = {this.props.componentProps.audioLightHeatControls.titleCont}
volume={this.props.componentProps.audioLightHeatControls.volumeCont}
mode={this.props.componentProps.audioLightHeatControls.modeCont}
track={this.props.componentProps.audioLightHeatControls.trackCont}
playpause={this.props.componentProps.audioLightHeatControls.playCont}
reqStateChange={this.props.componentProps.reqStateChange}
/>
) :null}
</>
)
}
}
export default withStyles(styles, { withTheme: true })(AudioLightandHeat);
The title , wolume, mode, track all update correctly when the props are updated, but the img does. I suspect this is because the src url does not change and hence the browser does not ask for it again despite there being a new image on the server at the same addresss.
How can i force React to call for the image when in render.
I have tried adding date.now to end of the url i.e src = "http://192.168.1.1:9000/music/current/cover.jpg?player=Bathroom" + new Date().getTime() but then get a 404 error from the server, I also added the following to index.html
<meta http-equiv="cache-control" content = "max-age=0">
<meta http-equiv="cache-control" content = "no-cache">
<meta http-equiv="pragma" content = "node_modules">
but this does not work either.
Any help greatly appreciated.

More General Advice about your react component
All the state of your react components should be in the component state. (this.song, this.titleCont etc. are currently not part of the component state, but assigned to the instance of the component)
This this.setState({ songTitle: this.song }) seems better, but it is usually not a good idea to have calls to setState in a function which is called in the render method. (It is very easy to produce infinite loops like this...)
The specific reason why it is only rendered once
NOTE: I am guessing here. You should really refactor your component first. Also you should probably read into the react component life cycle a bit, before you continue. Maybe this can help you.
Currently your render method will be called once during the initial render. During this initial render this.getControls() will be called once, but will probably not trigger the "Title" case.
After this initial render the props and state don't seem to change and therefore the component wont rerender. Therefore the render method and this.getControls() are not called again and the setState call wont be done.

Related

Only first element in a react.createElement is rendered

I need to render dynamicaly an img. Here is my function that translate my logos to components :
import {ReactComponent as MyLogo1} from '../assets/img/logo1.svg'
import {ReactComponent as MyLogo2} from '../assets/img/logo2.svg'
import {ReactComponent as MyLogo3} from '../assets/img/logo3.svg'
export const logoToComponent: any = {
logo_1: MyLogo1,
logo_2: MyLogo2,
logo_3: MyLogo3
};
and then I render it like this :
[...]
const logo: any = (key: string, props: any) => {
return logoToComponent[key] ? React.createElement(logoToComponent[key], props) : null;
};
return (
[...]
{logo(`logo_${category.code}`, { className: "mx-auto mb-3 w-6 h-6" })}
[...]
)
The problem is, only the first logo is rendered. In my DOM, when I inspect, every logos are there but only the first one is visible. And in my SVGs, the fill is set for each logos.
Any idea why React only render the first element ?
PS: If I delete the first line in my logoToComponent function, the second is visible and not the following ones.
I post the answer in case it can help.
Every SVG had the same id "clip-path". I just set a unique id in each SVG, and it works fine.

Need to re-render component after state is set to true

In my react app, I have a page that allows the user to "add" exercises to a personal list. The option to add an exercise is included in one "page" and the ability to see added exercises is view in a other. Side by side showing the two pages, notice 'Your Exercises' is empty. What I am trying to do is display these selected exercises. The exercises themselves are loaded via mapping function, from an array of objects. each object has a "selected" field and are set as "false" by default. My "add" button in the exercises page changes the state value of each component to "true", so now what I want to do is get these "true" values and render them on the page where it should show your exercises.
//Render your exercises
import React from "react";
import ExListItem from "./ExListItem";
// import selectedExercises from "../../../selectedExercises";
import exercises from "../../../exercises";
const selectedExerciseList = exercises
.filter(function (item) {
return item.selected === true;
})
.map(function ({name, reps}) {
return {name, reps};
});
// MAP HERE
function createSelectedExercises(exercises) {
return <ExListItem name={exercises.name} reps={exercises.reps} />;
}
function ExerciseList() {
return (
<ul data-tag="channelList" id="exercise-list" class="list-group mb-3">
{selectedExerciseList.map(createSelectedExercises)}
</ul>
);
}
export default ExerciseList;
Shouldnt this map the exercises?
You may need to use React state to accomplish this! You can do so with the following:
import React, { useState, useEffect } from 'react'
const Component = () => {
const [exercises, setExercises] = useState([])
useEffect(() => {
setExercises(exerciseFilterFunction(exercises)) // this will cause the re-render
}, [])
return (
<div>{exercises.map(e => <div>{e.name}</div>)}</div>
)
}

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

How can I make a native app that will pick and design charts according to the name fetched from API?

I want my TabDashboardDetails.js to find out which chart to be displayed according to the name of the chart fetched from API. In TabDashboardDetails.js I want to replace CogniAreaChart with a component that will have specific view for fetched chart and can also take data from API.
Here is my TabDashboardDetails.js
import React from 'react';
import DefaultScrollView from '../components/default/DefaultScrollView';
import ChartView from '../components/default/ChartView';
import CogniAreaChart from '../components/CogniAreaChart';
import { mapNameToChart } from '../utils/commonFunctions';
import { areaChartData } from '../chartData';
const TabDashboardDetail = ({ navigation, route }) => {
const tabsConfig = route.params.tabsConfig;
const ChartToDispay = mapNameToChart();
return (
<DefaultScrollView>
{tabsConfig.components.map((comp) => {
console.log(tabsConfig.components);
return (
<ChartView key={comp.name} title={comp.name}>
<CogniAreaChart
name={comp.name}
areaChartData={areaChartData}
height={200}
/>
</ChartView>
);
})}
</DefaultScrollView>
);
};
export default TabDashboardDetail;
I want to pick charts from commonfunctions.js that I have used:
/* eslint-disable prettier/prettier */
import {
AreaChart,
BarChart,
LineChart,
PieChart,
SingleCircularProgress,
Histogram,
SimpleTable,
BubbleChart,
CandlestickChart,
SankeyChart,
ScatterPlot,
StackedBarChart,
WaterfallChart,
TreeMap,
MixAndMatch,
SimpleCard,
BlogTable,
LiquidTable,
} from 'react-native-svg-charts';
export const mapNameToChart = (name) => {
const nameToChart = {
AreaChart: AreaChart,
BarGraph: BarChart,
LineChart: LineChart,
PieChart: PieChart,
SingleCircularProgress: SingleCircularProgress,
Histogram: Histogram,
SimpleTable: SimpleTable,
BubbleChart: BubbleChart,
CandlestickChart: CandlestickChart,
SankeyChart: SankeyChart,
ScatterPlot: ScatterPlot,
StackedBarGraph: StackedBarChart,
WaterfallTable: WaterfallChart,
TreeMap: TreeMap,
MixAndMatch: MixAndMatch,
SimpleCard: SimpleCard,
BlogCard: BlogTable,
LiquidGauge: LiquidTable,
};
return nameToChart[name];
};
You first need to import all the chart types in the file containing mapNameToChart and map the name to the Chart type accordingly. Then You can try this
const ChartToDispay = mapNameToChart(name);
<ChartToDisplay {...your_props_here} />
In your mapNameToChart function it looks like AreaChart is an actual component and the rest are just string names of components instead of the components themselves. You want to change it so that all of the entries in the map are the components. You want mapNameToChart(name) to return a callable component. Then you can call that component with your props.
I'm not fully understanding your the API comes into play here, but it seems like we get the props by looking up the name? I don't know the the API data comes from, so I'm expecting the components array to be passed as a prop to the CustomChart.
const CustomChart = ({name, components, ...props}) => {
// get the component function/class from your map
const Component = mapNameToChart(chart);
// find the component configuration from your API
const config = components.find(obj => obj.name === name);
// call with props from the config and passed down props
return (
<Component
{...config}
{...props}
/>
)
}

React execute script if window width

I have a button in React that executes a function onClick. I want to get rid of the button, and instead programmatically execute the function if window width < 1000px.
A restriction is that I can not add a plugin.
Here's what the code looks like...
// Do I need useState, useEffect?
import React, { PureComponent } from "react";
class MainNav extends PureComponent {
state = {
// Does something go here? What goes here and how do I use
// state to execute the function?
navIsCollapsed: false,
};
// this controls rendering of huge images
toggleShowImages() {
this.setState({
navIsCollapsed: !this.state.navIsCollapsed,
});
}
// I want this to be executed by width < 1000
handleSideNavToggle = () => {
this.toggleShowImages(); // controls if React renders components
document.body.classList.toggle("side-nav-closed");
}
Here's render the button that's currently executing the function. I want width < 1000 to programmatically execute its function.
// window width < 1000 should execute this function
<div onClick={this.handleSideNavToggle}>Don't render huge images</div>
// here are the images the function conditionally renders
<should show images &&
<div>Massive huge image</div>
<div>Massive huge image</div>
<div>Massive huge image</div>
>
I could use CSS media query to show or hide the massive images I don't want, but that's horrible use of React.
I've looked and tried to implement similar questions on SO that either invoke plugins, are out of date, or the use case is too different (for example, "re-render everything based on screen size"). I've also tried to React-ify vanilla javascript. This seems like it ought to be simple to do but I can't make it work.
Any React wizards out there who can answer with a clean, efficient solution?
Use the above method that Mathis Delaunay mentioned to get viewport/window width, then to get rid of that button. Just simply add a condition to whether render it or not and then watch on state changes to trigger the function.
Here I use hooks to do it
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [width]);
useEffect(() => {
width < 600 && handleSideNavToggle();
},[width]);
function handleSideNavToggle() {
console.log("toggle it");
}
return (
<div className="App">
{width > 600 && (
<button onClick={() => handleSideNavToggle()}>
Don't render huge images
</button>
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is a working example. I set the width to be handled as 600 to make it easy to see.
https://codesandbox.io/s/react-hooks-counter-demo-w9wgv
Try looking at this answer, i think it is what your are searching for :
Get viewport/window height in ReactJS
You just need to check in the updateWindowDimension if the window.innerWidth is under 1000, if so, change the css button property to display : none; or visibility: hidden;.

Resources