I am facing an issue where a component Question is not correctly receiving data from my react-redux store. When the component is initially rendered, it receives the desired data from the store with no issue. The problem arises when the question becomes a sub-question - when this is the case, the data from the store is not retrieved and is instead undefined, despite it being the same code. The sub-question code can be seen below where it renders a new Question component within the same Question component. This recursively rendered component does not receive the correct state.
The initial render code snippet is as follows, placed in an outside component from Question:
<div style={{ paddingRight: '8px' }}>
{data.Question.map(question => (
<Question question={question} key={question.ID} />
))}
</div>
The Question component is as follows:
import React from 'react';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import FlexibleInput from '../Inputs/FlexibleInput';
import {checkConditionals} from '../../dataHelper';
import { connect } from 'react-redux';
const mapStateToProps = (state, ownProps) => {
return {
answers: state.answers,
activeStep: state.stepper
}
}
const mapDispatchToProps = { }
class Question extends React.Component {
getStyle = () => {
if (this.props.subQuestion) return {paddingLeft: '24px', paddingRight: '-12px'}
return {}
}
render() {
const question = this.props.question;
console.log(this.props.activeStep, question.ID, this.props.answers);
return (
<React.Fragment>
{checkConditionals(question, this.props.answers) ? (
<div style={this.getStyle()}>
{/*Grid is used to placed the question and the possible answers on the same line*/}
<Grid container spacing={2}>
<Grid item> {/*Grid item for the question's prompt*/}
<Typography style={{padding: '12px', fontSize: '1rem'}}>
{question.Description}
</Typography>
</Grid>
<Grid item> {/*Grid item for the question's answer options*/}
<FlexibleInput obj={question}/>
</Grid>
</Grid>
{/*Display a question's sub-questions if they exist, mapping each sub question in the array to a new section*/
question.SubQuestion ? (
question.SubQuestion.map(subQuestion => (
<Question question={subQuestion} key={subQuestion.ID} subQuestion={true}/>
))
) : (
<React.Fragment></React.Fragment>
) }
</div>
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Question);
Question, on its own, does not have access to the store. By using connect you create a new component, and that component has access to the store. You do use connect at the end of the file, and you export the connected component, so as far as the rest of your codebase is concerned everything is fine. But inside this particular file, any references to <Question> are referring to the unconnected component.
Perhaps do this:
class Question extends React.Component {
// ... later, in render
question.SubQuestion.map(subQuestion => (
<ConnectedQuestion question={subQuestion} key={subQuestion.ID} subQuestion={true}/>
))
}
const ConnectedQuestion = connect(
mapStateToProps,
mapDispatchToProps
)(Question);
export default ConnectedQuestion;
Related
I am trying to figure out how to filter out a mapped array and making the rest of the results disappear in the same component. I've done the same with React Router as I can route the result to a different page but I am wondering if there is a way to do the same on the same component? I have a Directory component (below) that is mapping through an array to display results of items on the page.
I would like to click on one of the elements and remove the rest. I tried to incorporate a filter method in the same component but drawing blanks on how I should implement it. Let me know what you think!
import React from 'react'
import { Card, CardImg} from 'reactstrap'
function Presentational({example, onClick}){
return(
<Card onClick={()=> onClick(example.name) }>
<CardImg src={example.image}/>
</Card>
)
}
function Directory(props){
const examples = props.propExample.map(example=>{
return (
<div>
<Presentational example={example} onClick={props.onClick} />
</div>
)
})
return(
<div>
{examples}
</div>
)
}
export default Directory;
You may use useState hook for selection
We store clicked elements inside the state variable selected. using useState hook.
When the user clicks on the element react component will remember which element he clicked and will render an array from 1 clicked element [selected].
In order to cleanup selection, just call setSelected()
It is the same logic as you want.
import React, {useState} from 'react'
import { Card, CardImg} from 'reactstrap'
function Presentational({example, onClick}){
return(
<Card onClick={()=> onClick(example.name) }>
<CardImg src={example.image}/>
</Card>
)
}
function Directory(props){
const [selected, setSelected] = useState()
const examples = (selected ? [selected] : props.propExample).map(example=>{
return (
<div>
<Presentational example={example} onClick={(name) => {
props.onClick(name)
setSelected(example)
}}
/>
</div>
)
})
return(
<div>
{examples}
</div>
)
}
export default Directory;
if you want to do it with a filter clause it will look almost the same, but with the extra operations
import React, {useState} from 'react'
import { Card, CardImg} from 'reactstrap'
function Presentational({example, onClick}){
return(
<Card onClick={()=> onClick(example.name) }>
<CardImg src={example.image}/>
</Card>
)
}
function Directory(props){
const [selected, setSelected] = useState()
const examples = props.propExample.filter(it => typeof selected === 'undefined' || it.name === selected).map(example=>{
return (
<div>
<Presentational example={example} onClick={(name) => {
props.onClick(name)
setSelected(name)
}}
/>
</div>
)
})
return(
<div>
{examples}
</div>
)
}
export default Directory;
I am currently working on a project with React Hooks.
Parent component is a Navigator
Child component is a breadcrumb display in this navigator.
Child component fetches and displays a view with the data.
How can i use the response data in the 2. child component to set name in the 1. Child component?
My Code (omitted large portions of unnecessary code for this example):
Navigator
const { Header, Content } = Layout;
const Navigation = (props: any) => (
<>
<Layout>
<Layout>
<Header>
<Breadcrumbs
style={{ flexGrow: 2, paddingLeft: 20 }}
name='Name of User'
/>
</Header>
<Content style={{ margin: '24px 16px 0', overflow: 'hidden' }}>
<div className="content">
<Switch>
<Route exact path="/" component={MyPatients} />
<Route exact path="/Skjema" component={MySchemas} />
<Route
exact
path="/Pasient"
component={() =>
<PatientInfo
patientID={props.history.location.state}
/>
}
/>
export default withRouter(Navigation);
BreadCrumbs
import React from 'react';
import Breadcrumb from 'antd/lib/breadcrumb';
import { HomeOutlined, UserOutlined } from '#ant-design/icons';
const Breadcrumbs = (props: any) => {
return (
<>
<div className="Breadcrumbcontainer" style={props.style}>
<Breadcrumb>
<Breadcrumb.Item href="/">
<HomeOutlined />
<span style={{ color: 'black' }}>Hjem</span>
</Breadcrumb.Item>
<Breadcrumb.Item href="Pasient">
<UserOutlined />
<span style={{ color: 'black' }}>
{props.name}
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Skjema 1 - 17.04.20</span>
</Breadcrumb.Item>
</Breadcrumb>
</div>
</>
);
};
export default Breadcrumbs;
The third file contains a fetch to an api and works fine, the data in question is currently stored as response.name How can i lift this info up to Navigator?
If I understood your question correctly, there's a parent component that has two child components and you want to trigger a change from one child component in another.
You can maintain the state in the parent component, pass state in child1 and setState function in child2.
// Parent Component
const [name, setName] = useState('');
<>
<child1 name={name}/>
<child2 setName={setName}/>
</>
Try this one. I added code sandbox and you can check it out is this what you need. So from parent pass hook as props to a child, and then after the request is made inside of the child component call function from props that will fill the data inside parent component.
Update state in parent from child
I found a solution thanks to the suggestions in this thread. I made my mistake in the passing of the files to the function.
const PatientInfo = ({ patientID, setName }: any) => {
console.log(useFetch<IPatient>( // name of endpoint.... ));
const { response } = useFetch<IPatient>(
'// name of endpoint.... + patientID,
patientID,
);
This ended up fixing my problem. The problem was i initially called the two seperately, like this:
const PatientInfo = ({ patientID }: any, { setName } : any) => {
console.log(useFetch<IPatient>( // name of endpoint.... ));
const { response } = useFetch<IPatient>(
'// name of endpoint.... + patientID,
patientID,
);
So this change worked, although I am not entirely sure as to why.
Update (new changes):
So now I pretty much converted my code to a functional component however it is as if nothing is being returned from the API, or perhaps I am not "mounting" correctly? I get the error " TypeError: Cannot read property 'map' of undefined" meaning nothing is being returned but I'm not sure why. Help?
Old post (I had previously tried to use a hook in a a class component):
I'm pretty brand new to react and recently I have been getting this error from when trying to navigate to a page on click of a card media (using material ui). So I pretty much follow the instruction to push to history the route to the page I want to navigate to via a function and call that function when I click the media card. Below is my code and the error I get. Do you have any idea why I might be having an issue with this?
My updated code
import Card from '#material-ui/core/Card';
import CardMedia from '#material-ui/core/CardMedia';
import Grid from '#material-ui/core/Grid';
import Container from '#material-ui/core/Container';
import {getItems} from "../Network/network_utility";
import {useHistory} from "react-router-dom";
import {makeStyles} from '#material-ui/core/styles';
import React, {useState, useEffect} from "react";
const useStyles = makeStyles(theme => ({
icon: {
marginRight: theme.spacing(2),
},
heroContent: {
padding: theme.spacing(8, 0, 6),
},
cardGrid: {
paddingTop: theme.spacing(6),
paddingBottom: theme.spacing(3),
position: "fixed"
}
}));
export default function Items() {
let history = useHistory();
const classes = useStyles();
const useFeaturedItems = () => {
const [featured_items, setFeaturedItems] = useState([]);
useEffect(() => {
getItems(1).then(response => setFeaturedItems(response["data"]))}, []);
return featured_items;
};
return (
<div>
<Container className={classes.cardGrid} maxWidth="lg">
<Grid container spacing={6}>
{useFeaturedItems().map((card, index) => (
<Grid item key={index} xs={16} sm={4} md={2}>
<Card raised={false} style={{height: "30vh", width: "20vh"}}>
<CardMedia
component="img"
src={card["thumbnail"]}
style={{height: "25vh", width: "20vh"}}
onClick={history.push("/item")}
>
</CardMedia>
<div style={{paddingLeft: "10px", paddingTop: "10px"}}>
<text style={{
whiteSpace: "nowrap",
overflow: "hidden",
display: "block",
textOverflow: "ellipsis"
}}>
{card["title"]}
</text>
</div>
</Card>
</Grid>
))}
</Grid>
</Container>
</div>
);
}
As I can see you are using hook inside a class component. It is not possibile
how use hook
You can’t use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.
In class component you need to use the HOC withRouter. After that you can access the history through props with this.props.history
Suppose I'm making a gallery-like app where you can view a list of images in a long strip. This seems simple enough but I'm encountering a problem where these images are loaded at the same time and (my guess) is that these images are behind some sort of DDoS protection service which prevents me from loading them all at the same time (It gave me 403 errors).
So my solution would be to load them one by one sequentially, however I'm not sure how this could be done in React as I'm quite new to React/React Native in general and communications between sibling components and/or child-parent aren't very straight forward. I've read up on Redux and could this be the solution? - But I've still not got the clear picture on how this can be done either.
For example this is what I have which isn't working
class Gallery extends React.Component {
...
renderPage(page) { //Each Images
return (<Page image={page.item} />)
}
...
render () {
return (
<Container>
<Header />
<Content>
{ this.state.isLoading ? <Spinner /> : (
// this loads all images at the same time which causes error
<FlatList
initialNumToRender={2}
data={this.state.pageArray}
renderItem={this.renderPage}
keyExtractor={(item, index) => index}
/>
)}
</Content>
</Container>
);
}
}
export default Gallery;
I have tried this on my own, Maybe it will help you a bit.
import React, {Component} from 'react';
import {Text, FlatList} from 'react-native';
class Gallery extends Component {
state = {
pageArr: [{name: 'abc'}, {name: 'def'}, {name: 'ghi'}],
isLoading: false,
};
renderPage(page) {
return <Text>{page.name}</Text>;
}
render() {
const {isLoading, pageArr} = this.state;
return isLoading ? (
<Spinner />
) : (
<FlatList
data={pageArr}
renderItem={({item}) => this.renderPage(item)}
keyExtractor={(item, index) => index}
/>
);
}
}
export default Gallery;
I am trying to filter a table in a react component by a value inside a document in firebase.
The important code looks like this. Claims is being parsed into another component for the table rows.
class Claims extends Component {
componentDidMount() {
this.props.getClaims();
}
render() {
const { Claims, loading } = this.props.data;
let recentClaimsMarkup = !loading ? (
Claims.map(Claim => <ClaimsTable Claim={Claim} />)
) : (
<p>Loading...</p>
);
return (
<Grid>
<Paper className = {mergeClasses.root}>
<Typography> {this.props.match.params.Project} Claim's</Typography>{" "}
</Paper>
<Paper className={mergeClasses.root}>
<Table className={mergeClasses.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Claim ID</TableCell>
<TableCell align="right">Date received</TableCell>
<TableCell align="right">Progress</TableCell>
<TableCell>Edit Claim</TableCell>
</TableRow>
</TableHead>
<TableBody>{recentClaimsMarkup}</TableBody>
</Table>
</Paper>
I am wanting to filter it by the url Project, shown below
{this.props.match.params.Project}
This is due to the claims being tied to the project name.
If this was in Python I would just use a for loop or something similar, but a little bit confused how to filter what Claims are able to be mapped.
Basically want it to be, for claims with Project == {this.props.match.params.Project}: map claims.
Appreciate any help :)
Thanks
So basically filter also returns a new copy of array and you can read more here, but the problem is you can't create the jsx element, if you want to know more
you can read this question
so in your case you need to first filter and then you need to render the jsx element using map like shown below
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import "./styles.css";
const User = props => {
const claimsUserHasAccess = props.data.claims.filter(
claim => claim.project === props.match.params.access
);
return (
<>
{claimsUserHasAccess.map(claim => (
<p key={claim.name}>{claim.name}</p>
))}
</>
);
};
const userData = {
claims: [
{ project: "enabled", name: "job" },
{ project: "enabled", name: "nick" },
{ project: "disabled", name: "jef" }
]
};
function App() {
return (
<div className="App">
<BrowserRouter>
<Route
path="/user/:access"
render={props => <User data={userData} {...props} />}
/>
</BrowserRouter>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
in the url you need to enter enabled or disabled to see the users. I hope this will give a better view of your problem.
working codesandbox