Material UI multiple Select MenuItem ref error - node.js

Lately, I'm getting without a reason this error from opening a multiple Select in React+Next.js using Material UI:
Error: Argument appears to not be a ReactComponent. Keys: retry
This appears to be an error related to a ref.
It happens on both the Select I do have on that page and both the select on another one.
This error only happens when I try to open a Select which has MenuItems inside; if it doesn't, it just doesn't happen (the select opens but it's, obviously, empty). And the error never happens if I don't open the Select, it probably happens when trying to render the MenuItems.
const FormControlLabel = dynamic(() => import("#material-ui/core/FormControlLabel"));
const InputLabel = dynamic(() => import('#material-ui/core/InputLabel'));
const MenuItem = dynamic(() => import('#material-ui/core/MenuItem'));
/* Then, in render... */
<FormControl id="selected-genres-wrapper">
<InputLabel htmlFor="selected-genres-input">Generi</InputLabel>
<Select
multiple
value={this.state.selectedEdited && this.state.selectedEdited.genres ? this.state.selectedEdited.genres : this.state.selected.genres}
onChange={this.genresSelect}
input={<FilledInput variant="filled" id="selected-genres-input" />}
renderValue={selected => (
<div>
{selected.map(value => (
<Chip key={value} label={value} />
))}
</div>
)}
>
{this.genres.map(name => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
The error itself just doesn't make sense because it's an internal error with no kind of help to debug or prevent it in any way.
I also tried updating Material UI core (I was using 4.6, now I'm using 4.11) but really nothing changed.
Any idea? Thanks in advance.

The Menu.js line referenced in the stacktrace is code where the Menu is cloning the child MenuItem elements. The error is due to your dynamic import of MenuItem.
If you replace the dynamic import with:
import MenuItem from '#material-ui/core/MenuItem';
it should work fine.

Related

Load specific DIV with a react component without reloading the whole page

I have a menu where every menu item is a button and I want to load a specific reactjs component into a specific div without reloading the whole page.
This is the current code, clearly is bad but I don't know where to start fixing it...
...
<Button onClick={this.loadTarget}>
{menuItem.name}
</Button>
...
loadTarget(event) {
document.getElementById("datapanel").innerHTML="abc<TranslationsList />";
}
When I click a menu Item I want to load my div with the value "abc<TranslationsList />". "abc" is displayed but the custom component "TranslationsList" is not and I guess this is normal as the TranslationsList tag is not a HTML tag. But how could I load my component?
I could use links instead of buttons but in this case the question is how could I update the div content with a specific link?
It's hard if you've programmed plain JS before, but you have to forget the "good old JS pattern" in React. I also had a hard time getting used to not using standard JS elements (target, innerHTML, etc.) to solve such a problem.
So the solution in React is to use the framework and your page reload problem will be solved immediately. useState for the state of the component and handlers for the click. My main code looks like this. You can find a working application at Codesandbox.
export default function App() {
const [showComponent, setShowComponent] = useState(false);
const handleButtonClick = (e) => {
setShowComponent(!showComponent);
};
return (
<div className="App">
<h1>
Load specific DIV with a react component without reloading the whole
page
</h1>
<a href="https://stackoverflow.com/questions/74654088/load-specific-div-with-a-react-component-without-reloading-the-whole-page">
Link to Stackoverflow
</a>
<div style={{ marginTop: "20px" }}>
<button onClick={handleButtonClick}>Magic</button>
</div>
{showComponent ? (
<div style={{ marginTop: "20px" }}>
This is the place of your component!
</div>
) : (
""
)}
</div>
);
}
In the first place I wpuld not use vanilla JS syntax on a react app if it is not necessary. i.e: document.getElementById("datapanel").innerHTML="abc<TranslationsList />".
If you are using React you should be managing the State in the component of the DIV, giving the order to make an element appear once the button is clicked.
A simple example can be this:
CodeSandbox
import { useState } from "react";
export default function App() {
const [divState, setDivState] = useState(null);
const divElement = () => <div>I am the element that should appear</div>;
const handleDiv = () => {
setDivState(divElement);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={handleDiv}>Show DIV</button>
<div>{divState}</div>
</div>
);
}
I agree with the answers given above. Since you are already using React, you should take advantage of its features/functionalities. No need to reinvent the wheel.
However, if you are still interested in how to make your current implementation work. You may use renderToString(), which can be imported from ReactDOMServer. Please refer to the following code snippet as an example.
import { renderToString } from 'react-dom/server'
const TranslationsList = () => {
return <div>TranslationsList Content</div>
}
export default function App() {
const loadTarget = () => {
document.getElementById("datapanel").innerHTML=`abc${renderToString(<TranslationsList />)}`;
}
return (
<div>
<button onClick={loadTarget}>Insert Component</button>
<div id="datapanel">Data Panel Holder</div>
</div>
);
}

Issues populating dynamic dropdown with React Hooks

I've never had any issue with this before but I've been struggling to get this to work so I'm hoping someone can notice something that I'm just missing. I'm pulling data from an API to populate a dropdown in my form. The data gets returned just fine and I assign in to my state using useState with no issues. However, when I try to map my results to options for my dropdown it never populates.
Here is my state and my useEffect function that I'm calling once when the page loads.
const [users, setUsers] = useState([]);
useEffect(() => {
axios.get("/api/v1/users/find").then(res => {
setUsers(res.data)
})
}, [])
Like I said this all works with no issues. However this is where the issue is, my form:
<div className="relative">
<h3 className="text-2xl text-center font-bold mb-6">Create Item</h3>
<select className="h-full w-full border-gray-300 px-2 transition-all border-blue rounded-sm" name="owner" onChange={(e) => props.handleChange(e, 'owner')}>
<option selected value=''>Owner</option>
{users.map((i) => {
<option value={i.name}>{i.name}</option>
})}
</select>
</div>
Once again I've never had any issues with this so I'm not sure why it doesn't seem to be populating. Appreciate the help in advance!
Currently the function passed to .map() doesn't return anything. You can fix this by replacing the curly braces with parentheses:
{users.map((i) => (
<option value={i.name}>{i.name}</option>
))}
When curly braces are used in an arrow function you need an explicit return statement to return a value. Without curly braces there is an implicit return on the one line of the function body. (In this case parentheses just help define that one line since it includes carriage returns.)

How do I create show page based on id of item clicked

I am creating list of items looped through .map function. I want each of these items be rendered in a single page with some other details.
import React from 'react'
import {faArrowRight, faMusic, faPlay, faPlayCircle, faTachometerAlt} from "#fortawesome/free-solid-svg-icons";
import {FontAwesomeIcon} from "#fortawesome/react-fontawesome";
import music from '../mocks/music.json'
import { Link } from 'gatsby'
import Music from '../pages/music'
const newData = music.map( (data) => {
return (
<div className="row no-gutters justify-content-between align-items-center">
<div className="col-auto">
<button className="btn-gradient btn-circle">
<FontAwesomeIcon icon={faPlayCircle} />
</button>
</div>
<div className="col">
<div className="music-list-content">
<span className="artist">{data.author}</span>
<Link to={`/music/${data.id}`}>{data.title}</Link>
<span className="play">
<FontAwesomeIcon icon={faPlay} /> {data.duration}
</span>
</div>
</div>
<div className="col-auto">
<span className="badge-dark badge">{data.genre}</span>
</div>
</div>
)
})
const membersToRender = music.filter(member => member.id)
const numRows = membersToRender.length
const Musics = () => {
return (
<div>
<div className="title">
<h5>New Music</h5>
<span>{numRows} new songs</span>
</div>
<div>
<div className="music-list card-wrapper">
{newData}
</div>
</div>
<div className="footer-wrapper">
<div>
<FontAwesomeIcon icon={faMusic} />
<span>Song Library</span>
</div>
<FontAwesomeIcon icon={faArrowRight} />
</div>
</div>
)
}
export default Musics
I created a link which whenever I click, it takes me to another page (page not found) with id appended and .js extension.
Please, how do go about it? I want a click on the title and have it displayed on a full page.
Your logic seems good, however, you are missing the most important part, the page creation, since you are not creating the pages, all of your links are broken.
In Gatsby, you have two different ways of creating pages:
Using gatsby-node.js to create pages dynamically: when dealing with a huge amount of data, like your JSON, it's easier to let Gatsby deal with this responsibility of creating pages for Gatsby. Since you are sourcing from a JSON, you need everything set to create dynamic pages.
const path = require("path")
// Implement the Gatsby API “createPages”. This is called once the
// data layer is bootstrapped to let plugins create pages from data.
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const musics= require("./data/mocks/musics.json")
const musicTemplate = path.resolve(`src/templates/music-template.js`)
musics.forEach(music) => {
createPage({
path: `/music/${music.slug}`
component: musicTemplate,
context: {
title: music.title,
description: music.description,
// and so on for the rest of the fields
},
})
})
}
Note: I'm assuming that your JSON is properly defined and formatted, having all the fields I queried.
Your musicTemplate must be a template (inside /templates folder).
Notice that you are passing some fields through Gatsby's context, this means that those fields will be available through props.pageContext in your template. So, there, create a template like:
import React from "react"
import Layout from "../components/layout"
export default function MusicTemplate({pageContext}) {
return (
<Layout>
<div>Hello musician {pageContext.title}</div>
</Layout>
)
}
So, as I said, with this approach you are creating dynamic pages based on your JSON file, and they will be available inside localhost:8000/music/{music.slug}, and all your reference and links that point there, will be valid.
I would also recommend using static query/useStaticQuery to retrieve data from your JSON in that loop. If you create a static query from that data (in a separate component) you will be able to fetch it on-demand across your project, so you will be reusing an interesting part of logic. It's better to use it rather than requesting a JSON directly.
You can follow this guide from the great Jason Lengstorf which is mostly what you need.
Adding .js files in your /pages folder: Gatsby infers the internal structure of your /pages folder and will create pages accordingly to that structure. For instance, if you have a structure like: /pages/musicians/name1.js Gatsby will create a page like localhost:8000/musicians/name1.
As it has been said, the first approach fits your requirements and it's preferred for this use-cases, since the second one will be less scalable and maintainable.
You should do some routing with React-Router (https://reactrouter.com/web/example/basic).
So the link have to point to a Route in a Switch, as is in the example of the link.

React Virtualized windowscroller scrollElement not working

You were right that I was not passing the props in correctly. Now I have it set up as such:
Container.jsx
<div className='container' ref={(ref) => {this.foo = ref;}}>
this.renderContainer()
</div>
<Section scrollContainer={this.foo}/>
Section.jsx (just passing down props)
<Panel scrollContainer={this.props.scrollContainer}/>
Section.propTypes = { scrollContainer: PropTypes.object.isRequired }
Panel.jsx (Passing down props)
<RenderedTable scrollContainer={this.props.scrollContainer} />
RenderedTable.jsx
return (
<div className='padding-top-20 font-smoothing'>
<WindowScroller scrollElement={this.props.scrollContainer}>
{({ height, isScrolling, scrollTop, onChildScroll }) => (
<AutoSizer disableHeight>
{({ width }) => (
<Table
Unfortunately the windowScroller still does not resize. I also dont get any warnings or errors. Do you use css tricks to get the scroller to resize? I see that in the example https://bvaughn.github.io/react-virtualized/#/components/WindowScroller
you change the flex and overflow properties when changing the scrollElement from window to scrollingBody.
I know you are very busy and greatly appreciate your help!
In your example, you've assigned the scroll ref to this.tabsContainer but you're trying to access it as this.props.tabContainer. Either this is your mistake, or the example is incomplete and more context is needed. :) Can you provide a Plnkr?

How to notify List component that one item has changed the size?

I would like to render a list of items using react-virtualized, however some of the items could change the size.
I have added a demo, where each item has a button and when someone clicks the button then the item needs to expand and push the below items lower:
https://plnkr.co/edit/GG2bSIC5M1Gj7BR1pOii
The use case is similar to facebook posts, when someone comments it will expand the post and push the other posts lower.
ReactDOM.render(
<List
className='List'
autoHeight
width={300}
height={400}
rowCount={list.length}
rowHeight={30}
rowRenderer={
({ index, isScrolling, key, style }) => (
<div
className='Row'
key={key}
style={style}
>
{list[index]}
<Expander /> /* contains the button, which when click it will expand more text*/
</div>
)
}
/>,
document.getElementById('example')
Is any way to achieve this?
UPDATE
I am realizing that there must be a way to force the List to update items and recalculate positions. There is an example with InfiniteLoader (react-virtualized example) which is using registerChild in the List as a ref. And it seems that when InfiniteLoader receives answer from the server it capable of forcing list to re-render rows.
I have tried another example with forceUpdate on list, however the List is still not updating.
https://plnkr.co/edit/RftoShEkQW5MR5Rl28Vu
As per the Documentation forceUpdateGrid() should be called instead of forceUpdate().
https://github.com/bvaughn/react-virtualized/blob/master/docs/List.md#public-methods
I faced the similar issue and solved it by calling forceUpdateGrid() inside componentWillReceiveProps method.
componentWillReceiveProps(){
this.refs.forceUpdateGrid();
}
render(){
<List
ref={ref => this.refs = ref}
.....
/>
}
Try using Collection from react-virtualized instead of List. It should help :)
add key={Math.random()} the parent of the list item solved my issue.
<ListWidget key={Math.random()}>
<Autosizer>{
.....
<List
.....
/>
.....
}</Autosizer>
</ListWidget>
Note: not recommended, it's just a workaround to get PR accepted

Resources