Map Across an Array of Strings to Generate 'Select' Options (React/Node) - javascript

I have these two select bars:
When I select an option from the first bar, I have it set up to populate this object like so:
I am trying to make it so that the options available in the second select bar populated as the contents of the needed_skills array.
So in this example, the options in the second select bar would be: "Proposal Writing", "The McKinsey 7s Framework" etc
My attempt was this:
const createInputs = () => {
return values.skills_required.map((skill, idx) => {
return (
<div className="input-group">
<select
value={skill} placeholder="Enter a skill"
onChange={e => updateSkill(e, idx)}
className="form-control">
<option>Select an option...</option>
{values.category2.needed_skills && values.category2.needed_skills.map((c, i) => (
<option
key = {i}
value={JSON.stringify(c)}>
{JSON.stringify(c)}
</option>
))}
</select>
<div className="input-group-append">
<button
className="btn btn-outline-danger mb-3"
type="button"
id="button-addon2"
onClick={() => removeSkill(idx)}>x
</button>
</div>
</div>
);
});
};
But nothing, even though when I console.log(values.category2.needed_skills) and get the array, I cant seem to map across it and return it as options in the 'select' dropdown.
Any advice/help is greatly appreciated.
#malfunction is correct, when I console log the index and elements of the loop I get nothing. How can I loop through this array of strings? My data structure is exactly like this:
const [values, setValues] = useState({
name: '',
description: '',
pitch_price: '',
categories: [],
category: '',
quantity: '',
applications: '',
business_name: '',
skills_required: [''],
category2: {
name: "",
needed_skills: [""]
},
photo: '',
created_by: '',
loading: false,
error: '',
createdProject: '',
redirectToProfile: false
});
The category2 part is where I am keeping the data. Also, I know that the data is actually there because when I console log my values.category2 object after selecting a Category - I get this:

Have you tried adding a return. i.e.
return <option key = {i}
value={JSON.stringify(c)}>
{JSON.stringify(c)}
</option>
Also, not sure if JSON.stringify is required there. It's probably not hurting though.
Ok, that wasn't it ^^^
Without knowing the structure of your data I got this working in a new create-react-app without really changing your code:
import logo from './logo.svg';
import './App.css';
import { jsxFragment } from '#babel/types';
const values = {
skills_required: [
"skill_1",
"skill_2"
],
category2: {
needed_skills: [ "Proposal Writing", "The McKinsey Framwork", "etc"],
some_other_stuff: {},
a_date: "2019-09-30"
}
}
const updateSkill = (e, idx) => {
console.log("e: ", e);
console.log("e: ", idx);
}
const removeSkill = (idx) => {
console.log("e: ", idx);
}
const createInputs = () => {
return values.skills_required.map((skill, idx) => {
return (
<div className="input-group">
<select
value={skill} placeholder="Enter a skill"
onChange={e => updateSkill(e, idx)}
className="form-control">
<option>Select an option...</option>
{values.category2.needed_skills && values.category2.needed_skills.map((c, i) => (
<option
key = {i}
value={JSON.stringify(c)}>
{JSON.stringify(c)}
</option>
))}
</select>
<div className="input-group-append">
<button
className="btn btn-outline-danger mb-3"
type="button"
id="button-addon2"
onClick={() => removeSkill(idx)}>x
</button>
</div>
</div>
);
});
};
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<jsxFragment>
{ createInputs()}
</jsxFragment>
</header>
</div>
);
}
export default App;
Is your data structure similar to that? I'd say that the issue might be with the data structure possibly looping through and getting nothing.
If you're not sure you can always add a console.log in here:
<option>Select an option...</option>
{ values.category2.needed_skills
&& values.category2.needed_skills.map((c, i) => {
console.log("c", c);
console.log("i", i);
return ( <option
key = {i}
value={JSON.stringify(c)}>
{JSON.stringify(c)}
</option>)
}
)
}
The initial return didn't work because the curly braces weren't there.
The only other thing I can think of is making your array it's own variable. i.e. let needed_skills = values.category2.needed_skills;
I hope one of those things helps.

Related

How do I simplify this piece of React code? (Progressive input form)

