React Graphql, create select menu from comma separated string - reactjs

I'm creating a simple React app with graphql, I'm using strapi as the server.
In Strapi I have a Vechicle collection type with a name field and a make field.
In the name field I have car and in make I have volvo, saab, audi, ford
I'd simple like to display car and the makes in a select menu.
The query to get the data
import gql from 'graphql-tag';
export const GET_ALL_CARS = gql `
query Cars{
cars{
_id
name
makes
}
}
`
The react page
import React from 'react';
import { GET_ALL_CARS } from './queries';
import {Vechicles} from './generated/Vechicles'
import { useQuery } from "#apollo/react-hooks";
const App:React.FC = () => {
const {data, loading} = useQuery<Vechicles>(GET_ALL_CARS, {})
if(loading) return <div>Loading</div>
if(!data) return <div>No Data</div>
return (
<div className="App">
<h1>Vehicles</h1>
{
data && data.vechicles && data.vechicles.map(vechicle => (
<div>
//car displays ok
{vechicle?.name}
//trying to create an array here from the string of makes
const makes_arr = {car?.makes ?? ''}.split(',')
<select>
{
makes_arr.map(make = > {
return(
<option>{make}</option>
)
})
}
</select>
</div>
))
}
</div>
);
}
export default App;
How do I simple display the comma seperated list in a select menu.

