useState to update an object property - javascript

I am new to React. I have a functional component that is used to render am image and some properties of an image passed as props to the component. I would like to update the image source when there is an error rendering the image. I would also like to update the state property of the parent component and pass it back to the parent. I am not sure how to achieve the same. I have been struggling for so long to achieve this. Please can someone help me solve this issue. Many thanks in advance.
Parent Component:
import React, {
useState
} from 'react';
import PropTypes from 'prop-types';
import ImageRenderer from './ImageRenderer';
import VideoRenderer from './VideoRenderer';
const getComponent = {
'image': ImageRenderer,
'video': VideoRenderer
}
const AssetRenderer = (props) => {
console.log('props in asset ren:', props);
const [assetInfo, setAssetInfo] = useState(props);
console.log('assetInfo in parent:', assetInfo);
const isPublished = assetInfo.assetInfo.isAssetPublished;
let source = assetInfo.assetInfo.assetUrl;
const PreviewComponent = getComponent[assetInfo.assetInfo.type];
return ( < div > {
isPublished && source && < PreviewComponent assetInfo = {assetInfo} setAssetInfo = { setAssetInfo } />} </div>
);
}
AssetRenderer.propTypes = {
assetInfo: PropTypes.object.isRequired
};
export default AssetRenderer;
Child Component:
import React from 'react';
import PropTypes from 'prop-types';
import {
Subheading
} from '#contentful/forma-36-react-components';
const ImageRenderer = props => {
console.log('inside image renderer', props);
return ( <
div id = "asset-img" >
<
Subheading > Image preview: < /Subheading> <
p > Name: {
props.assetInfo.assetInfo.name
} < /p> <
p > Type: {
props.assetInfo.assetInfo.type
} < /p> <
p > Url: {
props.assetInfo.assetUrl
} < /p> <
img src = {
props.assetInfo.assetInfo.assetUrl
}
alt = "name"
onError = {
e => {
props.setAssetInfo(assetInfo => {
return { ...props.assetInfo.assetInfo,
assetUrl: 'https://example.com/404-placeholder.jpg',
isAssetPublished: false
} //would like to update the asset url to 404 and also set isAssetPublished to false and pass it back to parent to update parent state
});
}
}
/> <
/div>
)
}
ImageRenderer.propTypes = {
assetInfo: PropTypes.object.isRequired
};
export default ImageRenderer;

Instead of using new state in ImageRenderer component, you can just pass setState of Parent via props like this;
parent component
import React, { useStae } from 'react'
const parentCompoennt = props => {
const [assetInfo,setAssetInfo] = useState();
return (
<ImageRenderer assetInfo={assetInfo} setAssetInfo={setAssetInfo} />
);
}
imageRenderer component
const ImageRenderer = props => {
return(
<div id="asset-img">
<p> Name: {props.assetInfo.assetInfo.name} </p>
<p> Type: {props.assetInfo.assetInfo.type} </p>
<p> Url: {props.assetInfo.assetInfo.assetUrl} </p>
<img src={props.assetInfo.assetInfo.assetUrl} alt="name" onError={e => {
props.setAssetInfo(assetInfo => {
return { ...props.assetInfo, assetUrl: 'https://example.com/404-placeholder.jpg' } //would like to update the asset url to 404 and also set isAssetPublished to false and pass it back to parent to update parent state
});
}}/>
</div>
)
}

If the purpose is to handle image error only, then you can achieve it without re-rendering a component:
<img src={assetInfo.assetInfo.assetUrl} alt="name"
onError={e => {
e.target.src = 'https://example.com/404-placeholder.jpg';
}}
/>

Related

Bind value of property to value from another component