The purpose of the sample of React code below is to create progressive form which only displays one question to begin with and then reveals the next question when the user clicks a button (while keeping the previous questions visible). The actual piece of code I've written contains 12 questions - the number, content and order of which could change over time. Currently the code works but it's very long and difficult to update and so I'm sure there must be a better more dynamic way of doing this. One way I've experimented with is having a sperate .js file containing an array of variables for each question but I've not been able to keep the "progressive" aspect of the form working with is method.
Any thoughts or advice would be greatly appreciated!
import React, { useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import classes from './InputForm2.module.css';
const InputForm2 = () => {
const dateOfBirthInputRef = useRef();
const membershipTypeInputRef = useRef();
const dateOfJoiningInputRef = useRef();
let navigate = useNavigate();
function routeChange() {
let path = '/results';
navigate(path);
}
function dataHandler() {
const enteredDateofBirth = dateOfBirthInputRef.current.value;
const enteredMembershipType = membershipTypeInputRef.current.value;
const enteredDateOfJoining = dateOfJoiningInputRef.current.value;
const inputData = {
dateOfBirth: enteredDateofBirth,
membershipType: membershipTypeRef,
dateOfJoining: enteredDateOfJoining,
};
console.log(inputData);
}
function submitHandler(event) {
event.preventDefault();
dataHandler();
routeChange();
}
const [q2IsOpen, setQ2IsOpen] = useState(false);
const [q3IsOpen, setQ3IsOpen] = useState(false);
const btn1ClickHandler = (event) => {
event.preventDefault();
setQ2IsOpen(true);
};
const btn2ClickHandler = (event) => {
event.preventDefault();
setQ3IsOpen(true);
};
const btn3ClickHandler = (event) => {
event.preventDefault();
setQ4IsOpen(true);
};
return (
<div className={classes.formbox}>
<form>
<section className={`${classes.active}`}>
<div className={classes.textbox}>
<b>Question 1</b>
<p>What is your date of birth?</p>
<input
className={classes.input}
type="date"
required
ref="dateOfBirthInputRef"
></input>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={btn1ClickHandler}>
Next
</button>
</div>
</section>
<section className={`${q2IsOpen ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>Question 2</b>
<p>
What is your membershiptype?
</p>
<select
className={classes.input}
required
ref="membershipTypeInputRef"
>
<option></option>
<option value="Platinum">Platinum</option>
<option value="Gold">Gold</option>
<option value="Basic">Basic</option>
</select>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={btn2ClickHandler}>
Next
</button>
</div>
</section>
<section className={`${q3IsOpen ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>Question 3</b>
<p>What date did you start your membership?</p>
<input
className={classes.input}
type="date"
required
ref="dateOfJoiningInputRef"
></input>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={btn3ClickHandler}>
Next
</button>
</div>
</section>
<div className={classes.btn__container}>
<button
className={`${classes.submitbutton} ${
q4IsOpen ? classes.active : classes.inactive
}`}
onClick={submitHandler}
>
Calculate
</button>
</div>
</form>
</div>
);
};
export default InputForm2;
This isn't fully working code, just an idea of how you would structure this. As you suggested yourself, making an array of question objects is a good idea. You could also break out the question structure into it's own component:
const Question = ({thisIndex, currentIndex, title, text, ref, handler}) => {
return(
<section className={`${thisIndex >= currentIndex-1 ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>{title}</b>
<p>{text}</p>
<input
className={classes.input}
type="date"
required
ref=ref
></input>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={handler}>
Next
</button>
</div>
</section>
)
}
The props input to Question component can be mapped from a QUESTIONS object array, assuming an object structure like {title: 'Question 1', text: 'Why are we here?', //and so on}.
I would keep track of visibility simply by keeping track of the index of the last answered question in the main form, and passing that in to each question in the array - that way it only becomes visible when the question before is answered.
const InputForm2 = () => {
const [currentIndex, setCurrentIndex] = useState(0);
//all your other code
const handler = (i) => {
setCurrentIndex(i)
//do more stuff
}
const questions = QUESTIONS.map((question, i) => {
return(
<Question key={i}
thisIndex=i currentIndex={currentIndex}
title={question.title} text={question.text}
handler={handler(i)}
//and so on
/>
)
});
return (
<div>{questions}</div>
)
}
Then, in your form you can return the questions array of components.
EDIT
In a similar way, since you have different types of input / select for different questions, you can make separate components and pass those as props to you question.
const QSelector = ({options}) => {
const optionElements = options.map((option, i) => {
return(
<option //get your data from option element
)
})
return(
<selector>
{options}
</selector>
)
}
Obviously for three different input types and three questions, this isn't super useful but as you go in, if you have 5 selector q's, 5 inputs, you'll find you can reuse a lot of structures, just passing in any data that is different.
Below, I'm updating the Question component to receive this as an 'answer' prop.
const Question = ({thisIndex, currentIndex, title, text, ref, handler, answer}) => {
return(
<section className={`${thisIndex >= currentIndex-1 ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>{title}</b>
<p>{text}</p>
{answer}
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={handler}>
Next
</button>
</div>
</section>
)
}

How to filter an array on click in react?

So, basically, I'm making a website where you can search for Minecraft hacked clients. There is a search bar on the website, but you have to search in exact terms (different topic lol). Basically on the click of a button (search button) I then want to filter using the search term, (not automatically as I have it now) I've been searching but cant find a way to do it.
Code ->
import CountUp from 'react-countup';
import { Link } from 'react-router-dom';
import '../App.css';
/*Components ->*/
import Client from '../components/client.js'
function App() {
const [search, setSearch] = React.useState({
searchname: ''
});
**Array containing client data ->** const [client, setClient] = React.useState([
{ safestatus: 'safe-status-green', safe: 'Safe (Verified)', name: 'Impact', price: 'Free', mcversion: '1.11.2 - 1.16.5', type: 'Injected', compat: 'None (Stand alone client)', desc: 'The Impact client is an advanced utility mod for Minecraft, it is packaged with Baritone and includes a large number of useful mods.', screen: 'https://impactclient.net/', download: 'https://impactclient.net/'},
{ safestatus: 'safe-status-green', safe: 'Safe (Verified)', name: 'Future', price: '15€', mcversion: '1.8.9 - 1.14.4', type: 'Injected', compat: 'None (Stand alone client)', desc: 'Vanilla, OptiFine, Forge and Liteloader support, Easy to use account manager, Auto-reconnect mod.', screen: 'https://www.futureclient.net/', download: 'https://www.futureclient.net/'}
]);
const updateSearch = (event) => {
event.persist();
setSearch((prev) => ({
...prev,
[event.target.name]: event.target.value
}));
};
return (
<body>
<div className='header'><Link to='/'>Hacked Hub</Link><div className='header-links'><Link to='/about-us'>About Us</Link> | <Link to='/faq'>FAQ</Link></div></div>
<div className='counter'><CountUp end={16} duration={0.5}></CountUp>+</div>
<div className='counter-desc'><div className='code'>hacked clients</div> and counting...</div>
<div className='nxt-tab'>
<input name='searchname' value={search.searchname} onChange={updateSearch} placeholder='Search'></input>
<select name='price'>
<option value='Free'>Free</option>
<option value='Paid'>Paid</option>
</select>
<select name='safe'>
<option value='Safe'>Safe</option>
<option value='Probably Safe'>Probably Safe</option>
<option value='Not Safe'>Not Safe (BE CAREFUL!)</option>
</select>
<select name='mcver'>
<option value='1.8.9'>1.8.9</option>
<option value='1.12.2'>1.12.2</option>
<option value='1.16.5'>1.16.5</option>
<option value='1.17+'>1.17+</option>
</select>
<select name='type'>
<option value='Main'>Injected</option>
<option value='Side'>Mod</option>
</select>
<select name='compatibility'>
<option value='With Most Other Clients'>With Most Other Clients</option>
<option value='Stand Alone'>Stand Alone</option>
</select>
<div className='client-warning'><div className='safe-status-red'><div className='code'>⚠ WARNING ⚠</div></div> Only download clients you know are <div className='code'>100%</div> safe! If we do find a client that is a <div className='code'>rat / virus / BTC miner</div> we will tag it as <div className='safe-status-red'><div className='code'>UNSAFE IMMEDIATELY</div></div>. The saftey warnings for clients are <div className='code'>MERE RECCOMENDATIONS</div>, please do thorough research before downloading any hacked client that you are not <div className='code'>100%</div> sure is safe. This page is also in <div className='code'>development</div>, meaning features are prone to break! So be careful!!!</div>
<div className='code'>Sponsored clients</div>
<h1>None XD</h1>
<div className='code'>Submitted clients</div>
{client
**Filtering the array then mapping it -> (This i want to do onClick)** .filter((client) => client.name === search.searchname)
.map((client, index) => {
return (
<Client
safestatus={client.safestatus}
safe={client.safe}
name={client.name}
price={client.price}
mcversion={client.mcversion}
type={client.type}
compat={client.compat}
desc={client.desc}
screen={client.screen}
download={client.download}
/>
);
})}
</div>
</body>
);
}
export default App;
Anyone know how I could do this onClick?
Use two pieces of state; one to track the value in your text field and the other to store the search term for filtering. Only set the latter when you click your button
const [ searchValue, setSearchValue ] = React.useState("")
const [ searchTerm, setSearchTerm ] = React.useState("")
const filteredClient = React.useMemo(() => {
if (searchTerm.length > 0) {
return client.filter(({ name }) => name === searchTerm)
}
return client
}, [ searchTerm, client ])
and in your JSX
<input
name="searchname"
placeholder="Search"
value={searchValue}
onChange={e => setSearchValue(e.target.value)}
/>
<!-- snip -->
<button
type="button"
onClick={() => setSearchTerm(searchValue)}
>Search</button>
<!-- snip -->
{filteredClient.map((client, index) => (
<Client .../>
))}

How to get data from cloud firestore then filter it, map it then return it?

I am trying to get data from the firestore and then filter it then map it like so:
return Inventory
.filter(x =>x["sub_category"] === item && x["category"] === category)
.map(({id, item, price, quantity, image})=>{
//sets the default value for the item with that specific id
const defVal = parseInt((selectedQty.id === id)?selectedQty.qty:0)
return (
<React.Fragment key={uuid()}>
<div className="card">
<img src={image()} alt={item} />
<p>{item}</p>
<div className="innerBox">
<div className="dropdown">
<label htmlFor="quantity">Qty:</label>
<select id="quantity" defaultValue={defVal===0?1:defVal} onChange={e => {
e.preventDefault()
setSelectedQty({id, qty: parseInt(e.target.value)})
}}>
{
Array(quantity).fill().map((_, i)=> {
if(i===0){
return <option key={uuid()} value={0}>-</option>
}else{
return <option key={uuid()} value={i} >{i}</option>
}
})
}
</select>
</div>
<b>$ {price}</b>
</div>
<button type="submit" onClick={()=> {
addToCart(id, item, parseInt(finalQty), price, image(), parseInt(finalQty)*parseFloat(price))
setSelectedQty({id:null, qty: 0})
}}>Add to Cart</button>
</div>
</React.Fragment>
)})
Currently I am using Inventory Array but I want to switch to firestore but I have no clue how to do it. I am aware of the step db.collection().get().then(etc...) but i don't know how to map it to return it inside the Then
When you fetch data from cloud firestore, it returns a document / collection snapshot.
Document:
db.collection("colName").doc("docID").get().then(docSnapShot=>{
const docID = docSnapShot.id;
const docData = docSnapShot.data();
})
Collection:
db.collection("colName").get().then(colSnapShot=>{
const isEmpty = colSnapShot.empty;
const docsData = colSnapShot.docs.forEach(docSnapShot=>{
return docSnapShot.data();
})
})
I believe your solution will look something like this:
let arrOfDocs = await db.collection("colName").get().then(colSnapShot=>{
return colSnapShot.docs.map(docSnapShot=>{
return docSnapShot.data();
})
})
Note that to listen to live updates, replace get().then(snapshot=>{}) with onSnapshot(snapshot=>{})

React modal custom component not showing the correct data

I have built this modal component using react hooks. However the data that the modal shows when it pops up its incorrect (it always shows the name property for last element in the array).
//Modal.js
import ReactDOM from 'react-dom';
const Modal = ({ isShowing, hide, home_team }) => {return isShowing ? ReactDOM.createPortal(
<React.Fragment>
<div className="modal-overlay"/>
<div className="modal-wrapper">
<div className="modal">
<div className="modal-header">
<a>Home team: {home_team}</a>
<button type="button" className="modal-close-button" onClick={hide}>
</button>
</div>
</div>
</div>
</React.Fragment>, document.body
) : null;}
export default Modal;
// Main component
const League = ({ league, matches }) =>{
const {isShowing, toggle} = useModal();
return (
<Fragment>
<h2>{league}</h2>
{
matches.map((
{
match_id,
country_id,
home_team
}
) =>
{
return (
<div>
<p>{match_id}</p>
<button className="button-default" onClick={toggle}>Show Modal</button>
<a>{home_team}</a>
<Modal
isShowing={isShowing}
hide={toggle}
home_team={home_team}
/>
<p>{home_team}</p>
</div>
)
})
}
</Fragment>
)};
This is what matches data set looks like:
[{
match_id: "269568",
country_id:"22",
home_team: "Real Kings"
},
{
match_id: "269569",
country_id:"22",
home_team: "Steenberg United"
},
{
match_id: "269570",
country_id:"22",
home_team: "JDR Stars "
},
{
match_id: "269571",
country_id:"22",
home_team: "Pretoria U"
},
]
I am not sure whats going on because the data seems to be passed fine.
<p>{home_team}</p>
in the main component is showing everytime the expected property, however the Modal always shows the last home_team item in the array (e.g.Pretoria U)
you need to call useModal inside of the map function. otherwise you will open on toggle all Modals and the last one overlaps the others
const HomeTeam = ({ match_id, country_id, home_team }) => {
const {isShowing, toggle} = useModal();
return (
<div>
<p>{match_id}</p>
<button className="button-default" onClick={toggle}>Show Modal</button>
<a>{home_team}</a>
<Modal
isShowing={isShowing}
hide={toggle}
home_team={home_team}
/>
<p>{home_team}</p>
</div>
)
}
const League = ({ league, matches }) => (
<Fragment>
<h2>{league}</h2>
{ matches.map((match) => <Hometeam {...match} /> }
</Fragment>
);

Redux-form not formatting post data and [object Object] issue

I have two problems that are a result of each other. I populate two fields with initialValue data, I can then push another field to the array. The issue came about when I tried to amend the initialValue structure from:
initialValues: {
rockSingers: [ "Axl Rose", "Brian Johnson"]
}
to:
initialValues: {
rockSingers: [{ singer: "Axl Rose" }, { singer: "Brian Johnson" }]
}
The first problem is that the field now returns [object Object]. Upon submitting the form the correct json format is displayed until I come on to my 2nd issue... when adding a new value that does not format the same as the initialValue data - e.g.
{
"rockSingers": [
{
"singer": "Axl Rose"
},
{
"singer": "Brian Johnson"
},
"Tom Rudge"
]
}
Here is the codesandbox - https://codesandbox.io/s/8kzw0pw408
Modify renderRockSingers so that you are grabbing the object, not a string.
const renderRockSingers = ({ fields }) => (
<div>
<h3>Rock Singers:</h3>
{fields.map((rockSinger) => (
<div>
<Field name={`${rockSinger}.singer`} key="index" component="input" />
</div>
))}
<button type="button" onClick={() => fields.push()}>
Add more
</button>
</div>
);
More on the FieldArray component here: fieldarrays
Try this:
const renderRockSingers = ({ fields }) => (
<div>
<h3>Rock Singers:</h3>
{fields.map((rockSinger, index) => (
<div>
<Field
name={rockSinger}
format={value => value.singer}
parse={value => ({ singer: value })}
key={index}
component="input"
/>
</div>
))}
<button type="button" onClick={() => fields.push({ singer: '' })}>
Add more
</button>
</div>
);
<Field
name={rockSinger}
key={index}
component="input"
format={(value, name) => (value !== undefined ? value.singer : "")}
normalize={value => ({ singer: value })}
/>
Code Sandbox: https://codesandbox.io/s/7m1p9600y0

Categories

Resources