Material UI Styles Not Rendering - javascript

Im attempting to build a website using Material-UI and React. When attempting to use Material-UI's styling via the Hook API, it works online in codesandbox.io but does not work locally when I run it. The border radius property does not seem to update, nor do any of the properties in the button or instruction object
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
const useStyles = makeStyles({
root: {
width: "100%"
},
button: {
marginRight: 10,
borderRadius: 100,
fontSize: 20,
},
instructions: {
marginTop: 2,
marginBottom: 5
}
});
function getSteps() {
return ['Select campaign settings', 'Create an ad group', 'Create an ad'];
}
function getStepContent(step) {
switch (step) {
case 0:
return 'Select campaign settings...';
case 1:
return 'What is an ad group anyways?';
case 2:
return 'This is the bit I really care about!';
default:
return 'Unknown step';
}
}
function HorizontalLinearStepper() {
const classes = useStyles();
const [activeStep, setActiveStep] = React.useState(0);
const [skipped, setSkipped] = React.useState(new Set());
const steps = getSteps();
function isStepOptional(step) {
return step === 1;
}
function isStepSkipped(step) {
return skipped.has(step);
}
function handleNext() {
let newSkipped = skipped;
if (isStepSkipped(activeStep)) {
newSkipped = new Set(newSkipped.values());
newSkipped.delete(activeStep);
}
setActiveStep(prevActiveStep => prevActiveStep + 1);
setSkipped(newSkipped);
}
function handleBack() {
setActiveStep(prevActiveStep => prevActiveStep - 1);
}
function handleSkip() {
if (!isStepOptional(activeStep)) {
// You probably want to guard against something like this,
// it should never occur unless someone's actively trying to break something.
throw new Error("You can't skip a step that isn't optional.");
}
setActiveStep(prevActiveStep => prevActiveStep + 1);
setSkipped(prevSkipped => {
const newSkipped = new Set(prevSkipped.values());
newSkipped.add(activeStep);
return newSkipped;
});
}
function handleReset() {
setActiveStep(0);
}
return (
<div className={classes.root}>
<Stepper activeStep={activeStep}>
{steps.map((label, index) => {
const stepProps = {};
const labelProps = {};
if (isStepOptional(index)) {
labelProps.optional = <Typography variant="caption">Optional</Typography>;
}
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label} {...stepProps}>
<StepLabel {...labelProps}>{label}</StepLabel>
</Step>
);
})}
</Stepper>
<div>
{activeStep === steps.length ? (
<div>
<Typography className={classes.instructions}>
All steps completed - you&apos;re finished
</Typography>
<Button onClick={handleReset} className={classes.button}>
Reset
</Button>
</div>
) : (
<div>
<Typography className={classes.instructions}>{getStepContent(activeStep)}</Typography>
<div>
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
Back
</Button>
{isStepOptional(activeStep) && (
<Button
variant="contained"
color="primary"
onClick={handleSkip}
className={classes.button}
>
Skip
</Button>
)}
<Button
variant="contained"
color="primary"
onClick={handleNext}
className={classes.button}
>
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
</Button>
</div>
</div>
)}
</div>
</div>
);
}
export default HorizontalLinearStepper;
You can view the expected results here: https://98m6j7m314.codesandbox.io
in which the buttons border is circular after applying the borderRadius property

When using #material-ui/styles with #material-ui/core you need to follow the installation step https://v3.material-ui.com/css-in-js/basics/#migration-for-material-ui-core-users.
Here's your codesandbox link working: https://codesandbox.io/s/material-demo-rv2w1

Web browsers use cache and in some cases your changes are not reloaded. Refershing using Ctrl+f5, clearing or disabling cache in your settings may be useful.
Please attempt to see your localhost web page using another web browser & in incognito

Related

Share State between two specific instances of the same react component React