You can't create an array in this place because it's a part of the JSX. Try to create array inline in your select.
I have removed optional chaining because it's not supported by the editor.
function App() {
const data = {
vehicles: [
{
name: "car",
makes: "volvo, saab, audi, ford"
},
{
name: "car2",
makes: "volvo, saab, audi, ford"
}
]
};
return (
<div className="App">
<h1>Vehicles</h1>
{data &&
data.vehicles &&
data.vehicles.map(vehicle => (
<div>
{vehicle.name}
<select>
{vehicle.makes
.split(",")
.map(make => <option>{make.trim()}</option>)}
</select>
</div>
))}
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<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>
<div id="root" />

Related

Using react-icons that are imported from another file

In a react app (created by vite), I need a list of categories to create a navigation menu. I have created this list in another file with .js extension. In the form of an array of objects:
// constants.js
import { MdHomeIcon, MdCodeIcon } from "react-icons/md";
export const categories = [
{ name: "New", icon: <MdHomeIcon /> },
{ name: "Coding", icon: <MdCodeIcon /> },
{ name: "ReactJS", icon: <MdCodeIcon /> },
];
react-icons package is installed.
My problem is this: before doing anything, react app return a syntax error in console: Uncaught SyntaxError: expected expression, got '<' in constants.js.
after solving the problem: In Sidebar.jsx, I want to map through categories to build a list with names and icons.:
// Sidebar.jsx
import { categories } from "./constants.js";
export const Sidebar = () => (
<div>
{categories.map((category) => (
<div>
<span>{category.icon}</span>
<span>{category.name}</span>
</div>
))}
</div>
);
You can't include JSX inside a js file but your icons are being imported from "react-icons/md" and you use them as JSX elements.
If you want to use these icons inside of constants.js try renaming it to constants.jsx
There is an error on the map loop.
You can't return 2 children in the map loop.
One way to solve it is using React Fragment to wrap up those components.
https://reactjs.org/docs/fragments.html
// Sidebar.jsx
import { categories } from "./constants.js";
export const Sidebar = () => (
<div>
{categories.map((category) => (
<React.Fragment>
<span>{category.icon}</span>
<span>{category.name}</span>
</React.Fragment>
))}
</div>
);
Another thing, you must be alert to the key prop inside lists and loops.
https://reactjs.org/docs/lists-and-keys.html
Third thing. Attach the Icon call as the value of your obj, and then call this as a react component
// Sidebar.jsx
import { categories } from "./constants.js";
export const Sidebar = () => (
<div>
{categories.map((category) => {
const IconComponent = category.icon;
return (
<> // another way to use fragment
<span>
<IconComponent />
</span>
<span>{category.name}</span>
</>
)}
)}
</div>
);
Last but not least, there is a syntax error in your code. There is a } missing in the final line of the loop
//data categories.js
export const categories = [
{ name: "New", icon: "Home" },
{ name: "Coding", icon: "Code" },
{ name: "ReactJS", icon: "Code" },
];
//sidebar.js
import { categories } from "./constants.js";
import { MdHomeIcon, MdCodeIcon } from "react-icons/md";
export const Sidebar = () => (
<div>
{categories && categories.map((category, i) => (
<div key = {i}>
{category.icon === "Code"? <MdCodeIcon/> : <MdHomeIcon/>}
<span>{category.name}</span>
<div/>
))}
</div>
);
you can edit the code as needed

React not selecting corresponding component

This is a chat application and crated with React.js and StreamChat API. Need to function according to the team or message types. Now it only functions as a message, team chat also message cannot create channels, etc. code for the team also functions well. the problem is the code does not select the right component according to the section. Need your help to select the correct component.
import React, {useState} from 'react'
import { useChatContext } from 'stream-chat-react';
import { initialState } from 'stream-chat-react/dist/components/Channel/channelState';
import {UserList, TeamChannelList} from './';
import { CloseCreateChannel } from './assets';
const ChannelNameInput =({channelName ='', setChannelName}) => {
const handleChange =(event) =>{
event.preventDefault();
setChannelName(event.target.value);
}
return(
<div className='channel-name-input__wrapper'>
<p>Name</p>
<input value={channelName} onChange={handleChange} placeholder="channel-name" />
<p>Add Members</p>
</div>
)
}
const CreateChannel = ({createType, setIsCreating}) => {
const {client, setActiveChannel } = useChatContext();
const [selectedUsers, setSelectedUsers] = useState([client.userID || '']);
const [channelName, setChannelName] = useState('');
return (
<div className='create-channel__container'>
<div className='create-channel__header'>
<p>{createType === 'team' ? 'Create a New Channel' : 'Send a Direct Message'}</p>
<CloseCreateChannel setIsCreating={setIsCreating}/>
</div>
{createType === 'team' && <ChannelNameInput channelName={channelName} setChannelName={setChannelName}/>}
<UserList setSelectedUsers={setSelectedUsers}/>
<div className='create-channel__button-wrapper'>
<p>{createType==="team"? "ceate channel": "create group"}</p>
</div>
</div>
)
}
export default CreateChannel

How to use a useState function in another component with props?

I write a React.js note web application where a user can add up to 10 notes.
I use map() to iterate the array of notes, and a useState(1) hook to update its count (the default number of notes is 1), so I would like to do something like this:
{[...Array(noteCount)].map((_, i) => <Note onUpdateNoteCount={() =>setNoteCount(n => n - 1)} key={i} />)}
The thing is that the Note() component is inside a Main() component which is in the App() component, so I want to get the needed values as props of App(), and than use them in Note(), but can not figure out how and where to put it.
Thanks!
App.js
import React from 'react';
import Header from './Header';
import Main from './Main';
function App () {
const [noteCount, setNoteCount] = React.useState(1);
function multiplyNoteComponent () {
if (noteCount < 20) {
setNoteCount(n => n + 1)
}
else {
alert('too many notes. remove or combine some of them together!')
}
}
return (
<div>
<Header/>
{[...Array(noteCount)].map((_, i) => <Main onUpdateNoteCount={() =>setNoteCount(n => n - 1)} key={i} />)}
<button
style={{left: '5%'}}
id='addNoteBtn'
onClick={multiplyNoteComponent}
title='Add a note'
>
+
</button>
</div>
);
}
export default App;
Main.js
import React from 'react';
import Note from './Note';
function Main () {
return (
<main>
your notes are:
<Note/>
</main>
)
}
export default Main;
Note.js
import React from 'react';
function Note () {
return (
<div> <button title='delete note' onClick={}>X</delete>
<li>
<input type='text'/>
</li>
</div>
)
}
export default Note
Edit: the reason I think I need the setNoteCount() function to be used in the Note() component, is for the count down when a note is being deleted (every note has its own delete button).
I would recommend this architecture of the your App.
Store the Notes array at the App level.
Add a note using NoteInput which adds a notes to your Notes array.
Map your Notes using the Note component which takes onDelete as a prop from App level.
Your App component should be responsible for storing and delete a note from the state.
In your example, notesCount is meant to a derivative state.
i.e it could be derived simply from the Notes array (notes.length).
So, rather than storing notesCount, I recommend storing notes and deriving count from it.
You could see the working example here :- https://stackblitz.com/edit/react-g19tei
import React from "react";
import "./style.css";
const NOTES_ALLOWED = 10;
export default function App() {
const [notes, setNotes] = React.useState([]);
function addNote(newNote) {
if (notes.length === NOTES_ALLOWED) {
alert(`Only ${NOTES_ALLOWED} notes are allowed to be added`)
} else {
setNotes([...notes, newNote]);
}
}
function handleDelete(deleteNoteIdx) {
const newNotes = [...notes];
// delete the note at the specific index
newNotes.splice(deleteNoteIdx, 1)
setNotes(newNotes);
}
return (
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<p>Your notes are</p>
{notes.map((note, idx) => (
<Note
note={note}
onDelete={() => handleDelete(idx)}
/>
))}
</div>
<NoteInput onAdd={addNote} />
</div>
);
}
function Note({ note, onDelete }) {
return (
<div>
<p>{note}
<button onClick={onDelete}>Delete Note</button>
</p>
</div>
)
}
function NoteInput({ onAdd }) {
const [note, setNote] = React.useState('');
function handleSubmit(e) {
e.preventDefault();
const noteToBeSend = note;
setNote('')
onAdd(noteToBeSend.trim());
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={note}
onChange={e => setNote(e.target.value)}
required
/>
<button type="submit">Add Note</button>
</form>
</div>
)
}

How can I update the this.state.songs to songsList

I cant update the state songs which needs to get values from songsList . How can I update the songs to songsList ? Is it anything to do with the component life cycle ? While running the below code , 'songsList is undefined' error throws up . const songList is in the render .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import SongCards from './components/SongCards/SongCards';
import 'tachyons';
import axios from 'axios';
class App extends Component {
state = {
songs : [],
searchField: '',
entries: []
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({ entries: response.data.feed.entry });
});
}
onSearchChange=(event)=>{
this.setState({songs : songsList})
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}
render(){
const songsList = this.state.entries.map(entries => {
return (
<SongCards
key={entries.id.label}
artist={entries["im:artist"].label}
image={entries["im:image"][2].label}
link={entries.id.label}
price={entries["im:price"].label}
date={entries["im:releaseDate"].label}
title={entries.title.label}
/>
);
});
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
{songsList}
</div>
);
}
}
export default App;
Appreciate all your responses . I made it finally .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import Albums from './components/Albums/Albums';
import Scroll from './components/Scroll/Scroll';
import 'tachyons';
import emoji from 'emoji-dictionary';
import axios from 'axios';
class App extends Component {
state = {
show:false,
songs : [],
searchField: '',
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({songs:response.data.feed.entry });
});
}
itunesPageLoader=()=>{
this.setState({show:false})
}
onSearchChange=(event)=>{
this.setState({searchField : event.target.value})
}
render(){
const filteredSongs = this.state.songs.filter(song =>{
return
song.title.label.toLowerCase().includes(this.state.searchField.toLowerCase())
})
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
<Scroll >
<Albums songs={filteredSongs}/>
</Scroll>
<footer className="pv4 ph3 ph5-m ph6-l red">
<small className="f6 db tc">© 2018 <b className="ttu">Box8 Inc</b>., All
Rights Reserved</small>
<div className="tc mt3">
{`Made with ${emoji.getUnicode("purple_heart")} by Renjith`}
</div>
</footer>
</div>
);
}
}
export default App;
Try this. You are actually assigning songsList to songs using setState but the songsList doesn’t exist in onSearchChange. To push searched value to an array you need to push event.target.value to songs array
Try with below corrected code
onSearchChange=(event)=>{
this.setState(prevState => ({songs : [...prevState.songs, event.target.value]}));
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}
You have mentioned that this.state.entries is an Object.
If this is true, then yo can't perform .map on it as .map is an Array method.
You can however use Object.entries to get an array of [key,value] pairs of this.state.entries.
Object.entries(this.state.entries).map(([key,value]) => ...)
Simple running example:
const object1 = { foo: 'this is foo', baz: "this is baz" };
Object.entries(object1).map(([key,value]) => console.log(`key: ${key}, value: ${value}`));
So i will do something like this:
const IN_PROGRESS = 'IN_PROGRESS';
const SUCCESS = 'SUCCESS';
class App extends Component {
state = {
songs : null,
entries: null,
status: null
};
componentDidMount() {
this.setState({status: IN_PROGRESS});
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then({data} => {
const songs = data.feed.entry;
this.setState({entries: songs});
this.setState({songs});
this.setState({status: SUCCESS});
});
}
onSearchChange = ({target}) => {
const {value} = target;
const songs = this.state.entires.filter(song =>
song.title.toLowerCase().includes(value.toLowerCase())
});
this.setState({songs});
}
render() {
const {status, songs} = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange={this.onSearchChange}/>
{
status === IN_PROGRESS &&
(/* you can insert here some kind of loader which indicates that data is loading*/)
}
{
status === SUCCESS && songs.map(entry => {
const {
id, ['im:artist']: artist, ['im:image']: image,
['im:price']: price, ['im:releaseDate']: date, title
} = entry;
return (
<SongCard
key={id.label}
artist={artist.label}
image={image[2].label}
link={id.label}
price={price.label}
date={date.label}
title={entry.title.label}
/>
)
}
}
{
//Here you can display error message if status === FAILURE
}
</div>
);
}
}
When component did mount, I set status into IN_PROGRESS (if you want some kind of loader to show), and data are beeing fetched - axios.get is asynchronous so remember that when data is fetching then render method is already triggered. When data is loaded then in state I hold two variables, entries which holds unfiltered list of songs, and songs which holds filteres songs.
When search is triggered then I filter entires by searched phrase and set into state this filtered array.
Component renders songCards mapping by filtered songs

