change Component name with map function - javascript

I am trying to change component name.
It is normally like <Dashboard/> and <Table/>. But I want to make like
const names = [ {"name":Dashboard},{"name":Table}]
names.map(c => { <c.name />}
render(){
return(
{names.map(c => {
<Panel>
<Panel.Body>
<Row>
<Col md={4}>
<FormControl
/>
</Col>
</Row>
<hr />
<c.name bla={bla} />
<hr />
</Panel.Body>
</Panel>
)}

According to the doc, you need to declare a capitalized variable first:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
and then you can call your component name by using your variable.
And in your case, it would be :
{names.map((c) => {
const CapitalizedComponent = c.name;
return <CapitalizedComponent />;
})}
Please also notice that the map function in your code didn't return anything.
working example in sandbox

Related

Why is my page reloading after onChange trigger update?

I have added different forms in different methods but when I type anything in input fields, the page reloads keeping the states and again I have to click on the field and type and same cycle happens. It is working fine if I add everything in return. Can somebody tell explain why is this happening and how to stop it?
I am also sharing a piece of code.
function MyForm() {
const [commentForm, setCommentForm] = useState({
Comment: "",
});
const onCommentChange = (obj) => {
setCommentForm((prevState) => {
return {
...prevState,
...obj,
};
});
};
const IForm = () => (
<Table>
<CardBody>
<Row>
<Col className="col-2">
<Label>Comment: </Label>
</Col>
<Col className="col-1">
<Input type="text"
value={commentForm.Comment}
onChange={(e) =>
onCommentChange({ Comment: e.target.value })} />
</Col>
</Row>
</CardBody>
</Table>
);
return (
<div>
<IForm />
</div>
)
}
export default MyForm
that's because you define IForm as A component inside the current component which is not correct. so you have two solutions.
1 - move IFORM Component outside the current react.
function MyForm() {
const [commentForm, setCommentForm] = React.useState({
Comment: ""
});
const onCommentChange = (obj) => {
setCommentForm((prevState) => {
return {
...prevState,
...obj
};
});
};
return (
<div>
<IForm commentForm={commentForm} onCommentChange={onCommentChange} />
</div>
);
}
export default MyForm;
const IForm = ({ commentForm, onCommentChange }) => (
<Table>
<CardBody>
<Row>
<Col className="col-2">
<Label>Comment: </Label>
</Col>
<Col className="col-1">
<Input type="text"
value={commentForm.Comment}
onChange={(e) =>
onCommentChange({ Comment: e.target.value })} />
</Col>
</Row>
</CardBody>
</Table>
);
2 - declare the IForm as a normal function inside the current component.
function MyForm() {
const [commentForm, setCommentForm] = React.useState({
Comment: ""
});
const onCommentChange = (obj) => {
setCommentForm((prevState) => {
return {
...prevState,
...obj
};
});
};
const form = () => (
<Table>
<CardBody>
<Row>
<Col className="col-2">
<Label>Comment: </Label>
</Col>
<Col className="col-1">
<Input type="text"
value={commentForm.Comment}
onChange={(e) =>
onCommentChange({ Comment: e.target.value })} />
</Col>
</Row>
</CardBody>
</Table>
);
return <div> {form()} </div>;
}
export default MyForm;
The reason is that the IForm component is declared inside the MyForm Component. Which means that whenever the state of MyForm Component changes it will refresh its dom tree. And when the dom will rerender the functional component IForm will be executed again that's why you'll always lose the focus of the input but you never lose the state of the MyForm component.
To stop it from being happening either declare the IForm Component outside of the MyForm component or move the jsx of the IForm inside the Return of MyFOrm component.
You should just setCommentForm the value. I don't think you need to spread the prevState.
What you want to achieve is to set the state value to the new one.
Also, you don't have any useEffect right?

Warning: Cannot update a component while rendering a different component. ReactJS

In my ReactHooks/Typescript app, I have a Navigation component, that renders a PatientInfo component. The PatientInfo child is rendered conditionally based on what props it is passed, as decided by a searchbox in another child component - MyPatients.
In this structure, I am getting the following error:
Navigation.tsx:
// code....
<Route exact path="/" component={MyPatients} />
<Route
exact
path="/Pasient"
render={() => (
<PatientInfo
setName={setName}
setSchema={setSchema}
patientID={patientID}
/>
)}
/>
// code....
MyPatients:
const MyPatients = (props: { history: History }) => {
localStorage.clear();
const [patientID, setPatientID] = useState(
localStorage.getItem('myData') || '',
);
useEffect(() => {
localStorage.setItem('myData', patientID);
}, [patientID]);
return (
<>
<div className="search-container"></div>
<Row gutter={[60, 40]} justify={'center'}>
<Col span={1000}>
<p>Søk med personnummer for å finne en pasient</p>
<Search
style={{ width: 400 }}
className="search-bar"
placeholder="Søk etter en pasient!"
onSearch={(value: string) => setPatientID(value)}
/>
</Col>
</Row>
{patientID &&
props.history.push({ pathname: 'Pasient', state: patientID })}
</>
);
};
export default MyPatients;
I am not familliar with this issue, and don't understand what's happening. My educated guess is that React doesn't like the fact that the state of the parent component is being updated by functions passed to the children, which again are dependant on the props passed along with it. Am I on to something? Any ideas as to what is causing this if not?
Any help is appreciated.
You are navigating with history.push on each render.
As #HMR mentioned in the comment, you have to remove navigation from JSX template and add it into a separate effect.
const MyPatients = (props: { history: History }) => {
localStorage.clear();
const [patientID, setPatientID] = useState(
localStorage.getItem("myData") || ""
);
useEffect(() => {
localStorage.setItem("myData", patientID);
}, [patientID]);
// separate effect here
useEffect(() => {
if (patientID) {
props.history.push({ pathname: "Pasient", state: patientID });
}
}, [props, patientID]);
return (
<>
<div className="search-container"></div>
<Row gutter={[60, 40]} justify={"center"}>
<Col span={1000}>
<p>Søk med personnummer for å finne en pasient</p>
<Search
style={{ width: 400 }}
className="search-bar"
placeholder="Søk etter en pasient!"
onSearch={(value: string) => setPatientID(value)}
/>
</Col>
</Row>
</>
);
};
export default MyPatients;
EDIT
This might cause your error:
<PatientInfo
setName={setName}
setSchema={setSchema}
patientID={patientID}
/>
If you call setName or setSchema on render of PatientInfo then Navigation state gets updated before PatientInfo render is finished.

React Display component after click on card

I creating simple app. It must be app with information about pokemons. So I need to create, when user click on pokeCard, Sidebar info.
How it look now:
So, Sidebar is must to be, it can be, for example, only white background.
I think about styled-components, but I not sure, that this would be the right decision
How to do it with functional Component?
Wrapper
const [SelectedPokemonIndex, setSelectedPokemonIndex] = useState();
return (
<Row>
<Col xs={24} sm={14} lg={16}>
<Pokemons
PokemonsList={PokemonsList}
loadMoreItems={loadMoreItems}
Loading={Loading}
onClickPoke={(pokemonId) => {
fetchPokemonDetails(pokemonId);
fetchPokemon(pokemonId);
fetchPokemonStats(pokemonId);
setSelectedPokemonIndex(pokemonId);
}}
/>
</Col>
<Col xs={24} sm={10} lg={8}>
<About
pokemon={SelectedPokemon}
PokemonTypes={PokemonTypes}
PokemonStats={PokemonStats}
index={SelectedPokemonIndex}
LoadingForSelectedPokemon={LoadingForSelectedPokemon}
/>
</Col>
</Row>
);
}
export default Wrapper;
Child component of wrapper
function Pokemons(props) {
let { PokemonsList, loadMoreItems, Loading, onClickPoke } = props;
return (
<GridCard
image={`${IMAGE_BASE_URL}${++index}.png`}
pokemonId={index}
pokemonName={pokemon.name}
pokemonUrl={pokemon.url}
onClickPoke={onClickPoke}
/>
PokeCard
import React, { useEffect, useState } from "react";
import { Col, Typography } from "antd";
import "./GridCards.css";
const { Title } = Typography;
function GridCards(props) {
let { key, image, pokemonName, pokemonUrl, pokemonId } = props;
return (
<Col
key={key}
lg={8}
md={12}
xs={24}
onClick={() => {
props.onClickPoke(pokemonId);
}}
>
<div
className="poke-card"
}}
>
<img alt={pokemonName} src={image} />
{LoadingForPokemon && <div>Loading...</div>}
</div>
</Col>
);
}
export default GridCards;
This is Sidebar, what must to be change:
function About(props) {
let {
pokemon,
LoadingForSelectedPokemon,
index,
PokemonTypes,
PokemonStats,
} = props;
return (
<div
style={{
position: "sticky",
top: 0,
display: "flex",
justifyContent: "center",
}}
>
<PokemonDetails
pokemonName={pokemon.name}
pokemonId={pokemon.id}
pokemon={pokemon}
LoadingForSelectedPokemon={LoadingForSelectedPokemon}
image={`${IMAGE_BASE_URL}${index}.png`}
PokemonTypes={PokemonTypes}
PokemonStats={PokemonStats}
/>
</div>
);
}
This is a pretty broad question and there's a lot going on in your code, but I think you need to move some state management around.
In your GridCards component, give it a prop called onCardClick and call that function in the onClick of the <Col> component you're using. It'll look something like this:
function GridCard(props) {
const { key, image, pokemonName, pokemonUrl, pokemonId, onCardClick } = props;
return (
<Col
key={key}
lg={8}
md={12}
xs={24}
onClick={() => onCardClick()}
>
<div
className="poke-card"
>
<img alt={pokemonName} src={image} />
{LoadingForPokemon && <div>Loading...</div>}
</div>
</Col>
);
}
export default GridCard;
Then in your wrapper components, instead of using the Pokemons component, I think you can just use your GridCard component and map on whatever data you're using to render out the cards currently.
So something like this:
export default function Wrapper() {
const [selectedPokemon, setSelectedPokemon] = useState(null);
// data is whatever you have to iterate over to put data into your cards
const pokemonCards = data.map((p, idx) => {
return (
<GridCard
key={idx}
image={p.image}
pokemonName={p.pokemonName}
onCardClick={() => setSelectedPokemon(p)}
></GridCard>
);
});
return (
<Row>
<Col xs={24} sm={14} lg={16}>
{pokemonCards}
</Col>
<Col xs={24} sm={10} lg={8}>
<About pokemon={selectedPokemon} />
</Col>
</Row>
);
}
Basically, what you should try and accomplish is letting each GridCard component contain all the necessary information for your About component, so that when you click on a GridCard your Wrapper's state can update with a selectedPokemon and then pass that back down into your About component.
I found solution for my question.
Its only need to set isAboutShown
const [isAboutShown, setAboutShow] = useState(false);
And onClick I coding setAboutShow(true)
How to display component?
{isAboutShown && (
<About/>)

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

React-router: Pass props in Link and map match data

I'm trying to figure out what I'm not doing well.
I have a shops object which is a database with different shop inside.
I want to render each shop's information in their own page thanks to react-router.
I already try many way to render my details.
The error that always come back its that I can read props, or state of undefined in my shopDetails component. When I want to console log my location element, it is shown as undefined but when I go to my react developer tool I can see my shops data right stored in my shopDetails props.location...
I really don't understand how to render the good data. I open all other subject without understanding how to deal with my problem.
If you could help on this, it would be amazing. Thanks for your time.
App.js
render() {
return (
<Router>
<HeaderFilters
wrapperHeaderFunction={this.wrapperHeaderFunction}
zip_code={this.state.zip_code}
handleChanges={this.handleChanges}
isClicked={this.isClicked}
filterClick={this.filterClick}
selectedOption={this.state.selectedOption}
moreFilterClick={this.moreFilterClick}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
startDate={this.state.startDate} // momentPropTypes.momentObj or null,
startDateId="your_unique_start_date_id" // PropTypes.string.isRequired,
endDate={this.state.endDate} // momentPropTypes.momentObj or null,
endDateId="your_unique_end_date_id" // PropTypes.string.isRequired,
onDatesChange={({ startDate, endDate }) =>
this.setState({ startDate, endDate })
} // PropTypes.func.isRequired,
focusedInput={this.state.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null,
onFocusChange={focusedInput => this.setState({ focusedInput })} // PropTypes.func.isRequired,
/>
{this.state.isMoreFiltersRequired ? (
<MoreFilters
handleChanges={this.handleChanges}
isClicked={this.isClicked}
filterClick={this.filterClick}
moreFilterClick={this.moreFilterClick}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
/>
) : null}
<div>
{this.state.login ? <Spinner animation="border" size="xl" /> : null}
</div>
<Switch>
<Route
exact
path="/"
render={() => (
<ShopPreview
loading={this.state.loading}
shops={this.state.shops}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
/>
)}
/>
<Route
path="/search"
render={() => (
<ShopSearch
loading={this.state.loading}
shops={this.state.shops}
filteredResults={this.state.filteredResults}
rating={this.state.rating}
/>
)}
/>
<Route
path={`/shopDetail/:id`}
render={routeProps => (
<ShopDetails {...routeProps} shops={this.state.shops} />
)}
/>
</Switch>
</Router>
);
}
}
export default App;
Shops.js (the component which renders the shop list)
render() {
return (
<Container>
<ListGroup>
{this.props.shops.map((detail, index) => (
<ListGroup.Item key="index">
<Row>
<Col>
<Image
alt=""
src={detail.imgURL}
width={150}
height={150}
rounded
/>
</Col>
<Col>
<h3 className="shop_title">{detail.nom}</h3>
<StarRatings
rating={this.props.rating}
starRatedColor="#DAA520"
changeRating={this.changeRating}
numberOfStars={5}
starDimension="15px"
name="rating"
starSpacing="2px"
/>
<p id="resume">{detail.resume}</p>
</Col>
<Col>
<Row>
{detail.startPrice === ""
? "Sur devis"
: "A partir de " + detail.startPrice + " €"}
</Row>
<Row>
{/* Make route with id, with key= detail.id */}
<Link
to={{
pathname: "/shopDetail/" + detail.id,
state: {shops : this.props.shops}
}}
>
<Button
className="detailButton"
key={detail.id}
variant="primary"
onClick={this.props.filterClick}
>
Détails
</Button>
</Link>
</Row>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
</Container>
);
}
}
export default Shops;
ShopDetails.js (the component which renders the shop details according to its URL id)
import React, { Component } from 'react'
class ShopDetails extends Component {
constructor(props){
super(props)
this.setState({
})
}
render() {
console.log("Props shops: " ,this.props.shops)
const id = window.location.pathname.replace("/shopDetail/", "");
const data = this.props.shops
const location = this.props.location
console.log("Location:", location)
const shop = data.find(s => s.id === id)
return (
<div>
<h1>{shop.id}</h1>
<h3>{shop.nom}</h3>
<p>{shop.website}</p>
</div>
)
}}
export default ShopDetails
For now, I'm just allow to render the id, but I can't access to my elements "shops" in my state which stock my shop data to map on each component.
edit:
SCREENSHOT CONSOLE.LOG
edit2:
import React, { Component } from 'react'
class ShopDetails extends Component {
constructor(props){
super(props)
this.setState({
shop:{}
})
}
render() {
console.log("Props shops: " ,this.props.shops)
const id = window.location.pathname.replace("/shopDetail/", "");
console.log("id: ", id)
const data = this.props.shops
console.log("data: ", data)
const location = this.props.location.state
console.log("Location:", location)
const shop = data.find(s => s.id === id)
return (
<div>
</div>
)
}}
export default ShopDetails
Edit3
Screen log object developed1
Edit3
Screen log object developed2
Edit3
Screen log object developed3
Edit 4:
const shop, can finally be render something in console.log
Problem was about a triple = in my const shop = data.find(s => s.id == id)
import React, { Component } from 'react'
class ShopDetails extends Component {
constructor(props){
super(props)
this.setState({
shop:{}
})
}
render() {
console.log("Props shops: " ,this.props.shops)
const id = window.location.pathname.replace("/shopDetail/", "");
console.log("id: ", id)
const data = this.props.shops
console.log("data: ", data)
const shop = data.find(s => s.id == id)
console.log("shop: ", shop)
console.log("this.props.match.params.id: ", this.props.match.params.id)
return (
<div>
{shop.map((detail, index) => (
<div key={index}>
<h1>{detail.nom}</h1>
</div>
))}
<p>{data.id}</p>
</div>
)
}}
export default ShopDetails
Now I have to return my data stored in my shop const, see below the link of the console.log(shop) since the edit 4
screenshot edit4
You have to set the == instead of === because the id of your shop is a number and the id from your url is a string. If you cast the id from your url to number, it should also work with ===. To render your shop data, after you find it, should not be done with map since you cannot access the object keys with it. You should just render it with shop.nom etc. Hope this helps. Happy coding.
Finally, solve my problem !
So first I had this problem with my === operator as well explain Domino987 previously.
Then if I couldn't render my {shop.nom} element even if I could see my shop element in my props.
click to see my console.log("const shop = data.find(...): ", shop);
It is because at the moment the component renders, there is not value inside shop.
We can know that by looking at the 'i' icon in my dev tools.
So I had 2 options:
You have 2 options:
Put all my information in state, not like shop={}, but like bornePhoto, cabinePhoto, helio, booth… etc, then it first renders with empty information, and then when the information arrives, it updates the state and it will shown.
Conditional render. Example:
if (!shop.nom){
return null
} else {
return <h1>{shop.nom}<h1/>
}
OR :
{Boolean(shop.nom) ? <h1>{shop.nom}<h1/> : null}
I used ternary method with the following code:
class ShopDetails extends Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
console.log("Props shops: ", this.props.shops);
const id = this.props.match.params.id;
console.log("id: ", id);
const data = this.props.shops || {};
console.log("data: ", data);
const shop = data.find(s => s.id == id);
console.log("const shop = data.find(...): ", shop);
return (
<div className="container">
<Row>
<Col>
{shop ? (
<Image
alt=""
src={shop.imgURL}
width={150}
height={150}
rounded
/>
) : null}
</Col>
<Col >
{shop ? <h1>{shop.nom}</h1> : null}
{shop ? <h2>A partir de {shop.startPrice} €</h2> : null}
</Col>
</Row>
<Row >
<Col >
<h3>Site internet:</h3>
{shop ? <p>{shop.website}</p> : null}
</Col>
<Col >
<h3>Services</h3>
<p>Rappel des services</p>
</Col>
</Row>
<Row >
<Col>
<h4>Présentation</h4>
{shop ? <p>{shop.resume}</p> : null}
</Col>
</Row>
</div>
);
}
}
export default ShopDetails;
That solve my problem, and I could finally render my elements.
If that could help someone, I will be glad !
Thanks to all the community for helping me in my project !

Categories

Resources