Before y'all say global state(redux), I'd like to say one thing. I'm mapping through an array I fetched from my API. I receive images and map over them and render my Slider component. Every 2 sliders must share the same state. So, then if i move to the next slide in the first slider, then the second slider must also go to the next slide(but not any other slides). If I move to the next slide in the 5th slider, the 6th must also move to the next slide... so on.
Component where I map over slides:
<div className='image-grid'>
{screenshots.map((imagesByResolution, resIdx, screenshotResArr) => {
return imagesByResolution.map((img, scriptIdx, screenshotScriptsArr) => {
return <Slider slides={formattedSlides} />;
});
})}
</div>
Slider:
import Button from '#material-ui/core/Button';
import MobileStepper from '#material-ui/core/MobileStepper';
import { useTheme } from '#material-ui/core/styles';
import KeyboardArrowLeft from '#material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
import React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { autoPlay } from 'react-swipeable-views-utils';
import { encodeImage } from '../services/images';
import useStyles from '../styles/slider';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
export interface ISlide {
title: string;
img: ArrayBuffer;
}
interface Props {
slides: ISlide[];
}
export default function Slider(props: Props) {
console.log(props);
const { slides } = props;
const classes = useStyles();
const theme = useTheme();
const [activeSlide, setActiveSlide] = React.useState(0);
const maxSlides = slides.length;
const handleNext = () => {
setActiveSlide((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveSlide((prevActiveStep) => prevActiveStep - 1);
};
const handleSlideChange = (step: number) => {
setActiveSlide(step);
};
return (
<div className={classes.root}>
<div className={classes.header}>
<h4 className={classes.title}>{slides[activeSlide].title}</h4>
</div>
<AutoPlaySwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeSlide}
onChangeIndex={handleSlideChange}
enableMouseEvents
>
{slides.map((slide, index) => (
<div key={index}>
{Math.abs(activeSlide - index) <= 2 ? (
<img className={classes.img} src={encodeImage(slide.img, 'image/png')} alt={slide.title} />
) : null}
</div>
))}
</AutoPlaySwipeableViews>
<MobileStepper
steps={maxSlides}
position='static'
variant='text'
activeStep={activeSlide}
nextButton={
<Button size='small' onClick={handleNext} disabled={activeSlide === maxSlides - 1}>
Next
{theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</Button>
}
backButton={
<Button size='small' onClick={handleBack} disabled={activeSlide === 0}>
{theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
Back
</Button>
}
/>
</div>
);
}
If this is not possible using either some global state management library or plain ol' react state, what is the other alternative? Thanks in advance!
Pass a unique key prop to each instance of your component.
Credits: https://stackoverflow.com/a/65654818/9990676

Type Error property undefined even though action fired off defining it

I am currently having a button redirect to the same location a button on a prior dashboard takes you to it is essentially a render of a graph and all of its notes and questions rendered. Now the specific snippet in the error works from my initial location which is denoted below in my screenshot that speculation button will take you to the rendered show route below it. However, the button that appears after you have made a new question and note do not they use the same snippet but one errors out I am also adding the snippet for the buttons.
*edit 1 adding show.js
show.js
// built-in(lifecycle) methods imported here
import React, { Component } from 'react'
// import components from local
import Graph from '../components/Graph'
import Notes from '../components/Notes'
import Questions from '../components/Questions'
//imbrl allows us to enable routing by updating url and rendering needed component listed in routeer
import { NavLink } from 'react-router-dom'
//bootstrap WIP
import Button from 'react-bootstrap/Button'
import Row from 'react-bootstrap/Row'
import Col from 'react-bootstrap/Col'
import Card from 'react-bootstrap/Card'
// access state from redux store
import { connect } from 'react-redux'
class Show extends Component {
render() {
// variables for objects
const graph = this.props.graphs.find(graph => { return graph.id === parseInt(this.props.match.params.id)})
const notes = this.props.notes.filter(note => note.graph.id === graph.id)
const questions = this.props.questions.filter(question => question.graph.id === graph.id)
// if graph exists it loads all corresponding notes and questions with it
if (graph) {
return (
<Row>
<Col md={3}>
<Notes graph={graph} notes={notes} />
</Col>
<Col md={6} >
<Card>
<Graph graph={graph}/>
</Card>
<NavLink
to={`/graphs/${graph.id}/interact`}>
<Button>Interact</Button>
</NavLink>
</Col>
<Col md={3}>
<Questions graph={graph} questions={questions} />
</Col>
</Row>
)
} else {
return (
<div>
<NavLink
style={{marginRight: '10px'}}
to="/">
<Button variant="dark" size="lg" block>Add Data to get started</Button>
</NavLink>
</div>
)
}
}
}
// this will need access to the objects
const mapStateToProps = state => {
return {
graphs: state.graphs,
notes: state.notes,
questions: state.questions
}
}
export default connect (mapStateToProps)(Show)
graphinput.js
import React, { Component } from 'react'
// import actions future?
import { addNote } from '../actions/addSpeculations'
import { addQuestion} from '../actions/addSpeculations'
// browser url routing render component as needed
import { NavLink } from 'react-router-dom'
// import local components
import Note from '../components/Note'
import Question from '../components/Question'
// bootstrap styling
import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'
// allows access to redux store this is a stateful component
import { connect } from 'react-redux'
class GraphInput extends Component {
// initial state placejolders
state ={
note: {
content: ""
},
question: {
content: ""
},
visible: false,
view: false
}
// state will be updated everytime the form value changes
handleChange = (event) => {
this.setState({
[event.target.name]: {content: event.target.value, graph_id: this.props.graph_id}
})
}
// visible key will show as true to confirm before calling fetch.
handleSubmit = (event) => {
event.preventDefault()
this.setState({visible: true})
}
handleSave = () => {
this.props.addNote(this.state.note)
this.props.addQuestion(this.state.question)
this.setState({
note: {content: ""},
question: {content: ""},
visible: false,
view: true
})
}
// if user cancels submission, state should reset to initial values
handleCancel = () => {
this.setState({
note: {content: ""},
question: {content: ""},
visible: false,
view: false
})
}
render () {
// Need to check current state to base what return should be used for what user interaction has done.
/*const validated= this.state.note.content.length > 20 && this.state.note.content.length > 20*/
if (this.state.visible === false && this.state.view === false){
/// render form and graph
return (
<div>
<h3> Add your Observations or Speculation below. </h3>
<Form onSubmit={event => this.handleSubmit(event)} >
<Form.Group>
<Form.Control size="lg" type="text" name="note" placeholder="Make an observation." value={this.state.note.content} onChange={event => this.handleChange(event)} />
</Form.Group>
<Form.Group>
<Form.Control size="lg" type="text" name="question" placeholder="Ask a question" value={this.state.question.content} onChange={event => this.handleChange(event)} />
</Form.Group>
<Button type="submit" >Add</Button>
</Form>
</div>
)
} else if (this.state.visible === true && this.state.view === false) {
/// rwendering draft to confirm submission
return (
<div>
<Note note={this.state.note} />
<Question question={this.state.question} />
<Button type="submit" onClick={this.handleSave}> Save Speculation to Database</Button>
<Button type="submit" variant="danger" onClick={this.handleCancel}>Cancel</Button>
</div>
)
} else if (this.state.view === true) {
// after saving, user can now navigate to view all
return (
<NavLink
style={{ marginRight: '10px'}, {backgroundColor: 'transparent'}}
to={`/graphs/${this.props.graph_id}/speculations`}
graph={this.props.graph}>
<Button size="lg" block>View All Speculation for This Graph</Button>
</NavLink>
)
}
}
}
// only state needed is graph speculations for specific graph
const mapStateToProps = (state) => {
return {
graph: state.graph
}
}
// Dispatch to props for Notes and Questions
export default connect(mapStateToProps, {addNote, addQuestion})(GraphInput)
Broken Button
<NavLink
style={{ marginRight: '10px'}, {backgroundColor: 'transparent'}}
to={`/graphs/${this.props.graph_id}/speculations`}
graph={this.props.graph}>
<Button size="lg" block>View All Speculation for This Graph</Button>
</NavLink>
Working Button
<NavLink
style={{ marginRight: '10px'}}
to={`/graphs/${this.props.graph.id}/speculations`}
url={this.props.graph.screenshot_url} >
<Button variant="success" >
Speculations
</Button>
</NavLink>
This is the button that works
This is where the button should take you
This button does not go to the show with that specific graph's notes and questions. (Both are using the same snippet to identify graph and its id or are supposed to)

Need tabs to hold their own state, so it can hold its own number of items within

import axios from 'axios';
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import StarIcon from '#material-ui/icons/Star';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import Paper from '#material-ui/core/Paper';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import TwitterIcon from '#material-ui/icons/Twitter';
import CloseIcon from '#material-ui/icons/Close';
import Highlighter from 'react-highlight-words';
class TwitterBot extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleTabState = this.handleTabState.bind(this);
}
state = {
loaded: [],
searched: [],
searchedTicker: '',
actveTab: '',
addedTickers: []
};
async componentDidMount() {
//Gathering data from heroku API I built and adding tweets to loaded array state
let feed = await axios.get('https://boiling-plains-63502.herokuapp.com/');
let tweets = feed.data;
this.setState({
loaded: tweets
});
}
handleChange = (e) => {
//Watching input and changing searchedTicker string while typing
this.setState({ searchedTicker: e.target.value });
};
handleTabChange = (event, newValue) => {
//Selecting the correct tab
this.setState({ tabValue: newValue });
};
handleTabState = (e, data) => {
//This is changing searchedTicker state to the value of whichever tab is clicked
this.setState({ searchedTicker: data });
};
showAll = () => {
//Clearing searched state
this.setState({ searchedTicker: '' });
};
addTicker = () => {
// Adding ticker to saved list
if (this.state.searchedTicker.length > 0) {
this.setState((state) => {
const tickers = state.addedTickers.push(state.searchedTicker);
return {
tickers,
searchedTicker: ''
};
});
} else {
alert('Plase enter a symbol to search');
}
};
removeTicker = (e, data) => {
// Removing tab
let tickers = this.state.addedTickers;
if (tickers.indexOf(data) === 0) {
tickers.shift();
this.showAll();
console.log('zero');
} else {
tickers.splice(tickers.indexOf(data));
this.showAll();
}
};
savedTickerFilter = (f) => {
this.setState({ searchedTicker: f.target.value });
};
render() {
//Trimming searched input to all lowercase and filtering displayed post within return based on search
let loaded = this.state.loaded,
searchedTicker = this.state.searchedTicker.trim().toLowerCase();
if (searchedTicker.length > 0) {
loaded = loaded.filter(function(i) {
return i.text.toLowerCase().match(searchedTicker);
});
}
//Copying loaded state and attempting to added individual numbers of tweets to each tab
let copyOfLoaded = [ ...this.state.loaded ];
let filterCopy = copyOfLoaded.filter(function(i) {
return i.text.toLowerCase().match(searchedTicker);
});
let numOfTweets = filterCopy.length;
return (
<div className="main" style={{ marginTop: 40 + 'px' }}>
<h4>Search a stock symbol below to find relevant tweets from Stocktwitz.</h4>
<h4>You may then press Add to Favorites to create a saved tab for later reference.</h4>
<div className="main__inner">
<TextField
type="text"
value={this.state.searchedTicker}
onChange={this.handleChange}
placeholder="Search Ticker..."
id="outlined-basic"
label="Search"
variant="outlined"
/>
<Button onClick={this.addTicker} variant="contained" color="primary">
Add to favorites <StarIcon style={{ marginLeft: 10 + 'px' }} />
</Button>
</div>
{/* This will be the Filter Tabs component and that will import the list thats below the Paper component below */}{' '}
<Paper square>
<Tabs indicatorColor="primary" textColor="primary" onChange={this.handleTabChange}>
<Tab label={<div className="tabs-label">All ({loaded.length})</div>} onClick={this.showAll} />
{//Mapping through tabs that are added in TwitterBot component and passed down as props to this component
this.state.addedTickers.map((i) => {
return (
<div className="tab-container">
<Tab
label={
<div className="tabs-label">
{i}
({numOfTweets})
</div>
}
key={i}
onClick={(e) => this.handleTabState(e, i)}
/>
<CloseIcon value={i} onClick={(e) => this.removeTicker(e, i)} />
</div>
);
})}
</Tabs>
</Paper>
<List className="tweets">
{loaded.map(function(i) {
return (
<ListItem key={i.id}>
{' '}
<TwitterIcon style={{ marginRight: 10 + 'px', color: '#1da1f2' }} />
<Highlighter
highlightClassName="YourHighlightClass"
searchWords={[ searchedTicker ]}
autoEscape={true}
textToHighlight={i.text}
/>,
</ListItem>
);
})}
</List>
</div>
);
}
}
export default TwitterBot;
Above is the entire component that holds all necessary logic.
I basically want {{numOfTweets}} within the tab-label to be static to each Tab thats mapped through once created. Right now it correctly will show how many items per tab there are while searching, and if clicked on current tab, but all tabs will react. I need them to stay static after search so if clicked on another tab, the past tab will still show how many tweets there were for that searched tab. Right now it's happening just because it's referencing the global loaded state, I just need way to copy that and render each one individually. I hope I explained that clear enough. You can see what I mean on my demo here: https://5ec5b3cfc2858ad16d22bd3c--elastic-khorana-7c2b7c.netlify.app/
I understand I need to break out and componentize this more, but I know theres has to be an easy solution, somehow using a simple functional component to wrap the Tab component or simple just the number that will be displayed. (I'm using Material UI)
Thank you, anything helps, just need to wrap my head around it.
Please check the codesandbox here https://codesandbox.io/s/proud-leftpad-j0rgd
I have added an object instead of a string for Addedtickers so that the count can be tracked and remains constant throughout. You can further optimize this , if you want to search again within each individual tab, but you get the gist.
Please let me know if this works for you

Checkboxes in map not updating on array update after refactor to react hooks

I converted a class component into a function component using hooks. Currently, I'm struggling to figure out why the checkboxes within this map is not updating with checked value, despite the onChange handler firing, and updating the array as necessary. (The onSubmit also works, and updates the value within the DB properly).
import {
Container,
Typography,
Grid,
Checkbox,
FormControlLabel,
Button
} from "#material-ui/core";
import Select from "react-select";
import localeSelect from "../services/localeSelect";
import {
linkCharactersToGame,
characterLinked,
linkCharacters
} from "../data/locales";
import dbLocale from "../services/dbLocale";
import { LanguageContext } from "../contexts/LanguageContext";
import { UserContext } from "../contexts/UserContext";
import { GameContext } from "../contexts/GameContext";
import { CharacterContext } from "../contexts/CharacterContext";
import { Redirect } from "react-router-dom";
export default function LinkCharacter() {
const { language } = useContext(LanguageContext);
const { user } = useContext(UserContext);
const { games, loading, error, success, connectCharacters } = useContext(
GameContext
);
const { characters } = useContext(CharacterContext);
const [game, setGame] = useState("");
const [selectedCharacters, setSelectedCharacters] = useState([]);
if (!user) {
return <Redirect to="/" />;
}
return (
<section className="link-character">
<Container maxWidth="sm">
<Typography variant="h5">
{localeSelect(language, linkCharactersToGame)}
</Typography>
{error && (
<p className="error">
<span>Error:</span> {error}
</p>
)}
{success && <p>{localeSelect(language, characterLinked)}</p>}
<Select
options={games.map(game => {
return {
label: dbLocale(language, game),
value: game._id
};
})}
onChange={e => {
setGame(e.value);
const selected = [];
const index = games.findIndex(x => x._id === e.value);
games[index].characters.forEach(character => {
selected.push(character._id);
});
setSelectedCharacters(selected);
}}
/>
</Container>
<Container maxWidth="md">
{game !== "" && (
<>
<Grid container spacing={2}>
{characters.map((character, index) => {
return (
<Grid item key={index} md={3} sm={4} xs={6}>
<FormControlLabel
control={
<Checkbox
value={character._id}
onChange={e => {
const index = selectedCharacters.indexOf(
e.target.value
);
if (index === -1) {
selectedCharacters.push(e.target.value);
} else {
selectedCharacters.splice(index, 1);
}
}}
color="primary"
checked={
selectedCharacters.indexOf(character._id) !== -1
}
/>
}
label={dbLocale(language, character)}
/>
</Grid>
);
})}
</Grid>
<Button
variant="contained"
color="primary"
onClick={e => {
e.preventDefault();
connectCharacters(game, selectedCharacters);
}}
>
{localeSelect(language, linkCharacters)}
</Button>
</>
)}
</Container>
</section>
);
}
I feel like there's something I'm missing within Hooks (or there's some sort of issue with Hooks handling something like this). I have been searching and asking around and no one else has been able to figure out this issue as well.
The state returned by [state, setState] = useState([]) is something that you should only be reading from. If you modify it, React won't know that the data has changed and that it needs to re-render. When you need to modify data, you have to use setState, or in your case setSelectedCharacters.
Also, modifying the data by reference might lead to unpredictable results if the array is read elsewhere, later on.
In addition to that, if you give the same value to setState, that the hook returned you in state, React will skip the update entirely. It is not a problem when using numbers or strings, but it becomes one when you use arrays, because the reference (the value React uses to tell if there is a difference) can be the same, when the content might have changed. So you must pass a new array to setState.
With that in mind, your onChange function could look like:
onChange={e => {
const index = selectedCharacters.indexOf(
e.target.value
);
if (index === -1) {
// creating a new array with [], so the original one stays intact
setSelectedCharacters([...selectedCharacters, e.target.value]);
} else {
// Array.filter also creates new array
setSelectedCharacters(selectedCharacters.filter((char, i) => i !== index));
}
}}
Doc is here https://en.reactjs.org/docs/hooks-reference.html#usestate

In ReactJS is component state tied to an instance of the component or is it tied to all instances of the component?

I am trying to display multiple instances of a component on a page. Each instance should appear within a Tab.Pane (Semantic UI feature). However in my attempt to achieve this, I am getting some strange behaviour as explained in more detail below.
I have a component; <MultistepFilterSection />
Within this component the state is:
this.state = {
conjunction: "AND" // default
}
This state is modified by a onClick handler function in the component;
handleConjunctionButtonClick = (conjunction, e) => {
this.setState({conjunction: conjunction})
}
This onClick is triggered by clicking on one of two buttons:
<Button.Group vertical>
<Button
onClick={(e) => this.handleConjunctionButtonClick("AND")}
color={this.state.conjunction === "AND" ? "blue" : null}
>
And
</Button>
<Button
onClick={(e) => this.handleConjunctionButtonClick("OR")}
color={this.state.conjunction === "OR" ? "blue" : null}
>
Or
</Button>
</Button.Group>
I am using Semantic UI's Tab component to render 3 instances of this component on a page;
const panes = [
{
menuItem: "Panel Data",
render: () =>
<Tab.Pane attached={false}>
<MultistepFilterSection getAudience={this.getAudience} />
</Tab.Pane>
},
{
menuItem: "Geolocation Data",
render: () =>
<Tab.Pane attached={false}>
<MultistepFilterSection getAudience={this.getAudience} />
</Tab.Pane>
},
{
menuItem: "Combined Audience Selection",
render: () =>
<Tab.Pane attached={false}>
<MultistepFilterSection getAudience={this.getAudience} />
</Tab.Pane>
}
]
In the render method I have:
<Tab
menu={{ secondary: true, pointing: true }}
panes={panes}
/>
Triggering the onClick handler in any one of the tabs changes the state in all instances of the component. Is this normal? I thought that the component state in one instance of the component is exclusive to that instance.
On further investigation, I found that the behaviour is exhibited when I'm instantiating multiple instances of the <MultistepFilterSection /> using Semantic UI's Tab component. When rendering the instances on their own, the exhibit expected behaviour.
The full code of the <MultistepFilterSection /> component:
import React from "react";
import { Grid, Button, Container, Icon, Segment } from "semantic-ui-react";
import { connect } from "react-redux";
import uuid from "uuid";
import _ from "lodash";
import RuleGroup from "../rules/RuleGroup";
import { filterGroupCreated, filterGroupDeleted, filterDeleted } from "../../actions/FiltersAction"
export class MultistepFilterSection extends React.Component {
constructor(props) {
super(props);
this.state = {
conjunction: "AND" // default
}
// create one default group
this.props.filterGroupCreated({filterGroupId: uuid()})
this.handleAddRuleGroupButtonClick = this.handleAddRuleGroupButtonClick.bind(this);
this.renderRuleGroups = this.renderRuleGroups.bind(this);
this.handleConjunctionButtonClick.bind(this)
}
handleAddRuleGroupButtonClick() {
// console.log("called handleAddRuleGroupButtonClick()")
const filterGroupId = uuid()
let data = {}
data.filterGroupId = filterGroupId
this.props.filterGroupCreated(data)
}
handleConjunctionButtonClick = (conjunction, e) => {
this.setState({conjunction: conjunction})
}
renderRuleGroups() {
// console.log("called renderRuleGroups()")
return Object.values(this.props.filterGroups).map(function(ruleGroup) {
const key = ruleGroup.id;
// incomplete
let currentGenderSelected
return (
<React.Fragment key={key}>
<RuleGroup
id={key}
key={key}
deleteRuleGroup={this.deleteRuleGroup}
deleteFilter={this.deleteFilter}
currentGenderSelected={currentGenderSelected}
/>
</React.Fragment>
)
}.bind(this))
}
deleteRuleGroup = (id, e) => {
// console.log("delete rule group called")
this.props.filterGroupDeleted({filterGroupId: id})
}
deleteFilter = (filterGroupId, filterId, e) => {
// console.log("deleteFilter() called")
this.props.filterDeleted({
filterGroupId: filterGroupId,
filterId: filterId
})
}
render() {
const isFilterGroupQuery = true
return(
<Grid padded="vertically">
<Grid.Row stretched>
<Grid.Column verticalAlign={"middle"} width={2}>
{_.size(this.props.filterGroups) > 0 &&
<Segment basic>
<Button.Group vertical>
<Button
onClick={(e) => this.handleConjunctionButtonClick("AND")}
color={this.state.conjunction === "AND" ? "blue" : null}
>
And
</Button>
<Button
onClick={(e) => this.handleConjunctionButtonClick("OR")}
color={this.state.conjunction === "OR" ? "blue" : null}
>
Or
</Button>
</Button.Group>
</Segment>
}
</Grid.Column>
<Grid.Column width={14}>
{this.renderRuleGroups()}
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column width={2}></Grid.Column>
<Grid.Column width={3}>
<Button
color="grey"
basic
onClick={this.handleAddRuleGroupButtonClick}
>
<Icon name="plus" />Add Rule Group
</Button>
</Grid.Column>
<Grid.Column width={11}>
<Button
color="blue"
onClick={
(isFilterGroupQuery) => this.props.getAudience(
isFilterGroupQuery, this.state.conjunction
)
}
floated={"right"}
>
Run query
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
)
}
}
function mapStateToProps(state) {
return {
filterGroups: state.filters
};
}
export default connect(
mapStateToProps,
{
filterGroupCreated,
filterGroupDeleted,
filterDeleted
}
)(MultistepFilterSection);
After much digging around and eventually posting an issue on Semantic UI's GitHub repo, I learned that I needed to use renderActiveOnly={false} prop on the Tab component. I also had to change my markup so that panes objects were in the following format:
const panes = [
{
key: 1,
menuItem: "Panel Data",
pane: <Tab.Pane><Simple key={1} /></Tab.Pane>
},
{
key: 2,
menuItem: "Geolocation Data",
pane: <Tab.Pane><Simple key={2} /></Tab.Pane>
}
]

Categories