How to manipulate style/atributes in React DOM

I have two components. If I hovered over one I'd like to move (by changing style proporties) the other one component.
How can I achieve that?
In pure js it's simply
let elem1 = document.querySelector('.elem1');
let elem2 = document.querySelector('.elem2');
elemt1.addEventListener('mouseover', () => {
elem2.style.right = "200px" //or any other style property
})
So.. in react we can use "ref" and this works if I define static ref
import React, {Component} from 'react';
class MainCanvas extends Component {
onHover(){
console.log(this.refs.mybtntest);
}
render(){
return(
<div>
<h1 onMouseEnter={() => this.onHover()}> Testing</h1>
<button ref="mybtntest">Close</button>
</div>
);
}
}
export default MainCanvas
However in my case I need to each component should has dynamically added "ref" atribute.. so my code looks like below
import React, {Component} from 'react';
class Test extends Component {
onHover(e, dynamicRef){
console.log(dynamicRef); //correct number of ref
console.log(this.refs.dynamicRef); //undefined
console.log(this.refBtnName); //button reference but eachtime is overrided
console.log(this.dynamicRef);//undefinded
}
render(){
const elements = this.props.elements.map( element => {
let refBtnName = element.id + "btn";
return [
<ComponentElement
onHover={(e) => this.onHover(e, refBtnName)}
key={element.id} {...element}
/>,
<button key={element.id*2}
ref={refBtnName => this.refBtnName = refBtnName} //each time he will be overrided :(
className={`${refBtnName} deleteComponentBtn`} >
Close
</button>
]
})
return(
<div>
{elements}
</div>
);
}
}
export default Test
A real goal is that I want to positioning the button relative to the element. I could use div for this purpose as a wrapper but I don't want it.
So I thought to use for example this piece of code
onHover(e, dynamicRef){
Math.trunc(e.target.getBoundingClientRect().right)
dynamicRef.style.right = `${right}px`;
}
If you need dynamic object keys you shouldn't use the dot . and instead use the brackets:
ref={ref=> this[refBtnName] = ref}
Note that i changed the inline parameter to ref instead of refBtnName so you won't get variable names conflicts.
Running example:
class App extends React.Component {
state = {
items: [
{ name: 'John', id: 1 },
{ name: 'Mike', id: 2 },
{ name: 'Jane', id: 3 },
]
}
move = refName => e => {
this[refName].style.right = '-90px';
}
render() {
const { items } = this.state;
return (
<div>
{
items.map(item => {
return (
<div key={item.id} >
<div
ref={ref => { this[item.name] = ref }}
style={{ position: 'relative' }}
>
{item.name}
</div>
<button onClick={this.move(item.name)}>Move {item.name}</button>
</div>
)
})
}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Resources