Map and render two different arrays in the same component - React - reactjs

I just started learning React and I'm having issues understanding how to map and and render two different arrays in the same component. In my CMS I have 3 entries containing a title and a description each. I am calling them from GraphQL with the var data. The component SectionLeft has a grid container with threeFeatures as className. In my homepage I am passing the content to the grid container with the prop bottomcontent. I am passing the component SingleFeature in array which is my grid column and will contain an icon, a title and a description, it will repeat 3 times and compose my 3 column x 1 row grid. The component SingleFeature has the prop children which should contain an array map of the constant iconList containing 3 different svg icon components. I can't understand how to write a multiple array map for the component SingleFeature which will display the same data array displayed below for the props feature and details, but will render the 3 components for children in array. Any explanation would be really appreciated. Thank you.
homepage.js
import IconOne from "../components/icons/icon-one"
import IconTwo from "../components/icons/icon-two"
import IconThree from "../components/icons/icon-three"
export const iconList = [IconOne, IconTwo, IconThree]
export default function Home({data}) {
return (
<div>
<SectionLeft bottomcontent =
{data.allContentfulHomepageWordpressFeatures.edges.map(edge => (
<SingleFeature
children = {
...iconList array...
}
feature = {
edge.node.feature
}
details = {
edge.node.description
}
/>
))
}
/>
);
}
section-left.js
export default function SectionLeft(props) {
return (
<div className="threeFeatures">{props.bottomcontent}</div>
);
}
single-feature.js
export default function SingleFeature(props) {
return(
<div>
{props.children}
<h3>{props.feature}</h3>
<p>{props.details}</p>
</div>
);
}
GraphQL query
export const query = graphql`
allContentfulHomepageSpeedFeatures {
edges {
node {
feature
description
}
}
}
}
`

I would do it this way:
data.allContentfulHomepageWordpressFeatures.edges.map((edge, index) => (
<SingleFeature
children = {
iconList[index]
}
feature = {
edge.node.feature
}
details = {
edge.node.description
}
/>
))
map passes the index of the element in the mapped array as a second parameter to your arrow function.

Related

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

Mapping a react component doesn't return to the parent component

I have a two components. One component Profileviewlist is a child of Profileview mapping components inside of Profileviewlist returns a child component outside of Profileview . I have been looking around on this site for this issue. I am looking for a way to get Profileviewlist's components to render inside of Profileview. Any insight helps.
Note: The components render , just not in the parent(profileview).
Profile view:
import React from 'react'
import Profileviewheader from './profileviewheader'
import Profileviewlist from './Profileviewlist'
export default function Profileview({ persons, display_selector }) {
if (persons == null || persons.status == "error") {
return (
<Profileviewheader />
)
}
else {
console.log("rendering list")
console.log()
return (
<>
<Profileviewheader />
<Profileviewlist persons={persons} display_selector={display_selector} />
</>
)
}
}
** Profile view list:**
import React from 'react'
import Profileviewchild from './profileviewchild';
export default function Profileviewlist({ persons, display_selector }) {
return (
persons.map(person => {
return <Profileviewchild person={person} change_selected={display_selector} />
})
)
}
Parental Hierarchy(the way I would like the components to be structured in the form of divs) Each component points to its child:
Profileview:->
Profileviewlist:->
Profileviewchild:
As I understood you have a list of personal profiles, you should make a ternary operator for checking if persons not null and have a length then map into them, else show an error.
Something like this should work:
export default function Profileview({ persons, display_selector }) {
return (
<div id="profileview">
<Profileviewheader />
{persons.length ? persons.map(person => (
<Profileviewchild person={person} change_selected={display_selector} />
)) :
<p>{persons.status}</p>}
</div>
)
}
Edit:
After checking the shared picture, the problem is you assign id="profileview" on the Profileviewheader. you should assign this component.
The issue is occurring by persons in Profileview and Profileviewlist
Here the type of persons seems to be Object.
So persons.map() will never work since map is the method of Array not Object. The ideal way is that persons have list key field.
{
status: 'success',
list: [...] // persons list
}
...
<Profileviewlist persons={persons.list} display_selector={display_selector} />
...

Side panel with components loading dynamically based on context