I am currently working on a learning project to get myself familiar with React. The app is a quiz-app and the end goal is for the user to get a sport recommendation based on the answers he gives. There are 8 questions, each one of them is representative of an attribute. On the app, below each question there is a slider which the user can change between 1 and 5 as the answer to the question.
I would like to have something similar to an athlete's profile below the questions and that would be a bar-chart, with each column being an attribute and the value of the column being equal to the answer.
This is my component that displays the question with the slider :
import React, { Component } from 'react';
import { Slider } from '#material-ui/core';
import './FirstComponent.css';
import { IoIosArrowBack } from "react-icons/io";
import { IoIosArrowForward } from "react-icons/io";
export default class FirstComponent extends Component {
constructor(props) {
super(props);
this.state = {
textValue: "Select your answer",
answerValue: 3,
};
this.maxValue = 5;
this.minValue = 1;
}
changeTextBasedOnSliderValue = (value) => {
let intValue = parseInt(value);
const answersArray = this.props.answers;
this.setState({
textValue: answersArray[intValue - 1],
answerValue: value,
})
}
updateSliderValue = (increase) => {
if (increase && this.state.answerValue < this.maxValue) {
this.setState(prevState => ({
answerValue: prevState.answerValue + 1,
textValue: this.props.answers[this.state.answerValue] // an
}))
}
else if (!increase && this.state.answerValue > this.minValue) {
this.setState(prevState => ({
answerValue: prevState.answerValue - 1,
textValue: this.props.answers[this.state.answerValue - 2]
}))
}
}
render() {
return (
<div className="sliderQuestion">
<h2 className="questionText">{this.props.index}. {this.props.question}</h2>
<h4 className="answer">{this.state.answerValue}</h4>
<p className="answer">{this.state.textValue}</p>
<IoIosArrowBack onClick={(e) => this.updateSliderValue(false)} className="left-icon" />
<IoIosArrowForward onClick={(e) => this.updateSliderValue(true)} className="right-icon" />
<Slider
defaultValue={3}
min={this.minValue}
max={this.maxValue}
value={this.state.answerValue}
valueLabelDisplay="off"
onChange={(e, value) => this.changeTextBasedOnSliderValue(value)}
/>
</div>
)
}
}
And this is my App,js code
import React, { Component } from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css'
import 'react-bootstrap-range-slider/dist/react-bootstrap-range-slider.css';
import FirstComponent from './FirstComponent';
import Button from 'react-bootstrap/Button';
import BarChart from 'react-bar-chart';
class App extends Component {
constructor(props) {
super(props);
this.state = {
questions: [],
barChartData: [],
};
this.barChartData = []
}
async componentDidMount() {
const url = "http://localhost:9000/connection";
const response = await fetch(url);
const data = await response.json();
for (var i = 0; i < data.length; i++) {
let barChartColumn = {
text : data[i].Attribute,
value : 10,
}
this.barChartData.push(barChartColumn);
var item = <FirstComponent key={data[i]._id} index={i + 1} question={data[i].Question} answers={data[i].Answers} />
this.setState({
questions: [this.state.questions, item],
barChartData: this.barChartData,
})
};
}
render() {
return (
<div className="container">
<h2 className="header-title">Which sport are you made for?</h2>
{this.state.questions}
<Button className="results-button" variant="primary">Get my results</Button>
<BarChart ylabel=''
data={this.state.barChartData}
width={900}
height={500}
margin={{ top: 20, right: 70, bottom: 30, left: 70 }} />
</div>
);
}
}
export default App;
My question is how could I bind the value of a barChartColumn to the answerValue of a slider component and have it update when the value changes?
Don't waste your time. Even if you share values between component (classes) as you would like to, you'll be in problem when values in one of the components get changed. Other component won't be aware if the change.
Use state and props instead

Is there a way to fake a loop animation in React JS within the following context?

I am new to React and learning how to animate styles. I have created the following animation that moves the element down 50px when the page renders. The props from, to, and config are part of the react-spring library.
import React, { Component } from 'react';
import sphere from '../img/sphere.png';
import { Spring, config } from 'react-spring'
import '../Sphere.css';
class BioSphere extends Component {
state = { top: 0 }
render() {
const float = (num) => {
if(num == 0) {
return 50;
this.setState({
top: 50
})
} else {
return 0;
}
};
return(
<div style={this.props} className="sphere">
<Spring
from = {{top: '0px'}}
to = {{top: `${float(this.state.top)}px`}}
config = {config.slow} >
{props => (
<div style={props}>
<img style={props} className='img' src={sphere} alt={' '}/>
</div>
)}
</Spring>
</div>
);
}
}
export default BioSphere;
Try creating a function that runs a conditional.
const conditional = (b,n1,n2) => b?n1:n2;
conditional(somebool,'0px','50px')

Passing Style From Component State in ReactJS

