I have a MongoDB database hosted in MongoDB Atlas, I am using MongoDB Atlas RealM as well to create GraphQL schemas.
I am consuming the GraphQL API in react js application created using Next JS.
I am also using useSWR hooks to fetch the data. With useSWR I don't have any issues fetching the data but when it comes to running the mutation queries using useSWR I am unable to get it done.
There is no proper documentation on how to do it.
Here is my code
//#ts-nocheck
import React, { useContext, useEffect, useState } from "react";
import { Drawer, Button, Form, Input, Select, Cascader } from "antd";
import { TRANSACTION_TYPES } from "queries/transactionTypes";
import { LIST_CATEGORIES } from "queries/categories";
import { FINANCIAL_ACCOUNTS } from "queries/financialAccounts";
import { PAYMENT_MODES } from "queries/paymentModes";
import { INSERT_TRANSACTION } from "queries/transaction";
import useSWR from "swr";
import { AuthContext } from "context/AuthContext";
const AddTransaction = () => {
const userId = useContext(AuthContext);
const { Option } = Select;
const [visible, setVisible] = useState(true);
const [txnType, setTxnType] = useState();
const { data: txnTypeData } = useSWR(TRANSACTION_TYPES);
const { data: categories } = useSWR(LIST_CATEGORIES);
const { data: accounts } = useSWR([FINANCIAL_ACCOUNTS, { user_id: userId }]);
const { data: modes } = useSWR(PAYMENT_MODES);
const { data: transaction, mutate } = useSWR(INSERT_TRANSACTION);
if (txnTypeData && txnTypeData.error) {
return <p>An error occurred: ${txnTypeData.error}</p>;
}
const txnTypes = txnTypeData ? txnTypeData.data.transaction_types : null;
console.log("transaction", transaction);
const options = categories
? categories.data.categories.map((cat) => {
let children = [];
cat.subcategories.forEach((subcat) =>
children.push({
value: subcat,
label: subcat,
})
);
return {
value: cat.name,
label: cat.name,
children,
};
})
: null;
const showDrawer = () => {
setVisible(true);
};
const onClose = () => {
setVisible(false);
};
const handleTxnType = (value) => {
setTxnType(value);
};
const onFinish = (values) => {
const txnData = {};
txnData.date = new Date();
txnData.user_id = userId;
txnData.category = values.category[0];
txnData.subcategory = values.category[1];
txnData.type = values.txntype;
txnData.amount = values.amount;
txnData.description = values.description;
txnData.mode = values.mode;
txnData.account = {
account_name: "ICICI bank",
bank_name: "ICICI bank",
type: "savings",
};
console.log("txnData", txnData);
mutate([INSERT_TRANSACTION, { data: txnData }]);
};
return (
<>
<Button type="primary" onClick={showDrawer}>
Open
</Button>
<Drawer
title="Add Transaction"
placement="right"
onClose={onClose}
visible={visible}
>
<Form
layout={"vertical"}
initialValues={{ remember: true }}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item label="Txn Type" name="txntype">
<Select onChange={handleTxnType}>
{txnTypes &&
txnTypes.map((type) => (
<Option key={type._id} value={type.type}>
{type.type}
</Option>
))}
</Select>
</Form.Item>
<Form.Item label="Cateogry / Subcategory" name="category">
<Cascader options={options} placeholder="Please select" />
</Form.Item>
<Form.Item label="Description" name="description">
<Input placeholder="Proper description of the expense" />
</Form.Item>
<Form.Item label="Amount(₹)" name="amount">
<Input placeholder="Amount" />
</Form.Item>
{txnType !== "self transfer" && (
<Form.Item label="Account" name="account">
<Select>
{accounts &&
accounts.data.financial_accounts.map((acc) => (
<Option key={acc._id}>{acc.name}</Option>
))}
</Select>
</Form.Item>
)}
{txnType === "self transfer" && (
<>
<Form.Item label="From Account" name="from_account">
<Select>
{accounts &&
accounts.data.financial_accounts.map((acc) => (
<Option key={acc._id}>{acc.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item label="To Account" name="to_account">
<Select>
{accounts &&
accounts.data.financial_accounts.map((acc) => (
<Option key={acc._id}>{acc.name}</Option>
))}
</Select>
</Form.Item>
</>
)}
<Form.Item label="Mode" name="mode">
<Select defaultValue="gpay">
{modes &&
modes.data.payment_modes.map((mode) => (
<Option key={mode._id} value={mode.name}>
{mode.name}
</Option>
))}
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
</Drawer>
</>
);
};
export default AddTransaction;
The problem is with this query
const { data: transaction, mutate } = useSWR(INSERT_TRANSACTION);
Here is the mutation query
export const INSERT_TRANSACTION = `
mutation INSERT_TRANSACTION($data: TransactionInsertInput!) {
insertOneTransaction(data: $data) {
_id
}
}
`;
I am calling the mutate function in the onFinish() like this
mutate([INSERT_TRANSACTION, { data: txnData }]);
But, I am getting an error message
message: "Variable \"$data\" of required type \"TransactionInsertInput!\" was not provided."
Not sure, why I am getting this error, as I have already been passing the $data to the query through mutate.
Someone, please shed some light on this, what I am doing wrong here?
Related
There are 4 select Components with dependant dropdown menu.But when I select an option its not displaying in the input field although my 'selectedPlanets' state is updating just right.
Here is my code -
import React, { useState } from "react";
import "../css/Destination.css";
function Destination(props) {
const [selectedPlanets, SetselectedPlanets] = useState([
null,
null,
null,
null,
]);
const OnSelectPlanet = async (e, key) => {
const clonedSelectedPlanets = JSON.parse(JSON.stringify(selectedPlanets));
clonedSelectedPlanets[key] = e.target.value;
SetselectedPlanets(clonedSelectedPlanets);
};
const CustomSelectComponents = ({ value, options, OnSelect}) => {
return (
<select value={value} onChange={OnSelect}>
<option> -- Select a Planet -- </option>
{options.map((option) => {
return <option key = {option.name} value={option.name} >{option.name}</option>;
})}
</select>
);
};
const OptionsToRender = (Alloptions, AllselectedOptions, index) => {
console.log(AllselectedOptions);
const optionstoRender =
AllselectedOptions[index] != null
? Alloptions.filter(
(option) =>
!AllselectedOptions.some(
(selectedOption) =>
option && selectedOption && option.name === selectedOption
)
)
: Alloptions;
return optionstoRender;
};
return (
<>
<div className="Parent_Card">
{selectedPlanets.map((planet, index) => {
const options = OptionsToRender(props.planets, selectedPlanets, index);
return (
<>
{console.log(index)}
<CustomSelectComponents
value={
selectedPlanets[index] != null ? selectedPlanets[index] : ""
}
options={options}
OnSelect={(e) => OnSelectPlanet(e, index)}
key={index}
/>
</>
);
})}
</div>
</>
);
}
export default Destination;
I tried debugging it and figured that its maybe because of how and when my component is rendering.But I dont know why and hence not able to find the solution.
My expected result is when I am choosing an option it shows in the input field.
Your code could benefit from a few different approaches to building your 4 different select components:
the use of controlled components
(https://reactjs.org/docs/forms.html#controlled-components)
separating the state for each of the different selects
refactoring the <CustomSelectComponent /> to be a component that only accepts props
Here is an example of those approaches in practice that might provide some direction on getting these selects operating as expected!
import React, { useState } from 'react';
import '../css/Destination.css';
// custom select component
const CustomSelectComponent = ({ onChange, options, value }) => (
<select onChange={onChange} value={value}>
<option> -- Select a Planet -- </option>
{options.map(option => (
<option key={option.name} value={option.name}>
{option.name}
</option>
))}
</select>
);
const Destination = () => {
// mock props
const props = { planets: [{ name: 'Pluto' }, { name: 'Earth' }] };
// separated state
const [selectOneValue, setSelectOneValue] = useState('');
const [selectTwoValue, setSelectTwoValue] = useState('');
const [selectThreeValue, setSelectThreeValue] = useState('');
const [selectFourValue, setSelectFourValue] = useState('');
return (
<div className="Parent_Card">
{/* each custom select component is now controlled by it's own state */}
<CustomSelectComponent
onChange={e => setSelectOneValue(e.target.value)}
options={props.planets}
value={selectOneValue}
/>
<CustomSelectComponent
onChange={e => setSelectTwoValue(e.target.value)}
options={props.planets}
value={selectTwoValue}
/>
<CustomSelectComponent
onChange={e => setSelectThreeValue(e.target.value)}
options={props.planets}
value={selectThreeValue}
/>
<CustomSelectComponent
onChange={e => setSelectFourValue(e.target.value)}
options={props.planets}
value={selectFourValue}
/>
</div>
);
};
export default Destination;
I'm new to React and even more new to Next.js
I've got an input where the user search a city by name in a list of all the cities available.
I've read that useSWR could be interesting to use (before that I made the request with axios inside a useEffect).
Once I got the array of cities I do a map to return all the matches with the request (and then I use it to do an autocomplete).
But I get an error like this :
"Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."
If I just fetch the data, it works but if I do the map on the array I get an infinite loop and I don't know why.
my code :
import React, { useState, useEffect } from "react";
import styles from "./searchBar.module.css";
import { useRouter } from "next/router";
import axios from "axios";
import useSWR from "swr";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch, faAngleDown } from "#fortawesome/free-solid-svg-icons";
import installationType from "../public/installation_type_ids.json";
const SearchBar = ({
setSearchSport,
setSearchCity,
searchCity,
searchSport,
setSearchType,
searchType,
setPage,
}) => {
const router = useRouter();
// States for search bar request
const [city, setCity] = useState(searchCity);
const [cityData, setCityData] = useState([]);
const [sport, setSport] = useState(searchSport);
const [title, setTitle] = useState("");
const [type, setType] = useState(searchType);
const [autoComplete, setAutoComplete] = useState([]);
const [displayAutoComplete, setDisplayAutoComplete] = useState(false);
// handle submit button
const handleSubmit = (e) => {
e.preventDefault();
setSearchCity(city);
setSearchSport(sport);
type ? setSearchType(type) : setSearchType("ALL");
setPage(0);
if (router.pathname !== "/points-de-rencontre-sportive")
router.push("/points-de-rencontre-sportive");
};
const url = "https://bouge-api.herokuapp.com/v1.0/city/ids";
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data: result, error } = useSWR(url, fetcher);
if (error) return <h1>Oups ! Une erreur est survenue...</h1>;
if (!result) return <h1>Chargement en cours...</h1>;
const handleTest = (e) => {
setCity(e.target.value);
e.target.value === 0
? setDisplayAutoComplete(false)
: setDisplayAutoComplete(true);
if (result && result.data) {
const dataMapped = result.data.map((city) => {
return { city: city.name, type: "city" };
});
let tab = [];
dataMapped.map((item, i) => {
item.name
if (item.name.toLowerCase().includes(city)) {
tab.push(item);
}
return setAutoComplete(tab);
});
}
};
// autocomplete rendering
const renderAutoComplete = autoComplete.map((elem, index) => {
console.log(elem);
if (index <= 9) {
return (
<div
className={styles.autocompleteDiv}
key={index}
onClick={() => {
if (elem.type === "city") {
setCity(elem.city);
}
if (elem.type === "sport") {
setSport(elem.sport);
}
setDisplayAutoComplete(false);
}}
>
<p>{elem.type === "city" ? elem.city : elem.sport}</p>
</div>
);
} else {
return null;
}
});
return (
<div className={styles.searchBar}>
<form className={styles.form} onSubmit={handleSubmit}>
<div>
<label htmlFor="city">Ville</label>
<input
type="text"
id="city"
placeholder="Où veux-tu jouer?"
value={city}
onChange={handleTest}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="sport">Sport</label>
<input
type="text"
id="sport"
placeholder="Spécifie le sport"
value={sport}
onChange={(e) => {
setSport(e.target.value);
}}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="title">Nom</label>
<input
type="text"
id="title"
placeholder="Recherche par nom"
value={title}
onChange={(e) => {
setTitle(e.target.value);
let tab = [];
installationType.map((item, i) => {
if (item.installation_type.includes(title)) {
tab.push(item);
}
return setAutoComplete(tab);
});
console.log(tab);
}}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="type">Type</label>
<select
type="text"
id="type"
placeholder="Type de structure"
value={type}
>
<option value="ALL" defaultValue>
Type de structure
</option>
<option value="AS">Association</option>
<option value="PRIV">Privé</option>
<option value="PUB">Public</option>
<option value="EVENT">Activité</option>
</select>
<i>
<FontAwesomeIcon
icon={faAngleDown}
className={styles.selectIcon}
></FontAwesomeIcon>
</i>
</div>
<button>
<i>
<FontAwesomeIcon
icon={faSearch}
className="fa-lg"
></FontAwesomeIcon>
</i>
Rechercher
</button>
</form>
{displayAutoComplete ? (
<div className={styles.searchResults}>{renderAutoComplete}</div>
) : null}
</div>
);
};
export default SearchBar;
After you fetched data, you call setCityData method to update city data, this cause component re-render and run code in your SearchBar component again, so it call setCityData again and then continue re-render => infinite re-render.
I think you should put it into a useEffect:
useEffect(() => {
if (result && result.data) {
const dataMapped = result.data.map((city) => {
return { city: city.name, type: "city" };
});
setCityData(dataMapped)
}
}, [result])
so it will update city data only when result has data
Hi Guys I'm new to React. I am trying to create a cascade drop down list using react hooks and the way I did it works well but I feel something wrong in the way I did it. Please check this code and tell me there is a way that I can improve my code.Thanks in advance
import React, { useState } from 'react';
import './App.css';
function App() {
const[data, setName] = useState({
countrie:"",
state:""
});
let state;
const countrie =['Germany','India','France']
const gstate = ['Duesseldorf', 'Leinfelden-Echterdingen', 'Eschborn']
const istate = ['Delhi', 'Kolkata', 'Mumbai', 'Bangalore']
const fstate =['Auvergne','Bretagne','Corse','Centre']
if(data.countrie==="Germany"){
state = gstate.map((gstate,key)=> <option key={key} value={gstate}>{gstate}</option>)
}else if(data.countrie==="India"){
state = istate.map((istate,key)=> <option key={key} value={istate}>{istate}</option>)
}else{
state = fstate.map((fstate,key)=> <option key={key} value={fstate}>{fstate}</option>)
}
const countries = countrie.map((countrie,key)=> <option key={key} value={countrie}>{countrie}</option>)
function handleCountry(e){
setName({...data,countrie:e.target.value});
}
function handleStateChange(e){
setName({...data,state:e.target.value});
}
return (
<form onSubmit={handleSubmit}>
<div>
<select value={data.countrie} onChange={handleCountry}>
{countries}
</select>
</div>
<div>
<select value={data.state} onChange={handleStateChange}>
{state}
</select>
</div>
<input type="submit" />
</form>
);
}
export default App;
The best suggestion I have is to change the data structure which combines the country and states. Doing so makes it a lot easier to map over each country and getting the states without having to map variables. This makes it also more scalable.
Here is an example using the country data as a collection:
Codesandbox
import React, { useState } from "react";
const countriesData = [
{
name: "Germany",
states: ["Duesseldorf", "Leinfelden-Echterdingen", "Eschborn"]
},
{
name: "India",
states: ["Delhi", "Kolkata", "Mumbai", "Bangalore"]
},
{
name: "France",
states: ["Auvergne", "Bretagne", "Corse", "Centre"]
}
];
function Form() {
const [{ country, state }, setData] = useState({
country: "Germany",
state: ""
});
const countries = countriesData.map((country) => (
<option key={country.name} value={country.name}>
{country.name}
</option>
));
const states = countriesData.find(item => item.name === country)?.states.map((state) => (
<option key={state} value={state}>
{state}
</option>
));
function handleCountryChange(event) {
setData(data => ({ state: '', country: event.target.value }));
}
function handleStateChange(event) {
setData(data => ({ ...data, state: event.target.value }));
}
return (
<form onSubmit={() => console.log("Submitted")}>
<div>
<select value={country} onChange={handleCountryChange}>
{countries}
</select>
</div>
<div>
<select value={state} onChange={handleStateChange}>
{states}
</select>
</div>
<input type="submit" />
</form>
);
}
export default Form;
I am new to react, I am working on a project. I need little help regarding the filtering of the data. There are two filters one-: State and Location. The location is dependent on State, which is working fine, but after that, I want to filter my card component i.e VideoCategory card basis on above-selected value. There are three levels of filtering and for the third level, the filtering is not working.
Please, anyone, help me out, as I am trying to fix this issue from last two days.
import React, { useState, useEffect } from "react";
import Menu from "../common/Menu";
import { Form, Container, Row, Col } from "react-bootstrap";
import {
getVideosBasedOnCategories,
getAllStates,
} from "./helper/userApiCalls";
import VideoCard from "../common/VideoCard";
const CategoryVideos = ({ match }) => {
const [videoCategory, setVideoCategory] = useState([]);
const [selectState, setSelectState] = useState([]);
const [selectLocation, setSelectLocation] = useState([]);
let location="";
const preload = (categoryId) => {
getVideosBasedOnCategories(categoryId).then((data) => {
if (data.error || !data) {
console.log(data.error);
return <h1>No Data to Show Now</h1>;
} else {
setVideoCategory(...videoCategory, data);
}
});
};
//Intial Loading
useEffect(() => {
preload(match.params.categoryId);
getAllStateForSelect();
}, []);
//getting data for first Select Component from API
const getAllStateForSelect = () => {
getAllStates().then((data) => {
console.log(data);
if (data.error) {
return console.log(data.error);
} else {
setSelectState(...selectState, data);
}
});
};
const handleChange = (event) => {
console.log(event.target.value);
setSelectLocation(event.target.value);
};
const onLocationChange=(event)=>{
location = event
console.log(location)
}
//Storing Location into Option for Second Select
const onSplit = (x) => {
var arr = [];
for (let i = 0; i < x.citynames.length; i++) {
arr.push(
<option key={i} value={x.citynames[i]}>
{x.citynames[i]}
</option>
);
}
return arr;
};
return (
<div>
<Menu />
<Container style={{ marginTop: "200px" }}>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select State</Form.Label>
<Form.Control
as="select"
onChange={handleChange.bind(selectState[0])}
custom
>
{selectState.map((data, index) => (
<option name="setmap" key={index} value={data._id}>
{data.statename}
</option>
))}
</Form.Control>
</Form.Group>
</Form>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select Location</Form.Label>
<Form.Control
as="select"
onChange={(e) => onLocationChange(e.target.value)}
custom
>
{selectState
.filter((selectState) => selectState._id.includes(selectLocation))
.map((e) => onSplit(e))}
</Form.Control>
</Form.Group>
</Form>
<Row>
{videoCategory.filter(videocard=>videocard.location.includes(location.toString()))
.map((videocard) => {
return (
<Col lg={4} xs={12} md={12} className="py-3 px-3">
<VideoCard videoCategory={videocard}
/>
</Col>
);
})}
</Row>
</Container>
</div>
);
};
export default CategoryVideos;
VideoCategory File
import React, { Fragment,useEffect,useState } from "react";
import { Card, Button, Container, Row, Col } from "react-bootstrap";
import {getStateById} from '../user/helper/userApiCalls'
const VideoCard = ({ videoCategory }) => {
const [state,setState] = useState("");
const [city,setCity] = useState("");
//const { name, link, description } = videoCategory;
const getStateByFromVideoId = (stateId)=>{
getStateById(stateId).then((data)=>{
console.log(data)
if(data.error){
return console.log(data.error)
}
else{
setState(data.statename)
}
})
}
useEffect(() => {
getStateByFromVideoId(videoCategory.state);
}, []);
return (
<Container fluid>
<iframe
src={videoCategory.link}
width="300px"
height="300px"
id="myId"
className="myClassname"
display="initial"
position="relative"
allowfullscreen="allowfullscreen"
></iframe>
<Card style={{ width: "300px", }}>
<Card.Body>
<Card.Title>{videoCategory.name}</Card.Title>
<Card.Text>{videoCategory.description}</Card.Text>
<Card.Text>{state}</Card.Text>
<Card.Text>{videoCategory.location}</Card.Text>
<Button variant="primary">Go somewhere</Button>
</Card.Body>
</Card>
</Container>
);
};
export default VideoCard;
**UPDATE**
The API structure for State, which will reflect in card, the location is hardcoded, it doesn't have any id.
{
"_id": "5eb59d177693b6f99404f4c6",
"link": "https://www.youtube.com/embed/NEIwl93Yr8o",
"description": "LifeStyle",
"name": "Testing",
"state": "5eb564ec7693b6f99404f4c5",
"category": "5ead7555fb5c440f458e625b",
"location": "Kondapur",
"createdAt": "2020-05-08T17:55:35.731Z",
"updatedAt": "2020-05-08T18:28:43.635Z",
"__v": 0
},
** The Filter of State and location(citynames) is :**
{
"citynames": [
"ABC",
"EFG"
],
"_id": "5eb2ad8b554215be68773cf1",
"statename": "Madras",
"createdAt": "2020-05-06T12:28:59.149Z",
"updatedAt": "2020-05-06T12:28:59.149Z",
"__v": 0
},
Here's i change some code, but there's also some notes i need to confirm first
import React, { useState, useEffect } from "react";
import Menu from "../common/Menu";
import { Form, Container, Row, Col } from "react-bootstrap";
import {
getVideosBasedOnCategories,
getAllStates,
} from "./helper/userApiCalls";
import VideoCard from "../common/VideoCard";
const CategoryVideos = ({ match }) => {
const [videoCategory, setVideoCategory] = useState([]);
const [apiStates, setApiStates] = useState([])
const [selectedState, setSelectedState] = useState(null);
// Change default value to '' i think
const [selectedLocation, setSelectedLocation] = useState(null);
const preload = (categoryId) => {
getVideosBasedOnCategories(categoryId).then((data) => {
if (data.error || !data) {
console.log(data.error);
return <h1>No Data to Show Now</h1>;
} else {
setVideoCategory(...videoCategory, data);
}
});
};
//Intial Loading
useEffect(() => {
preload(match.params.categoryId);
getAllStateForSelect();
}, []);
//getting data for first Select Component from API
const getAllStateForSelect = () => {
getAllStates().then((data) => {
console.log(data);
if (data.error) {
return console.log(data.error);
} else {
setApiStates(...apiStates, data);
// remove this line
// setSelectedLocation(data[0])
}
});
};
const onStateChange = event => {
setSelectedState(event.target.value)
}
const onLocationChange= event => {
setSelectedLocation(event.target.value);
}
//Storing Location into Option for Second Select
const onSplit = (x) => {
var arr = [];
for (let i = 0; i < x.citynames.length; i++) {
arr.push(
<option key={i} value={x.citynames[i]}>
{x.citynames[i]}
</option>
);
}
return arr;
};
return (
<div>
<Menu />
<Container style={{ marginTop: "200px" }}>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select State</Form.Label>
<Form.Control
as="select"
onChange={onStateChange}
custom
>
{apiStates.map((data, index) => (
<option name="setmap" key={index} value={data._id}>
{data.statename}
</option>
))}
</Form.Control>
</Form.Group>
</Form>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select Location</Form.Label>
<Form.Control
as="select"
onChange={onLocationChange}
custom
>
{apiStates
.filter(apiState => apiState._id === selectedState)
.map(value => onSplit(value))}
</Form.Control>
</Form.Group>
</Form>
<Row>
// I'm curious about the location, is it array of string ?
{videoCategory.filter(videocard => videocard.location === selectedLocation))
.map((videocard) => {
return (
<Col lg={4} xs={12} md={12} className="py-3 px-3">
<VideoCard videoCategory={videocard} />
</Col>
);
})}
</Row>
</Container>
</div>
);
};
export default CategoryVideos;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
When I make a selection from the dropdown I saved the selected value to type then when I click the button I add an object to drums, I map over thee drums and based on the type I want to render the component with the same name.
Sandbox here
import React, { useState } from "react";
import uuid from "react-uuid";
import "./styles.css";
const Snare = () => {
return <div>Snare</div>;
};
const Gong = () => {
return <div>Gong</div>;
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => (
<Drum.type /> // Why cant I use `.type`?
))}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
In your case Drum.type is not a component but a string, you need to maintain a map of the string to component and then render it
const map = {
Snare: Snare,
Gong: Gong
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => {
const Component = map[Drum.type];
return <Component key={index}/>;
})}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([
...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
Working demo
That's because the type is a string.
You could create a mapping to solve this and use React.createElement().
Something like:
const mapping = {
'Snare': Snare,
'Gong': Gong
}
{ drums.map(({ type }, index) => (
React.createElement(mapping[type], { key: index })
))
}