With react: how can I render a side panel component with different components depending on the type of action. To give a concrete example:
On the main page, I have a table with some rows. Every row may be of a different "type" (Row 1 may be of type A, Row 2 may be of type B.
When I click on a row that's type A, I would like to render components related to the type A row, and different components for type B row.
You can almost define the panel like a "widget" area, where I can load different components based on different context, or action I perform outside of that area.
Below is what I am trying to do.
This can be done by changing the data stored on state of the selected row type.
For example:
state = {
currently_selected_type : ""
}
In your render function, we can have something like this:
render(){
let SidePanelComponent = null;
if(this.state.currently_selected_type === "A"){
SidePanelComponent = (<div>A</div>);
}
else if(this.state.currently_selected_type === "B"){
SidePanelComponent = (<div>B</div>);
}
return (
<div>
<Table
onClick={
// this type will be passed from the inside Table when rows are clicked based on the row type
(type) => this.setState({currently_selected_type : type})
}
/>
{SidePanelComponent}
</div>
);
}
Inside the Table component you will be rendering the different types of rows, so based on what row is click just send the type to the passed onClick function.
The Table render function can be as follows, for example:
render(){
return (
...
<tr onClick={this.props.onClick("A")}>Row Type A</tr>
<tr onClick={this.props.onClick("B")}>Row Type B</tr>
...
);
}
Ok, I figured it out:
I created a context and a provider
import React, { useState } from 'react';
import PanelContext from './panelContext'
const PanelProvider = ({ children }) => {
const [content, setContent] = useState(undefined);
return (
<PanelContext.Provider
value={{
content: content,
setContent: setContent
}}>
{children}
</PanelContext.Provider>
);
};
export default PanelProvider;
Then I created a usePanel hook:
import PanelContext from "../contexts/panel/panelContext";
const usePanel = () => {
const { content, setContent } = useContext(PanelContext)
return {content, setContent};
}
export default usePanel;
Then in my Table Row component, I use it:
const { setContent } = usePanel();
...
...
const testCallback = (e) => console.log(e.target.value)
...
...
setContent(<Input value="wow!!!!" onChange={testCallback} />)
And finally, on the actual panel component itself:
import { usePanel } from "../../../hooks"
export default ({ }) => {
const { content } = usePanel();
return (
<aside className="contextualizer" id="contextualizer">
<PerfectScrollbar>
<div className="customizer-body">
{content}
</div>
</PerfectScrollbar>
</aside >
);
}
Works perfectly.

Component rendered 8 times

I don't understand clearly why Row component rendered 8 times. Should I use custom comparing function in React Memo for this component? I'm using the react-window package. Please explain how it works for me. Thanks a lot.
Parent component of ListView: CovidMap Component
My entire project: Github
This is my code:
import React, { useRef, memo, useEffect } from 'react';
import { FixedSizeList as FixedList, areEqual } from 'react-window';
import './ListView.scss';
const ListView = (props) => {
const listRef = useRef();
const Row = memo((props) => {
console.log('Row rendering...');
const { data, index, style } = props;
const className =
data.itemIndex === index
? 'PatienIndicator Highlight'
: 'PatientIndicator';
return (
<button
key={index}
className={className}
onClick={() => data.onClickPatient(data.patients[index])}
style={style}
>
{data.patients[index].name}
</button>
);
}, areEqual);
const patientsLength = props.patients
? Object.keys(props.patients).length
: 0;
const data = Object.assign(
{},
{ patients: props.patients },
{ onClickPatient: props.onClickPatient },
{ itemIndex: props.itemIndex }
);
console.log('List View rendering...');
useEffect(() => {
if (props.itemIndex) {
listRef.current.scrollToItem(props.itemIndex, 'smarter');
}
});
return (
<FixedList
className="List"
height={300}
itemCount={patientsLength}
itemSize={50}
width={'100%'}
ref={listRef}
itemData={data}
>
{Row}
</FixedList>
);
};
export default memo(ListView);
The itemCount prop tells FixedSizeList how many rows to render, so your list will render however many items are in your props.patients list, which is ultimately coming from the result of filtering the array returned by the fetch in the parent component. The index prop that you're using in the Row component is passed to the Row from FixedSizeList and refers to its position in the list, so it tells the first instance of the Row component that its index is 0, and Row uses that information to render the name of the first item in the data.patients prop. FixedSizeList passes its own itemData prop to each Row component as Row's data prop.
From the react-window documentation for the FixedSizeList component:
itemCount: number
Total number of items in the list. Note that only a few items will be rendered and displayed at a time.
and
children: component
React component responsible for rendering the individual item specified by an index prop
You're passing the Row component to FixedSizeList as a child, so it renders the Row component itemCount number of times.
React memo won't affect the number of instances of the component. All memo does is potentially improves performance of rendering each instance by reusing its last rendered version if the props haven't changed.

Resources