I have the following component:
import React from "react";
import { connect } from "react-redux";
class Filter extends React.Component {
state = {
value: ''
};
handleChange = (e) => {
let value = e.target.value;
if(value){
document.getElementById("clear").style["display"] = "none";
document.getElementById("fetch").style["display"] = "none";
} else {
document.getElementById("clear").style["display"] = "inline-block";
document.getElementById("fetch").style["display"] = "inline-block";
}
this.setState({ value });
this.props.handleFilter({ value });
}
render(){
let content = this.props.items > 0 ? (
<div
className="filter"
>
<input
id="search-input"
type="text"
placeholder="Search..."
value={this.state.value}
onChange={this.handleChange}
/>
</div>
) : <div></div>
return content;
}
}
const mapStateToProps = (state,props) => ({
items: state.settings.length
});
module.exports = connect(mapStateToProps, null)(Filter);
Is there a way I can more gracefully pass props to the clear and fetch components? I'm trying to style them based on interactions with my search input (basically, I'd like to be able to style them whenever my search value is at ""). How do I send down styles as a prop based on the state of my current component?
Please react-jss that is exactly what you are searching for: please find the sample below:
import React, {
Component
} from 'react'
import injectSheet from 'react-jss'
import classNames from 'classnames'
class someComponent extends Component {
handleClose = () => {}
render() {
const {
classes,
} = this.props
return ( <
div className = {
classes.resize
} >
<
/div>
)
}
}
const styles = {
container: {
width: '100%',
height: '100rem'
borderBottom: '10px'
}
}
export default injectSheet(styles)(someComponent)
If you use a library called styled-components, you can easily do this like in this example: https://www.styled-components.com/docs/basics#passed-props. The idea with that lib is that you wrap your basic HTML components, like <input> to <Input>, then use the new <Input> component in your render() method. The new component will be controlled by passing values from your state as a prop.

React JS - How to access props in a child component which is passed from a parent component

