React rendering user list in child component - javascript

In my parent component (a search bar component) I have this code which passes an array down to the child component:
When form is submitted I make am axios request to my back-end database
async onFormSubmit(event) {
event.preventDefault();
const users = await axios.post("/api/fetchuser", {
persona: this.state.term
});
let userArray = this.state.userArray;
userArray = users.data.map(user => {
return user.steamInfo;
});
this.setState({ userArray: userArray });
}
Then I pass down the userArray to the child component:
renderResults() {
if (this.state.userArray.length > 0) {
return <UserSearchResult userArray={this.state.userArray} />;
} else {
return;
}
}
In my child component I have this, keep note of the console.logs as I will show the output afterwards.
class UserSearchResult extends Component {
async renderUsers() {
let userList = this.props.userArray;
console.log(userList);
userList = userList.map(user => {
return (
<Segment>
<Grid>
<Grid.Column width={3} style={resultStyle.results}>
<Image src={user.avatar} fluid />
</Grid.Column>
<Grid.Column width={9} />
<Grid.Column width={3} />
</Grid>
</Segment>
);
});
console.log(userList);
return userList;
}
render() {
return (
<div>
{console.log(this.renderUsers())}
</div>
);
}
}
Here is the output:
In the first two console.logs, it prints out exactly what I want, but once I return the userList back to the render function, it changes into promises?
My question is: Why is the userList changing to something else when it's returned to the render function. And how can I render each element in the array according to my jsx?

