In my application, I want to conditionally render something, so I made a function getItem which I want to call in my custom Tooltip, const CustomTooltip.
Seen in the code below, I want to pass payload in const CustomTooltip = ({ active, payload, label} to function getState({payload}
I try to do this by {getState(payload, "A")} However, when I do so, I get this error:
Type 'Payload<ValueType, NameType>[]' has no properties in common with type 'TooltipProps<ValueType, NameType>'
Note: I am new to React
const numberStates = 3;
function getState({payload}: TooltipProps<ValueType, NameType>, state: string ){
if(payload){
for(let i = 0; i < numberStates; i++){
if(payload[i].dataKey == state){
return <p>{ payload[i] } : { payload[i].value }</p>
}
}
}
return null;
}
const CustomTooltip = ({ active, payload, label}: TooltipProps<ValueType, NameType>) => {
if(active && payload && payload.length){
return (
<div className = "custom-tooltip">
{getState(payload, "A")}
{getState(payload, "B")}
{getState(payload, "C")}
</div>
);
}
return null;
}
You are passing payload which is of type: Payload<ValueType, NameType>[]
But in getState function you expect TooltipProps<ValueType, NameType>
Change code to this:
function getState(payload: Payload<ValueType, NameType>[], state: string) {
if (payload) {
for (let i = 0; i < numberStates; i++) {
if (payload[i].dataKey === state) {
return (
<p>
{payload[i]} : {payload[i].value}
</p>
);
}
}
}
return null;
}
Related
I'm using a for loop to create some svg paths but have some trouble passing a parameter alongside the function. Everything added with the props uses the correct i value, except selectRegion(i). This gets 2 as value, which I think is the final value of i after finishing the loop. How do I pass the correct i value?
componentDidMount() {
var regions = []
for (var i = 0; i < this.state.regionNames.length; i++) {
var region = <Region id={i} border={this.state.regionBorders[i]} color={this.state.regionColors[i]} selectRegion={() => this.selectRegion(i)}/>;
regions.push(region);
}
this.setState({regions: regions});
}
// Select region.
selectRegion(id) {
alert(id);
this.setState({selRegion: id});
}
Region component
import React, { Component } from 'react'
export default class Region extends Component {
constructor (props) {
super(props);
this.state = {
id: props.id,
color: props.color,
border: props.border,
opacity: "0.3",
is_selected: false
}
}
mouseEnter = (is_enter) => {
if(is_enter) {
this.setState({opacity: "0.5"});
alert(this.state.id);
this.props.selectRegion();
} else if (!this.state.is_selected) {
this.setState({opacity: "0.3"});
}
}
mouseClick = () => {
this.setState({is_selected: !this.state.is_selected})
}
render() {
return (
<path d={this.state.border}
fill={this.state.color}
fill-opacity={this.state.opacity}
onClick={() => this.mouseClick()}
onMouseEnter={() => this.mouseEnter(true)}
onMouseLeave={() => this.mouseEnter(false)}/>
)
}
}
#Norse was correct I've fixed it by doing the following:
// Generate map regions.
componentDidMount() {
var regions = []
for (let i = 0; i < this.state.regionNames.length; i++) {
var region = <Region id={i} border={this.state.regionBorders[i]} color={this.state.regionColors[i]} selectRegion={() => this.selectRegion(i)}/>;
regions.push(region);
}
this.setState({regions: regions});
}
I have a file where I try to determine which data should be used in a Gatsby template. I get an array that contains child pages in return, these child pages may contain other child pages. I want to support up to three levels of child pages.
I have a template where I use my paginator (component to find the correct pages), I look for correct pages to render bypassing the slug via pageContext from gatsby-node.js
Template (minus imports)
const projectsSubPages = ({ data, pageContext }) => {
return (
<Layout>
<Menu parentPage={pageContext.parentSlug} />
{data.allSanityProjects.edges.map((childNode) =>
<>
{childNode.node.childPages.length > 0 &&
<Paginator
pageData={childNode.node.childPages}
parentPage={pageContext.parentSlug}
key={childNode.node._id}
/>
}
</>
)}
</Layout>
);
};
export const query = graphql`
{
allSanityProjects {
edges {
node {
childPages {
_rawBlockContent
title
slug
childPages {
slug
title
childPages {
title
slug
childPages {
slug
title
_key
}
_key
}
_key
}
_key
}
_key
}
}
}
}
`;
export default projectsSubPages;
My paginator component (minus imports)
const subPageLevelFinder = ({ pageData, parentPage }) => {
const SubLevels = () => {
let pageLevel = "test";
if (pageData.slug === parentPage) {
pageLevel = pageData.slug
}
if (pageData.childPages && pageData.childPages.length > 0) {
pageData.childPages.map((secondLevel) => {
if (secondLevel.slug === parentPage) {
pageLevel = secondLevel.slug
return (pageLevel)
} else if (pageData.childPages.childPage && pageData.childPages.childPages.length > 0) {
secondLevel.childPages.map((thirdLevel) => {
if (thirdLevel.slug === parentPage) {
pageLevel = thirdLevel.slug
return (pageLevel)
}
})
} else {
return (
pageLevel = "No page level found"
)
}
})
}
return (
pageLevel
)
}
return (
<>
{console.log(SubLevels())}
{SubLevels()}
</>
)
};
See this gist for the return of the GraphQL query and gatsby-node.js https://gist.github.com/AndreasJacobsen/371faf073a1337b6879e4fd6b860b26f
My goal is to run a component that has a template in my paginator and passing the data this template should use from the SubLevels function, but this function returns the first set let value every time. So all of my if-statements fail, I can't figure out where the issue is, I've tried changing the if parameters several times, but this seems to fit the GraphQL query
It turns out that the error came from my trying to access array elements in a multi dimentional array.
So the array I got back had three elements, all with a slug. I tried to access the slug but in order to get that slug I had to loop through the elements.
See attached solution that works (but is not very efficient), notice that this solution has a map function at the very top level; this solved the issue.
import React from "react";
import SubPageTemplate from "./subPageTemplate";
import { Link } from "gatsby";
import { useStaticQuery, graphql } from "gatsby";
const BlockContent = require("#sanity/block-content-to-react");
const subPageLevelFinder = ({ pageData, parentPage, childSlug }) => {
const subLevels = () => {
let pageLevel = null;
pageData.map((mappedData) => {
if (mappedData.slug === childSlug) {
pageLevel = mappedData;
return pageLevel;
} else {
if (mappedData.childPages && mappedData.childPages.length > 0) {
if (mappedData.slug === childSlug) {
return (pageLevel = mappedData);
} else {
mappedData.childPages.map((secondLevel) => {
if (secondLevel.slug === childSlug) {
pageLevel = secondLevel;
return pageLevel;
} else if (
mappedData.childPages.childPage &&
mappedData.childPages.childPages.length > 0
) {
secondLevel.childPages.map((thirdLevel) => {
if (thirdLevel.slug === childSlug) {
pageLevel = thirdLevel;
return pageLevel;
}
});
}
});
}
} else {
return false;
}
}
});
return pageLevel;
};
return <>{subLevels() && <SubPageTemplate pageLevel={subLevels()} />}</>;
};
export default subPageLevelFinder;
So I have built a graph generator and have all the correct data on the props the problem is when I go to render the build component It logs
Object { "$$typeof": Symbol(react.element), type: createElement(), key: "1", ref: null, props: {…}, _owner: null, _store: {…}, … }
Here is the Code I am sure its something silly I am not understanding about the render.
i
mport React, { Component } from 'react'
import * as action from '../../actions'
import { connect } from 'react-redux'
import jsx from 'react-jsx'
import { bindActionCreators } from 'redux'
import {Modal} from './Modal'
#connect(
(flux, props) => {
return {
filters: flux.FilterStore,
ready: flux.FilterStore.ready,
presets: flux.DataStore.preSelectList,
graphs: flux.GraphStore.graphList
}
},
(dispatch, props) => {
dispatch(action.fetchGraphList())
return {
addDataReportGraphDetails: bindActionCreators(action.addDataReportGraphDetails, dispatch)
}
}
)
class RenderGraphPreview extends Component {
constructor(props) {
super(props)
this.state = {
running: {},
show:false,
graph:{}
}
this.data = 0;
}
//Error function for shorthand errors
throwError = (string = "Error", err = null) => {
throw new Error(`${string}:${err}`)
}
//simple print function to print objects and strings
p = (string, variable) => {
typeof variable === `object` ? variable = JSON.stringify(variable) : variable
console.log(`${string}:${variable}`)
}
showModal = e => {
this.state.show = true
}
generateGraph = ({ presets, filters, graphDetails }) => {
var reportProps = {
wasRunning: true,
companyName: filters.company_name,
companyVertical: filters.company_vertical,
graphTitle: jsx.client(`<div>${graphDetails.title}</div>`)(reportProps).props.children
}
this.data++
if (typeof reportProps.graphTitle == "object") {
reportProps.graphTitle = reportProps.graphTitle.join("")
}
if (!this.state.running) {
reportProps.wasRunning = false
this.state.running = true
}
if (graphDetails.graph) {
var Graph = React.createFactory(require(`../graphs/${graphDetails.graph}`).default);
var newGraphProps = {}
var graphPropKeys = Object.keys(graphDetails.props || {})
graphPropKeys.map((graphKey) => {
if (graphDetails.props[graphKey] && graphDetails.props[graphKey].toString().length > 0)
newGraphProps[graphKey] = graphDetails.props[graphKey]
})
if (graphDetails.timeframe) {
newGraphProps[timeframe] = graphDetails[timeframe]
}
if (graphDetails.props.attackIndexFilterPreset) {
let preset;
for (let j = 0, jEnd = presets.length; j < jEnd; j++) {
if (presets[j]._id == graphDetails.props.attackIndexFilterPreset) {
return preset = presets[j]
}
}
if (preset) {
console.log(`In presents`)
newGraphProps = { ...preset, ...newGraphProps }
}
}
}
// console.log(<Graph key={this.state.count++} isDocument={true} reportKey={graphDetails.key} onImageCreated={this.props.addDataReportGraphDetails} {...filters} {...reportProps} {...newGraphProps}/>)
return (
<Graph key={this.data} isDocument={true} reportKey={graphDetails.key} onImageCreated={this.props.addDataReportGraphDetails} {...filters} {...reportProps} {...newGraphProps}/>
)
}
//Verifies we have the correct data to build the graph
startGraphGeneration = async (e,{ props }) => {
e.preventDefault()
let require = this.props.filters && this.props.presets && props
if (!require) {
this.throwError()
}
let graphProps = {
presets: this.props.presets,
filters: this.props.filters,
graphDetails: props,
}
let g = await this.generateGraph(graphProps)
this.setState({
graph: g
});
console.log(g)
}
render() {
var x = this.state.graph
return (
<div>
<button onClick={(e) => this.startGraphGeneration(e,this.props)}>Preview Graph</button>
{this.state.graph ? <x/> : `Doing Noting`}
</div>
)
}
}
export default connect()(RenderGraphPreview)
In your render method you use this.state.graph. You set this variable to the value returned from generateGraph function, which returns rendered node, not a component
. And then you try to render this node as a component(<x/>), which doesn't work. Also in
generateGraph function console.log(g) shows you rendered component. So just return x in you render method instead:
render() {
var x = this.state.graph
return (
<div>
<button onClick={(e) => this.startGraphGeneration(e,this.props)}>Preview Graph</button>
{this.state.graph ? x : `Doing Noting`}
</div>
)
}
I have a method that returns an array of components that can be comletely different:
renderComponents() {
const children = [];
children.push(this.renderComponent1());
children.push(this.renderComponent2());
if (something) {
children.push(this.renderComponent3());
}
return children;
}
But of course I'm getting an error Each child in an array or iterator should have a unique "key" prop.. I've tried to set key like this:
children.forEach((child, i) => {
Object.defineProperty(child.props, 'key', { value: i });
});
But as it turns out React prevents extension of props so I've received Cannot define property key, object is not extensible.
So my question is next: is it possible to set key prop to each component in an array after instantiating of those components?
UPD: The real code is next (it renders a pagination with ranges like this [1]...[5][6][7][8][9]...[100]):
renderPaginationButton(page) {
const { query, currentPage } = this.props;
return (
<Link
className={classNames(styles.link, { [styles.active]: page === currentPage })}
to={routes.searchUrl({ ...query, page })}
>
{page}
</Link>
);
}
renderPaginationSeparator() {
return (
<div className={styles.separator}>...</div>
);
}
renderPaginationRange(from, amount) {
const { pagesCount } = this.props;
const result = [];
for (let i = Math.max(from, 1); i < Math.min(from + amount, pagesCount); i++) {
result.push(this.renderPaginationButton(i));
}
return result;
}
renderPagination() {
const { currentPage, pagesCount } = this.props;
if (pagesCount <= 1) {
return;
}
const children = this.renderPaginationRange(currentPage - 2, 5);
if (currentPage > 4) {
children.unshift(
this.renderPaginationButton(1),
this.renderPaginationSeparator()
);
}
if (pagesCount - currentPage > 4) {
children.push(
this.renderPaginationSeparator(),
this.renderPaginationButton(pagesCount)
);
}
return (
<div className={styles.pagination}>
{children}
</div>
);
}
To answer your question directly, you can use React.cloneElement to add props onto an already instantiated component.
But that's not what you should do in this case.
In your case, you should have renderPaginationButton() to return a <Link> element with key= prop already put in.
So in one of my projects, I am attempting to add the functionality of saving track elements from the Spotify API to the session, then pulling them again. I inserted a console log to find out that even when the element from the session storage is undefined, it still enters the if block in the checkPlaylistName() method. The console log in the render statement is run twice, the first time, it passes an empty array, which is what I want when no elements are passed, the second time, it passes undefined for some reason. That's what causes an error in a different component, saying it is passed undefined. If you need the whole repo, you can find it here. Otherwise, this is the code causing the issues:
import React from 'react';
import './App.css';
import SearchBar from '../SearchBar/SearchBar';
import SearchResults from '../SearchResults/SearchResults';
import Playlist from '../Playlist/Playlist';
import Spotify from '../../util/Spotify.js';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
playlistName: 'New Playlist',
playlistTracks: [],
term: ''
}
this.addTrack = this.addTrack.bind(this);
this.removeTrack = this.removeTrack.bind(this);
this.updatePlaylistName = this.updatePlaylistName.bind(this);
this.savePlaylist = this.savePlaylist.bind(this);
this.search = this.search.bind(this);
}
addTrack(track) {
const addingTrack = (track) => this.setState({playlistTracks: [...this.state.playlistTracks, track]});
addingTrack(track);
this.removeTrack(track, false);
sessionStorage.setItem("playlistTracks", this.state.playlistTracks);
}
removeTrack(track, removePlaylist) {
if(removePlaylist) {
const ids = this.collectIds(true);
let trackIndex = -1;
for(let i = 0; i < ids.length; i++) {
if (ids[i] === track.id) {
trackIndex = i;
}
}
if (trackIndex !== -1) {
const newPlaylist = this.state.playlistTracks;
newPlaylist.splice(trackIndex, 1);
this.setState({playlistTracks: newPlaylist});
this.search(this.state.term);
}
} else {
const ids = this.collectIds(false);
let trackIndex = -1;
for(let i = 0; i < ids.length; i++) {
if (ids[i] === track.id) {
trackIndex = i;
}
}
if (trackIndex !== -1) {
const newResults = this.state.searchResults;
newResults.splice(trackIndex, 1);
this.setState({searchResults: newResults});
}
}
sessionStorage.setItem("playlistTracks", this.state.playlistTracks);
}
collectIds(removePlaylist) {
let ids = [];
if(removePlaylist) {
this.state.playlistTracks.map(track => ids.push(track.id));
} else {
this.state.searchResults.map(track => ids.push(track.id));
}
return ids;
}
updatePlaylistName(name) {
this.setState({playlistName: name});
sessionStorage.setItem("playlistName", name);
}
savePlaylist() {
let trackURIs = [];
for(let i = 0; i < this.state.playlistTracks.length; i++) {
trackURIs.push(this.state.playlistTracks[i].uri);
}
Spotify.savePlaylist(this.state.playlistName, trackURIs);
this.setState({playlistName: 'New Playlist', playlistTracks: []});
sessionStorage.removeItem("playlistTracks");
sessionStorage.removeItem("playlistName");
}
async search(term) {
const results = await Spotify.search(term);
this.setState({searchResults: results});
const resultIds = this.collectIds(false);
const playlistIds = this.collectIds(true);
let indexes = [];
for(let i = 0; i < resultIds.length; i++) {
for(let j = 0; j < playlistIds.length; j++) {
if (resultIds[i] === playlistIds[j]) {
indexes.push(i);
}
}
}
if(indexes.length > 0) {
for (let k = 0; k < indexes.length; k++) {
results.splice(indexes[k], 1);
}
}
this.setState({searchResults: results});
this.setState({term: term});
}
checkTracks() {
if (sessionStorage.getItem("playlistTracks") !== undefined) {
const tracks = sessionStorage.getItem("playlistTracks");
this.setState({playlistTracks: tracks});
}
return this.state.playlistTracks;
}
checkPlaylistName() {
const savedName = sessionStorage.getItem("playlistName");
if (savedName !== null || savedName !== undefined) {
this.setState({playlistName: savedName});
console.log("hi");
}
return this.state.playlistName;
}
render() {
return (
<div id="root">
<h1>Ja<span className="highlight">mmm</span>ing</h1>
<div className="App">
<SearchBar onSearch={this.search} />
<div className="App-playlist">
<SearchResults searchResults={this.state.searchResults} onAdd={this.addTrack} onRemove={this.removeTrack} />
{console.log(this.checkTracks())}
<Playlist
playlistName={this.checkPlaylistName()}
playlistTracks={this.checkTracks()}
onRemove={this.removeTrack}
onNameChange={this.updatePlaylistName}
onSave={this.savePlaylist}
/>
</div>
</div>
</div>
);
}
}
export default App;
setState function is asynchronous (https://medium.com/#baphemot/understanding-reactjs-setstate-a4640451865b), therefore you are returning old state in checkTracks method.
Solution:
Don't setState in your checkTracks method, make it:
if (sessionStorage.getItem("playlistTracks") !== undefined) {
return sessionStorage.getItem("playlistTracks");
}
return [];
And use this method in constructor to define state.
Also in your way you are setting state (via checkTracks method) in render function which causes an infinite loop.