In my react application, I am passing my data from parent to child as props. In my child component, I am able to see the data in props however when I try to access the data, I am getting an error saying "cannot read property of undefined".
I have written my child component like below-
Child Component-
import React from 'react';
import ReactDOM from 'react-dom';
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
const ChildAccordion = (props) => {
console.log(props);
return (
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3> Details:
{ props?
props.map(d =>{
return <span>{d.key}</span>
})
:
""
}
</h3>
<div>With a bit of description</div>
</AccordionItemTitle>
<AccordionItemBody>
<p>Body content</p>
</AccordionItemBody>
</AccordionItem>
</Accordion>
)
};
export default ChildAccordion
Parent Component-
import React from 'react';
import ReactDOM from 'react-dom';
import ChildAccordion from './ChildAccordion'
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
class ParentAccordion extends React.Component {
componentWillMount() {
//call to action
this.props.setData();
}
getMappedData = (dataProp) =>{
if (dataProp) {
let Data = this.props.dataProp.map(d =>{
console.log(d);
})
}
}
render(){
const { dataProp } = this.props;
return (
// RENDER THE COMPONENT
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3>Policy Owner Details:
{ dataProp?
dataProp.map(d =>{
return <span>{d.key1}</span>
})
:
""
}
</h3>
</AccordionItemTitle>
<AccordionItemBody>
<ChildAccordion {...dataProp} />
</AccordionItemBody>
</AccordionItem>
</Accordion>
);
}
}
const mapStateToProps = state => {
return {
dataProp: state.dataProp
}
};
const mapDispatchToProps = dispatch => ({
setData(data) {
dispatch(setData(data));
}
})
export default connect (mapStateToProps,mapDispatchToProps) (ParentAccordion)
I am using map function inside as my api response can be array of multiple objects.
Once you know what the prop that you're passing in is called, you can access it like so from within your child component: {props.data.map(item => <span>{item.something}</span>}
const Parent = () => {
return (
<Child data={[{ id: 1, name: 'Jim' }, { id: 2, name: 'Jane ' }]} />
);
}
const Child = (props) => {
return (
<ul>
{props.data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
You are passing dataProp down to ChilAccordian as a prop. So in Child component you should access it using props.dataProp and do map on props.dataProp but not on props directly
ChildAccordian:
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map(d =>{
return <span key={d.id}>{d.key}</span>
})
:
""
}
</h3>
Also keep in mind that you have to add unique key to parent Jsx element when you generate them in loop like for loop, .map, .forEach, Object.keys, OBject.entries, Object.values etc like I did in the above example. If you don’t get unique id from the data then consider adding index as unique like
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map((d, index) =>{
return <span key={"Key-"+index}>{d.key}</span>
})
:
""
}
</h3>
Edit: If it is an object then do something like below and regarding using a method to generate jsx elements
getMappedData = dataProp =>{
if(props.dataProp){
Object.keys(props.dataProp).map(key =>{
return <span key={"Key-"+key}>{props.dataProp[key]}</span>
});
}else{
return "";
}
}
<h3> Details:
{this.getMappedData(props.dataProp)}
</h3>

How to setState from child component in React

I would like to set state of parent component from child component. I tried using props however its giving error Uncaught TypeError: this.props.setTopicClicked is not a function. And is there more efficient way for setting state of parent component instead of using props? I would like to set state of isTopicClicked: true
main-controller.jsx
import {React, ReactDOM} from '../../../build/react';
import SelectedTopicPage from '../selected-topic-page.jsx';
import TopicsList from '../topic-list.jsx';
import topicPageData from '../../content/json/topic-page-data.js';
export default class MainController extends React.Component {
state = {
isTopicClicked: false,
topicPageData
};
onClick(topicID) {
this.setState({
isTopicClicked: true,
topicsID: topicID
});
};
setTopicClicked(event){
this.setState({isTopicClicked: event});
};
render() {
return (
<div className="row">
{this.state.isTopicClicked
? <SelectedTopicPage topicsID={this.state.topicsID} key={this.state.topicsID} topicPageData={topicPageData}/>
: <TopicsList onClick={ this.onClick.bind(this) }/>}
</div>
);
}
};
selected-topic-page.jsx
import {React, ReactDOM} from '../../build/react';
import SelectedTopicPageMarkup from './selected-topic-page-markup.jsx';
import NextPrevBtn from './next-prev-btn.jsx';
export default class SelectedTopicPage extends React.Component {
state = {
topicPageNo: 0,
total_selected_topic_pages: 1
};
navigateBack(topicPageNo) {
if (this.state.topicPageNo > 0){
topicPageNo = this.state.topicPageNo - 1;
}
else {
topicPageNo = 0;
}
this.setState({topicPageNo : topicPageNo});
};
navigateNext(totalPagesInSelectedTopic) {
let topicPageNo;
if (totalPagesInSelectedTopic > this.state.topicPageNo + 1){
topicPageNo = this.state.topicPageNo + 1;
}
else if (totalPagesInSelectedTopic == this.state.topicPageNo + 1) {
this.props.setTopicClicked(true);
}
else {
topicPageNo = this.state.topicPageNo;
}
this.setState({topicPageNo : topicPageNo});
};
render() {
let topicsID = this.props.topicsID;
let topicPageNo = this.state.topicPageNo;
return (
<div>
{this.props.topicPageData.filter(function(topicPage) {
// if condition is true, item is not filtered out
return topicPage.topic_no === topicsID;
}).map(function (topicPage) {
let totalPagesInSelectedTopic = topicPage.topic_pages.length;
return (
<div>
<div>
<SelectedTopicPageMarkup headline={topicPage.topic_pages[0].headline} key={topicPage.topic_no}>
{topicPage.topic_pages[topicPageNo].description}
</SelectedTopicPageMarkup>
</div>
<div>
<NextPrevBtn moveNext={this.navigateNext.bind(this, totalPagesInSelectedTopic)} key={topicPage.topic_no} moveBack={this.navigateBack.bind(this, topicPageNo)}/>
</div>
</div>
);
}.bind(this))}
</div>
);
};
};
It seems you forgot to pass setTopicClicked to the child:
setTopicClicked={this.setTopicClicked.bind(this)}
Your <SelectedTopicPage /> does not contain setTopicClicked as props which results into the error
<SelectedTopicPage
topicsID={this.state.topicsID}
key={this.state.topicsID}
topicPageData={topicPageData}/>
You can try using a flux implementation to handle the state of your application and just pass props to the component. Otherwise, I think you're stuck in passing in setting the state using the component or its children.

Categories

Resources