Try removing async. async makes your function return a promise which isn't necessary.
Also UserSearchResult doesn't need to be a class, just do this.
const UserSearchResult = (props) => {
const userList = this.props.userArray;
console.log(userList);
const UserDetail = ({user}) => {
return (
<Segment>
<Grid>
<Grid.Column width={3} style={resultStyle.results}>
<Image src={user.avatar} fluid />
</Grid.Column>
<Grid.Column width={9} />
<Grid.Column width={3} />
</Grid>
</Segment>
);
console.log(userDetail);
return (
<div>
{userList.map((user, index) => <UserDetail key={index} user={user} /> )}
</div>
);
}
Or you could just {userList.map((user, index) => <UserDetail key={index} user={user} /> )} in your original component and make UserDetail it's own component and get rid of the UserSearchResult.

You should remove the async portion. I believe that is what the root of your issue is. I refactored your code below:
class UserSearchResult extends Component {
render() {
const { userArray } = this.props
return (
<div>
{ userArray.map(user => {
return (
<Segment>
<Grid>
<Grid.Column width={3} style={resultStyle.results}>
<Image src={user.avatar} fluid />
</Grid.Column>
<Grid.Column width={9} />
<Grid.Column width={3} />
</Grid>
</Segment>
);
});
}
</div>
);
}
}

Related

React table rowProps prop doesnt log anything

[Basically, we're using this from one of our libraries. But the thing is while using it through our package the rowProps prop doesn't work]
(https://i.stack.imgur.com/fER2B.png).
import { AdvancedTable } from "ims-ui-kit";
function Table(props) {
console.log(props)
return <AdvancedTable {...props} />;
}
export default Table;
Here is another screenshot of the prop passing through the table we are using. It doesn't log anything.
[enter image description here]
<>
{alert}
<div className="content">
<ReactTable
// hasBulkActions={true}
data={data}
filterable
{...rest}
resizable={false}
columns={columns.slice()}
defaultPageSize={10}
showPaginationTop
showPaginationBottom={false}
className="-striped -highlight"
rowProps={(row) => {
onclick = () => {
console.log("hello", row);
};
}}
/>
<Modal title="Risk management">
<RiskDetail isModalOpen={isOpen} />
</Modal>
</div>
</>

list of ref shows undefined when logging

I've made a list of refs for each of my components that is being rendered in a map, I am assigning a ref to a button within EditWebAppTypeForm and am trying to use it within MappedAccordion but it shows undefined? what can I do to make sure ref is set before passing it in the MappedAccordion component?
The information logged in addtoRefs function is correct, it shows -
(2) [button, button]
I've removed a lot of the code so its easier to read.
function Admin() {
const allRefs = useRef([] as any);
allRefs.current = [];
const addtoRefs = (e: any) => {
if (e && !allRefs?.current?.includes(e)) {
allRefs?.current?.push(e);
}
console.log(allRefs.current); <-- Logs correct info
};
return (
<div className="adminContainer">
<Grid container spacing={2}>
<Grid item md={8} xs={12} sm={12}>
<div style={{ width: 725, paddingBottom: 150 }}>
{webAppTypes &&
webAppTypes.map((a: IWebAppType, index: number) => {
return (
<>
<Accordion
key={a.id}
defaultExpanded={a.id === 0 ? true : false}
>
<AccordionDetails>
<EditWebAppTypeForm
key={a.name}
setWebAppTypes={setWebAppTypes}
IWebAppTypeModel={a}
ref={addtoRefs} // <-- returning ref to add to list
/>
<MappedAccordion
waobj={a}
key={a.id}
setWebAppTypes={setWebAppTypes}
editRef={allRefs.current[index]} <-- using ref but showing undefined in MappedAccordion component
/>
</AccordionDetails>
</Accordion>
</>
);
})}
</div>
</Grid>
</Grid>
</div>
);
}
export default Admin;
EditWebAppTypeForm Component -
const EditWebAppTypeForm = (props: any, ref: any) => {
return (
<div className="editWebAppSContainer">
<form onSubmit={handleSubmit(onSubmit)} id="edit-app-form">
<button hidden={true} ref={ref} type="submit" /> // <-- Assiging ref to button
</form>
</div>
);
};
export default forwardRef(EditWebAppTypeForm);
type MappedAccordionProps = {
waobj: IWebAppType;
setWebAppTypes: Dispatch<SetStateAction<IWebAppType[]>>;
editRef: any;
};
function MappedAccordion({
waobj,
setWebAppTypes,
editRef,
}: MappedAccordionProps) {
const onSubmit = (data: FormFields) => {
console.log(editRef); // <-- Logs undefined
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} id="environment-form">
</form>
</div>
);
}
export default MappedAccordion;
I would create an extra component WebAppTypeAccordion like this :
function WebAppTypeAccordion({a, setWebAppTypes}) {
const [formEl, setFormEl] = useState(null);
function handleRef(el) {
if (el) {
setFormEl(el)
}
}
return (
<Accordion defaultExpanded={a.id === 0}>
<AccordionDetails>
<EditWebAppTypeForm
setWebAppTypes={setWebAppTypes}
IWebAppTypeModel={a}
ref={handleRef}
/>
<MappedAccordion
waobj={a}
setWebAppTypes={setWebAppTypes}
editRef={formEl}
/>
</AccordionDetails>
</Accordion>
);
}
Then you can update the Admin component :
webAppTypes.map((a: IWebAppType) => (
<WebAppTypeAccordion key={a.id] a={a} setWebAppTypes={setWebAppTypes} />
))

saga fetch data after component rendered

hi sorry for my bad english.i am using react and redux.i dispatch getTags action in layout component.problem is after getData action called,getDataSuccess action called after my components rendered.so my data is null.
how i can be sure that data is fetched and render my components?
layout:
function DashboardLayout({
children,
showSideBar,
backgroundColor,
getTagsFromServer,
getCategoriesFromServer,
}) {
getTagsFromServer();
getCategoriesFromServer();
return (
<StyledDashbordLayout>
<NavBar />
<SideBarDrawer />
<Grid container className="container">
{showSideBar && (
<Grid item className="sidebar-section">
<SideBar />
</Grid>
)}
<Grid item className="content">
{children}
</Grid>
</Grid>
</StyledDashbordLayout>
);
}
DashboardLayout.propTypes = {
children: PropTypes.node.isRequired,
showSideBar: PropTypes.bool.isRequired,
backgroundColor: PropTypes.string,
getTagsFromServer: PropTypes.func,
getCategoriesFromServer: PropTypes.func,
};
function mapDispatchToProps(dispatch) {
return {
dispatch,
getTagsFromServer: () => dispatch(getTags()),
getCategoriesFromServer: () => dispatch(getCategories()),
};
}
const withConnect = connect(
null,
mapDispatchToProps,
);
export default compose(withConnect)(DashboardLayout);
saga:
import { call, put, takeLatest } from 'redux-saga/effects';
function* uploadVideo({ file }) {
try {
const { data } = yield call(uploadVideoApi, { file });
yield put(uploadFileSuccess(data));
} catch (err) {
yield put(uploadFileFail(err));
}
}
function* getTags() {
const { data } = yield call(getTagsApi);
console.log(data, 'app saga');
yield put(getTagsSuccess(data));
}
function* getCategories() {
const { data } = yield call(getTCategoriesApi);
yield put(getCategoriesSuccess(data));
}
// Individual exports for testing
export default function* appSaga() {
yield takeLatest(UPLOAD_VIDEO, uploadVideo);
yield takeLatest(GET_TAGS, getTags);
yield takeLatest(GET_CATEGORIES, getCategories);
}
this is my select box component which gets null data from store:
import React, { useState } from 'react';
function UploadFileInfo({ tags, categories }) {
return (
<Paper square className={classes.paper}>
<Tabs
onChange={handleChange}
aria-label="disabled tabs example"
classes={{ indicator: classes.indicator, root: classes.root }}
value={tab}
>
<Tab
label="مشخصات ویدیو"
classes={{
selected: classes.selected,
}}
/>
<Tab
label="تنظیمات پیشرفته"
classes={{
selected: classes.selected,
}}
/>
</Tabs>
{tab === 0 && (
<Grid container className={classes.info}>
<Grid item xs={12} sm={6} className={classes.formControl}>
<label htmlFor="title" className={classes.label}>
عنوان ویدیو
</label>
<input
id="title"
type="text"
className={classes.input}
onChange={e => setValue('title', e.target.value)}
defaultValue={data.title}
/>
</Grid>
<Grid item xs={12} sm={6} className={classes.formControl}>
<SelectBox
onChange={e => setValue('category', e.target.value)}
value={data.category}
label="دسته بندی"
options={converItems(categories)}
/>
</Grid>
<Grid item xs={12} className={classes.textAreaWrapper}>
<label htmlFor="info" className={classes.label}>
توضیحات
</label>
<TextField
id="info"
multiline
rows={4}
defaultValue={data.info}
variant="outlined"
classes={{ root: classes.textArea }}
onChange={e => setValue('info', e.target.value)}
/>
</Grid>
<Grid item xs={12} sm={6} className={classes.formControl}>
<SelectBox
onChange={e => {
if (e.target.value.length > 5) {
console.log('hi');
setError(
'tags',
'تعداد تگ ها نمی تواند بیشتر از پنج عدد باشد',
);
return;
}
setValue('tags', e.target.value);
}}
value={data.tags}
label="تگ ها"
options={converItems(tags)}
multiple
onDelete={id => deleteTagHandler(id)}
error={errors.tags}
/>
</Grid>
</Grid>
)}
{tab === 1 && 'دومی'}
<Dump data={data} />
</Paper>
);
}
const mapStateToProps = createStructuredSelector({
tags: makeSelectTags(),
categories: makeSelectCategories(),
});
const withConnect = connect(
mapStateToProps,
null,
);
export default compose(withConnect)(UploadFileInfo);
Question Summary
If I understand your question correctly, you are asking how to guard the passing of options, options={converItems(tags)}, in the SelectBox in UploadFileInfo against null or undefined values when the data hasn't been fetch yet.
Solutions
There are a few options for either guarding against null or undefined values.
Easiest is to provide a default fallback value for tags. Here I am making an assumption the tags are an array, but they can be anything, so please adjust to match your needs.
Inline when passed options={converItems(tags || []) or options={converItems(tags ?? [])
In the function signature function UploadFileInfo({ tags = [], categories })
As part of a fallback return value in makeSelectTags
Another common pattern is conditional rendering where null may be the initial redux state value, so you simply wait until it is not null to render your UI.
Early return null if no tags
function UploadFileInfo({ tags, categories }) {
if (!tags) return null;
return (
<Paper square className={classes.paper}>
...
Conditional render SelectBox
{tags ? (
<SelectBox
...
/>
) : null
}
Side Note about fetching data calls in DashboardLayout
When you place function invocations directly in the function body they will be invoked any time react decides to "render" the component to do any DOM diffing, etc..., pretty much any time DashboardLayout renders the data fetches are made, which could have unintended effects. For this reason, react functional component bodies are supposed to be pure functions without side effects. Place any data fetching calls in an effect hook that is called only once when the component mounts (or appropriate dependency if it needs to be called under other specific conditions).
useEffect(() => {
getTagsFromServer();
getCategoriesFromServer();
}, []);
Use your functions to call the API inside React.useEffect.
All your API calls should be inside the useEffect hook.
For more on useEffect, read this
function DashboardLayout({
children,
showSideBar,
backgroundColor,
getTagsFromServer,
getCategoriesFromServer,
}) {
React.useEffect(() => {
getTagsFromServer();
getCategoriesFromServer();
}, []);
return (
<StyledDashbordLayout>
<NavBar />
<SideBarDrawer />
<Grid container className="container">
{showSideBar && (
<Grid item className="sidebar-section">
<SideBar />
</Grid>
)}
<Grid item className="content">
{children}
</Grid>
</Grid>
</StyledDashbordLayout>
);
}

pass multiple refs to child components

Before diving to the main problem, my use case is I am trying to handle the scroll to a desired section. I will have navigations on the left and list of form sections relative to those navigation on the right. The Navigation and Form Section are the child component. Here is how I have structured my code
Parent.js
const scrollToRef = ref => window.scrollTo(0, ref.current.offsetTop);
const Profile = () => {
const socialRef = React.useRef(null);
const smsRef = React.useRef(null);
const handleScroll = ref => {
console.log("scrollRef", ref);
scrollToRef(ref);
};
return (
<>
<Wrapper>
<Grid>
<Row>
<Col xs={12} md={3} sm={12}>
<Navigation
socialRef={socialRef}
smsRef={smsRef}
handleScroll={handleScroll}
/>
</Col>
<Col xs={12} md={9} sm={12}>
<Form
socialRef={socialRef}
smsRef={smsRef}
/>
</Col>
</Row>
</Grid>
</Wrapper>
</>
);
};
Navigation.js(child component)
I tried using forwardRef but seems like it only accepts one argument as ref though I have multiple refs.
const Navigation = React.forwardRef(({ handleScroll }, ref) => {
// it only accepts on ref argument
const items = [
{ id: 1, name: "Social connections", pointer: "social-connections", to: ref }, // socialRef
{ id: 2, name: "SMS preference", pointer: "sms", to: ref }, // smsRef
];
return (
<>
<Box>
<UL>
{items.map(item => {
return (
<LI
key={item.id}
active={item.active}
onClick={() => handleScroll(item.to)}
>
{item.name}
</LI>
);
})}
</UL>
</Box>
</>
);
});
export default Navigation;
Form.js
I do not have idea on passing multiple refs when using forwardRef so for form section I have passed the refs as simple props passing.
const Form = ({ socialRef, smsRef }) => {
return (
<>
<Formik initialValues={initialValues()}>
{({ handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<Social socialRef={socialRef} />
<SMS smsRef={smsRef} />
</form>
);
}}
</Formik>
</>
);
};
Social.js
const Social = ({ socialRef }) => {
return (
<>
<Row ref={socialRef}>
<Col xs={12} md={3}>
<Label>Social connections</Label>
</Col>
<Col xs={12} md={6}></Col>
</Row>
</>
);
};
Can anyone help me at passing multiple refs so when clicked on the particular navigation item, it should scroll me to its respective component(section).
I have added an example below. I have not tested this. This is just the idea.
import React, { createContext, useState, useContext, useRef, useEffect } from 'react'
export const RefContext = createContext({});
export const RefContextProvider = ({ children }) => {
const [refs, setRefs] = useState({});
return <RefContext.Provider value={{ refs, setRefs }}>
{children}
</RefContext.Provider>;
};
const Profile = ({ children }) => {
// ---------------- Here you can access refs set in the Navigation
const { refs } = useContext(RefContext);
console.log(refs.socialRef, refs.smsRef);
return <>
{children}
</>;
};
const Navigation = () => {
const socialRef = useRef(null);
const smsRef = useRef(null);
const { setRefs } = useContext(RefContext);
// --------------- Here you add the refs to context
useEffect(() => {
if (socialRef && smsRef) {
setRefs({ socialRef, smsRef });
}
}, [socialRef, smsRef, setRefs]);
return <>
<div ref={socialRef}></div>
<div ref={smsRef}></div>
</>
};
export const Example = () => {
return (
<RefContextProvider>
<Profile>
<Navigation />
</Profile>
</RefContextProvider>
);
};

How to filter the props for to render with ReactJS?

I have a code that get data of a json-server. And to render seven names in the screen. I want to filter by input and to render only the elements filtereds.
My Apps.js:
class AppRouter extends React.Component {
state = {
employeeCurrent: [],
employee: []
};
componentDidMount() {
axios
.get("http://127.0.0.1:3004/employee")
.
then(response => this.setState({ employee: response.data }));
}
add = name => {
this.setState(prevState => {
const copy = prevState.employeeCurrent.slice();
copy.push(name);
return {
employeeCurrent: copy
};
});
};
render() {
return (
<Router>
<div className="router">
<Route
exact
path="/"
render={props => (
<Home
{...props}
add={this.add}
employee={this.state.employee}
currentEmployee={this.state.currentEmployee}
/>
)}
/>
<Route
path="/user/:id"
component={props => (
<User
{...props}
employee={this.state.employee}
currentEmployee={this.state.currentEmployee}
/>
)}
/>
</div>
</Router>
);
}
}
My body.js (Where has the function for to render)
class Body extends React.Component {
getName = () => {
const { employee, add } = this.props;
return employee.map(name => (
<Link className="link" to={`/user/${name.name}`}>
{" "}
<div onClick={() => add(name)} key={name.id} className="item">
{" "}
<img
className="img"
src={`https://picsum.photos/${name.name}`}
/>{" "}
<h1 className="name"> {name.name} </h1>
</div>{" "}
</Link>
));
};
render() {
return <div className="body">{this.getName()}</div>;
}
}
I tried to pass the state to the App. JS but had no success. I tried everything in the Body. JS but also not succeeded. Could someone help me how to do this?
I'm on the phone so there are some things that are bad to indent. Sorry!
Try this,
class Body extends React.Component {
getName = () => {
const { employee, add } = this.props;
//Filter names in employee array with input
const filterNames = employee.filter(x => x.name === "check with the input value" );
// Then map over the filtered names
return filterNames.map(name => (
<Link className="link" to={`/user/${name.name}`}>
{" "}
<div onClick={() => add(name)} key={name.id} className="item">
{" "}
<img
className="img"
src={`https://picsum.photos/${name.name}`}
/>{" "}
<h1 className="name"> {name.name} </h1>
</div>{" "}
</Link>
));
};
render() {
return <div className="body">{this.getName()}</div>;
}
}

Categories

Resources