I'm having a great difficulty with this for a few days. How does one change the context value in a nested component inside a provider wrapper.
For example:
The provider:
<MapThemeContext.Provider value={this.state}>
<div className="App">
<Dashboard/>
</div>
</MapThemeContext.Provider>
where this.state (within the same component as the where the provider is rendered)
this.state = {
theme : themes.silver,
handleMapChange: this.handleMapChange,
}
this.handleMapChange = (style) => {
if (style == "silver") {
this.setState({
theme : themes.silver
});
} else if (style == "dark") {
this.setState({
theme : themes.dark
})
}
}
}
And deep inside the provider (nested insside the Dashboard) is a consumer:
<MapThemeContext.Consumer>
{({handleMapChange}) => (
<div>
<Button variant="contained" className={classes.button} onClick={e => handleMapChange('silver')}> // this does not work
Silver
</Button>
<Button variant="contained" className={classes.button} onClick={handleMapChange}>
Dark
</Button>
</div>
)}
</MapThemeContext.Consumer>
How does the method call within the consumer use the correct parameter to set the value of the context?
Someone again deep down is another component that uses this context for the theme, and it does use the default value correctly. I just don't understand how to change it on the consumer button.
The context content:
export const themes = {
silver: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
export const ThemeContext = React.createContext(
theme: themes.silver,
handleMapChange: () => {},
);
Can you replace below two section and check once.
----------------------------- Section 1 -----------------------------
const isTheme = this.state.theme;
let buttonName = "dark";
if(isTheme == "silver"){
buttonName = "silver";
}
<MapThemeContext.Consumer>
<div>
<Button variant="contained" className={classes.button} onClick={handleMapChange}>{buttonName}</Button>
</div>
</MapThemeContext.Consumer>
----------------------------- Section 2 -----------------------------
this.handleMapChange = () => {
const style = this.state.theme;
if (style == "silver") {
this.setState({
theme : themes.dark
});
} else if (style == "dark") {
this.setState({
theme : themes.silver
})
}
}
}
Related
It's saying state.map is not a function but it works just fine before i call the handleclick function.I am stuck here and would like to know why its giving me the state.map is not a function error. Added bonus too would
be me asking how does one setState when you have objects inside objects.
For example if id like to return with setStatea {...item, value:false} value is inside and object thats inside and object. How would I specifically target the value I need inside and array of objects. Thank you.
import React from "react";
import Quiz from "./components/Quiz";
import Menu from "./components/Menu";
import { v4 as uuidv4 } from 'uuid'
function App() {
const [state,setState] = React.useState([])
const [start,setStart] = React.useState(false)
React.useEffect(()=> {
if(start === true){
console.log(state)
fetch("https://opentdb.com/api.php?amount=5&category=27&difficulty=easy&type=multiple")
.then(res => res.json())
.then(data => setState(data.results.map(item =>
({selectedQuestion: "",
buttons: item.incorrect_answers.concat(item.correct_answer).map(item =>
({use: item ,value: false, id: uuidv4()})),
questions: item.question
}))))
}} , [start])
function startGame () {
setStart(prevState => !prevState)
}
const myButtons = state.map(function(item) {
return (<Quiz
key={uuidv4()}
buttons={item.buttons}
value ={item.buttons.map(item => item.value)}
question = {item.questions}
handleClick ={(event) =>handleClick(event.target.value)}
/> ) })
console.log(state.map(item => item))
function handleClick (event) {
if (event === "false"){
//this is what is giving me issues//
setState(item => (
{
...item, selectedQuestion: "howdy"
}))
//////////////////////////////////////////////////////////////
}
else {
console.log("hi")
}
}
return (<div>
{start === false ? <Menu
startGame ={startGame}
/> : <div className="parent--quiz">
<div className="quiz">
{myButtons}
</div>
</div>
}
</div>
)
}
export default App;
Quiz component is here
function Quiz (props) {
const styles ={
backgroundColor: props.value === true ? "red" : "#D6DBF5"
}
return (
<div>
<div className="cards" style={{ borderTop: "2px solid #fff ", marginLeft: 20, marginRight: 20 }}>
<h1 className="quiz--h1">{props.question}</h1>
<div className="quiz--buttons-div" >
{props.buttons.map((item) => (
<button className="quiz--buttons" style={styles} value={item.value} onClick={props.handleClick}>{item.use}</button>
))}
</div>
</div>
</div>
)
}
export default Quiz
code sections are fine
I have a ButtonGroup with a few Buttons in it, and when one of the buttons gets clicked, I want to change its color, I kinda want to make them behave like radio buttons:
<ButtonGroup>
<Button
variant={"info"}
onClick={(e) => {
..otherFunctions..
handleClick(e);
}}
>
<img src={square} alt={".."} />
</Button>
</ButtonGroup>
function handleClick(e) {
console.log(e.variant);
}
But that doesnt work, e.variant is undefined.
If it was just a single button I would have used useState and I would be able to make this work, but how do I make it work when there are multiple buttons, how do I know which button is clicked and change the variant prop of that button? And then revert the other buttons to variant="info"
Another approach that I could think of is to create my own Button that wraps the bootstrap Button and that way I can have access to the inner state and use onClick inside to control each buttons state, but I'm not sure if that will work, as then how would I restore the other buttons that werent clicked..?
To further from my comment above, you could create your own button component to handle its own state and remove the need to have lots of state variables in your main component e.g.
const ColourButton = ({ children }) => {
const [colour, setColour] = React.useState(true)
return (
<button
onClick={ () => setColour(!colour) }
style = {{color: colour ? "red" : "blue"} }
>
{ children }
</button>
)
}
That way you can just wrap your image in your new ColourButton:
<ColourButton><img src={square} alt={".."} /></ColourButton>
Edit:
I actually like to use styled-components and pass a prop to them rather than change the style prop directly. e.g. https://styled-components.com/docs/basics#adapting-based-on-props
EDIT: Kitson response is a good way to handle your buttons state locally :)
I like to handle the generation of multiple elements with a function. It allows me to customize handleClick.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [buttons, setButtons] = useState([
{
id: 1,
variant: "info"
},
{
id: 2,
variant: "alert"
}
]);
const handleClick = id => {
setButtons(previous_buttons => {
return previous_buttons.map(b => {
if (b.id !== id) return b;
return {
id,
variant: "other color"
};
});
});
};
const generateButtons = () => {
return buttons.map(button => {
return (
<button key={button.id} onClick={() => handleClick(button.id)}>
Hey {button.id} - {button.variant}
</button>
);
});
};
return <div>{generateButtons()}</div>;
}
https://jrjvv.csb.app/
You can maintain a state variable for your selected button.
export default class ButtonGroup extends Component {
constructor(props) {
super(props);
this.state = {
selected: null
};
}
handleClick = e => {
this.setState({
selected: e.target.name
});
};
render() {
const selected = this.state.selected;
return (
<>
<button
name="1"
style={{ backgroundColor: selected == 1 ? "red" : "blue" }}
onClick={this.handleClick}
/>
<button
name="2"
style={{ backgroundColor: selected == 2 ? "red" : "blue" }}
onClick={this.handleClick}
/>
<button
name="1"
style={{ backgroundColor: selected == 3 ? "red" : "blue" }}
onClick={this.handleClick}
/>
</>
);
}
}
Here is a working demo:
https://codesandbox.io/live/OXm3G
I have search filter and categories. I just want to have a possibility to reset state in single page application.
Due to React.js I guess I do everything correct to pass state from parent to child and then from child to parent. But, unfortunately, something is going wrong. I tried a lot and what I discovered, that onAddCategory() in DropdownGroup doesn't update current state.
Sorry in advance, I add whole code, my be something there could affect this. But i guess you can see first halfs of two codes and it will be enough.
Thank you in advance.
I have parent component:
class DropdownGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [], // we have empty array, that pass to CategoryDropdown
};
this.onAddCategory = this.onAddCategory.bind(this);
}
onAddCategory(newCategory) {
this.setState(() => ({
categories: newCategory,
}));
}
onSelectCategory(path) {
this.props.onChangeEvents(path);
}
render() {
const months = ['January', 'February' ... ];
const eventsType = ['Party', 'Karaoke ... ];
const { categories } = this.state;
return (
<ButtonToolbar className="justify-content-center pb-4 pt-4">
{ console.log(categories) }
<CategoryDropdown
items={eventsType}
homePath="events"
path="events/categories/"
categories={categories} // here we pass our empty array (or updated later)
addCategories={this.onAddCategory} // this is what helps to update our array
onApply={(path) => this.onSelectCategory(path)}
/>
<MyDropdown
id="sort-by-month"
name="By month"
items={months}
onSelect={(e) => this.onSelectCategory(`events/month/${e}`)}
/>
<DropdownWithDate
oneDate="events/date/"
rangeDate="events/dates?from="
onApply={(path) => this.onSelectCategory(path)}
/>
<Button
onClick={() => this.setState({ categories: [] })} // here we can reset the value of our array
className="m-button ml-5"
>
Reset
</Button>
</ButtonToolbar>
);
}
}
DropdownGroup.propTypes = {
onChangeEvents: PropTypes.any.isRequired,
};
export default DropdownGroup;
and this is child component
class CategoryDropdown extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
selected: this.props.categories, // here we get values from props (now empty, then updated values)
};
this.onVisibleChange = this.onVisibleChange.bind(this);
}
onVisibleChange(visible) {
this.setState({
visible: visible,
});
}
saveSelected(selectedKeys) {
this.setState({
selected: selectedKeys,
});
}
addCategories() {
this.props.addCategories(this.state.selected); // here props are updated
}
confirm() {
const { selected } = this.state;
this.addCategories(this.state.selected);
const { homePath, path } = this.props;
if (selected.length > 0) {
this.props.onApply(path + selected);
} else {
this.props.onApply(homePath);
}
this.onVisibleChange(false);
}
render() {
const { visible } = this.state;
const { items } = this.props;
const menu = (
<Menu
multiple
onSelect={(e) => { this.saveSelected(e.selectedKeys); }}
onDeselect={(e) => { this.saveSelected(e.selectedKeys); }}
>
{items.map((item) => (
<MenuItem
key={item.replace('\u0020', '\u005f').toLowerCase()}
>
{item}
</MenuItem>
))}
<Divider />
<MenuItem disabled>
<Container
className="text-center "
style={{
cursor: 'pointer',
pointerEvents: 'visible',
}}
onClick={() => {
this.confirm();
}}
>
Select
</Container>
</MenuItem>
</Menu>
);
return (
<Dropdown
trigger={['click']}
onVisibleChange={this.onVisibleChange}
visible={visible}
closeOnSelect={false}
overlay={menu}
>
<Button className="m-button">By Category</Button>
</Dropdown>
);
}
}
CategoryDropdown.propTypes = {
onApply: PropTypes.any.isRequired,
items: PropTypes.any.isRequired,
path: PropTypes.string.isRequired,
homePath: PropTypes.string.isRequired,
categories: PropTypes.array.isRequired,
addCategories: PropTypes.any.isRequired,
};
export default CategoryDropdown;
I am trying to persist the state of Button option value in localStorage when the user selects it and retrieve the state in componentDidMount method but I am getting a null value. When the user navigates back to the page then state value should be there. Can anyone tell me what's wrong with my code?
code::
import React, { Component } from "react";
import { Button } from "semantic-ui-react";
import { withRouter } from "react-router";
import Answers from "../Answers/Answers";
class Section extends Component {
state = {
que1: "",
que2: "",
que3: ""
};
handleClick = event => {
this.setState(
{
que1: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que1", que1);
console.log(this.state.que1);
}
};
handleClick2 = event => {
this.setState(
{
que2: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que2", que2);
console.log(this.state.que2);
}
};
handleClick3 = event => {
this.setState(
{
que3: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que3", que3);
console.log(this.state.que3);
}
};
componentDidMount() {
this.setState({
que1: localStorage.getItem("que1"),
que2: localStorage.getItem("que2"),
que3: localStorage.getItem("que3")
});
}
render() {
console.log(this.state);
let styles = {
width: '50%',
margin: '0 auto',
marginBottom: '15px'
}
const { history } = this.props;
const { que1, que2, que3 } = this.state;
return (
<>
<p>1. I was stressed with my nerves on edge.</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que1} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>2. I lost hope and wanted to give up when something went wrong.</p>
<Button.Group widths="5" onClick={this.handleClick2} style={styles}>
<Answers selected={this.state.que2} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>3. I feel very satisfied with the way I look and act</p>
<Button.Group widths="5" onClick={this.handleClick3} style={styles}>
<Answers selected={this.state.que3} style={{ backgroundColor: 'red' }} />
</Button.Group>
<p />
{` `}
<Button
disabled={!que1 || !que2 || !que3}
onClick={() => history.push("/section2", [this.state])}
>
NEXT
</Button>
</>
);
}
}
export default withRouter(Section);
output ::
In your onclick handlers, you have:
handleClick2 = event => {
this.setState(
{
que2: event.target.attributes.getNamedItem("data-key").value
}
),
() => {
localStorage.setItem("que2", que2);
console.log(this.state.que2);
}
};
In the setState callback, you need to change it to(you logged the correct value, but didn't store it properly):
localStorage.setItem("que2", this.state.que2);
This is not supposed to be the React way of doing it. You should implement a redux workflow here which will help you persist data when navigating between pages. Refer https://redux.js.org/basics/usage-with-react. And when you click on the back button, you can retrieve the values back from the redux store.
Now, in your case change it to
handleClick(e) => {
const value = event.target.attributes.getNamedItem('data-key').value;
this.setState({que1 : value});
localStorage.setItem('que1', value);
};
And in the constructor
constructor() {
this.state = {
que1 : localStorage.getItem('que1') ? localStorage.getItem('que1') : ''
};
};
But this above solution is not at all recommended. Go ahead with the redux approach.
The callback function (lies in Images component) is responsible for making a state update. I'm passing that function as props to the Modal component, and within it it's being passed into the ModalPanel component.
That function is used to set the state property, display, to false which will close the modal. Currently, that function is not working as intended.
Image Component:
class Images extends Component {
state = {
display: false,
activeIndex: 0
};
handleModalDisplay = activeIndex => {
this.setState(() => {
return {
activeIndex,
display: true
};
});
};
closeModal = () => {
this.setState(() => {
return { display: false };
});
}
render() {
const { imageData, width } = this.props;
return (
<div>
{imageData.resources.map((image, index) => (
<a
key={index}
onClick={() => this.handleModalDisplay(index)}
>
<Modal
closeModal={this.closeModal}
display={this.state.display}
activeIndex={this.state.activeIndex}
selectedIndex={index}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</Modal>
</a>
))}
</div>
);
}
}
export default Images;
Modal Component:
const overlayStyle = {
position: 'fixed',
zIndex: '1',
paddingTop: '100px',
left: '0',
top: '0',
width: '100%',
height: '100%',
overflow: 'auto',
backgroundColor: 'rgba(0,0,0,0.9)'
};
const button = {
borderRadius: '5px',
backgroundColor: '#FFF',
zIndex: '10'
};
class ModalPanel extends Component {
render() {
const { display } = this.props;
console.log(display)
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
</div>
);
return <div>{display ? overlay : null}</div>;
}
}
class Modal extends Component {
render() {
const {
activeIndex,
children,
selectedIndex,
display,
closeModal
} = this.props;
let modalPanel = null;
if (activeIndex === selectedIndex) {
modalPanel = (
<ModalPanel display={this.props.display} closeModal={this.props.closeModal} />
);
}
return (
<div>
{modalPanel}
{children}
</div>
);
}
}
export default Modal;
links to code
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Images.js
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Modal.js
You're dealing with this modal through a very non-react and hacky way.
Essentially, in your approach, all the modals are always there, and when you click on image, ALL modals display state becomes true, and you match the index number to decide which content to show.
I suspect it's not working due to the multiple children of same key in Modal or Modal Panel.
I strongly suggest you to ditch current approach. Here's my suggestions:
Only a single <Modal/> in Images component.
Add selectedImage state to your Images component. Every time you click on an image, you set selectedImage to that clicked image object.
Pass selectedImage down to Modal to display the content you want.
This way, there is only ONE modal rendered at all time. The content changes dynamically depending on what image you click.
This is the working code I tweaked from your repo:
(I'm not sure what to display as Modal content so I display public_id of image)
Images Component
class Images extends Component {
state = {
display: false,
selectedImage: null
};
handleModalDisplay = selectedImage => {
this.setState({
selectedImage,
display: true
})
};
closeModal = () => {
//shorter way of writing setState
this.setState({display: false})
}
render() {
const { imageData, width } = this.props;
return (
<div>
<Modal
closeModal={this.closeModal}
display={this.state.display}
selectedImage={this.state.selectedImage}
/>
{imageData.resources.map((image, index) => (
<a
//Only use index as key as last resort
key={ image.public_id }
onClick={() => this.handleModalDisplay(image)}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</a>
))}
</div>
);
}
}
Modal Component
class Modal extends Component {
render() {
const { display, closeModal, selectedImage } = this.props;
const overlayContent = () => {
if (!selectedImage) return null; //for when no image is selected
return (
//Here you dynamically display the content of modal using selectedImage
<h1 style={{color: 'white'}}>{selectedImage.public_id}</h1>
)
}
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
{
//Show Modal Content
overlayContent()
}
</div>
);
return <div>{display ? overlay : null}</div>;
}
}