react does not re-render on context change - reactjs

I create a context, use context in consumer, after update, the interface is not re-rendering. Please help.
The reason that I am not using state is because the Sample1 will have to pass down the component tree and it will be hard to maintain.
Another problem I am having is at onChange={updateContext}. Is there a way to pass in some parameters into the updateContext so that it knows which question and answer to be updated?
// Dependencies
import React, {useContext} from "react";
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
// CSS
import './App.css';
// API
import Sample1 from "./api/Sample1";
const UserContext = React.createContext();
function updateContext() {
console.log(Sample1);
Sample1.Questions[0].Answers[0].Selected = !Sample1.Questions[0].Answers[0].Selected;
}
function Selection(props) {
const userContext = useContext(UserContext);
return (
<UserContext.Consumer>
{() => (
<FormGroup row>
<FormControlLabel
control={<Checkbox checked={userContext.Questions[0].Answers[0].Selected} onChange={updateContext} name="Answers0" />}
label={userContext.Questions[0].Answers[0].Answer}
/>
</FormGroup>
)}
</UserContext.Consumer>
);
}
function App(props) {
return (
<UserContext.Provider value={Sample1}>
<Selection>
</Selection>
</UserContext.Provider>
);
}
export default App;

The way your are updating state will not re-render component. Please try below solution.
const { useContext, setContext } = useContext(UserContext);
And while updating..
function updateContext() {
setContext('New Value Here')// new Logic : Update whatever your context value is
console.log(Sample1);
Sample1.Questions[0].Answers[0].Selected = !Sample1.Questions[0].Answers[0].Selected;
}

Related

How can I update state in two separate components using a custom hook?

I am trying to create a custom hook to be able to open and close a pop-out menu with conditional rendering using style display: none and display:block. I think I understand how to share the state between the components (I can console log that and get that working) , but I can not figure out how to update the state using the hook.
I am certain that I have some fundamental misunderstanding here but if anyone can clarify what it is I am trying to achieve that would be awesome! I have tried to learn this for several nights and here is where I have got to.
This is the header of the pop out menu it only contains a close button at the moment
import React from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faWindowClose } from '#fortawesome/free-solid-svg-icons'
import useOpenCloseElementMenu from '../Hooks/openCloseElementMenu'
function ElementMenuHeader() {
const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();
return (
<div id="App-Close-Element-Menu-Container">
<button id="App-Close-Element-Menu"
onClick={() => setElementMenuOpenClose(false) }
>
<FontAwesomeIcon icon={faWindowClose} />
</button>
</div>
);
}
export default ElementMenuHeader
This is the pop out menu
import React from 'react';
import SizerGroup from '../Sizer/sizerGroup';
import './element-menu.css';
import ElementMenuHeader from './element-menu-header';
import TitleWithLine from './title-with-line';
import TypeSelector from './type-selector';
import TemplateSelector from './template-selector';
import useOpenCloseElementMenu from '../Hooks/openCloseElementMenu'
function Editor(props) {
const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();
console.log(elementMenuOpenClose);
return (
<div className="App-Element-Menu"
style={{display: elementMenuOpenClose ? 'block' : 'none' }}
>
<ElementMenuHeader />
<TitleWithLine title="Element size" />
<SizerGroup />
<TitleWithLine title="Elements" />
<TypeSelector />
<TitleWithLine title="Templates" />
<TemplateSelector />
</div>
);
}
export default Editor
This is the toolbar that has the open menu button
import React from 'react'
import Button from '../Button/button'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faBoxes } from '#fortawesome/free-solid-svg-icons'
import './toolbar.css'
import useOpenCloseElementMenu from '../Hooks/openCloseElementMenu'
function Toolbar(props) {
const { toolbar_show_or_hide } = props
const elementMenuIcon = <FontAwesomeIcon icon={ faBoxes } />
const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();
const openEditor = setElementMenuOpenClose[true]
return (
<div className="App-Toolbar" style={{ display: toolbar_show_or_hide ? "flex" : "none" }} >
<Button
id="App-Open-Element-Menu-Button"
icon={ elementMenuIcon }
useToolTip={ true }
toolTipText="Elements menu. Select elements to populate the theme."
buttonFunction={ openEditor }
/>
</div>
)
}
export default Toolbar
This is the hook
import React, { useState } from 'react';
const useOpenCloseElementMenu = () => {
const [elementMenuOpenClose, setElementMenuOpenClose] = useState(false);
return { elementMenuOpenClose, setElementMenuOpenClose };
};
export default useOpenCloseElementMenu;
I feel you donot have to pass the hook in a separate function, useOpenCloseElementMenu, like you did.
Instead of importing the ../Hooks/openCloseElementMenu function thing,
I'd rather just call the hooks directly instead as
const [elementMenuOpenClose, setElementMenuOpenClose] = useState(false);
in the editor and toolbar component in place of const { elementMenuOpenClose, setElementMenuOpenClose } = useOpenCloseElementMenu();.
Also which component did you use the toolbar component if I may ask? Because I don't seem to see any of that here...It confusing where it got the toolbar_show... props from.
{I hope this helps cause that seem like the most obvious reason}

React useContext not triggering a re-render

I have set a three part Component for a filter menu. Component A) is the createContext which has an object with my globalData data and a function setData to change the data. When setData is triggered it retrieves data from the DB and updates the my globalData.data. Component B) Triggers component A and passes the appropriate values (This part works). Component C) is the useContext where it retrieves the data and users .Provider to display the data.
The problem is that Component C does not re-render any component.
Component A) Creates context and function to change context
import React, { createContext } from 'react';
import APIEndpoint from 'src/js/api/apAPIEndpoint.js';
const globalData={data:{kpi:[]}, filters:{date:'', login:'9166', country:[], city:[]}, setData:(field, value)=>{
globalData.filters = {...globalData.filters,[field]:value};
let fetchURL = `/APIURL?alias=${parseInt(globalData.filters.login)}&${globalData.filters.country.map((item)=>('country='+item.country)).join('&')}&${globalData.filters.city.map((item)=>('city='+item.city)).join('&')}`;
if(globalData.filters.login && globalData.filters.country.length>0 && globalData.filters.city.length>0){
APIEndpoint
.get(fetchURL)
.then(res => {
globalData.data={...globalData, data:res.data.data};
});
}
}
};
const GlobalDataContext = createContext(globalData);
export default GlobalDataContext;
Component B) Triggers context change in (A)
import React, {useContext} from 'react';
import GlobalDataContext from '/GlobalDataContext';
const globalData = useContext( GlobalDataContext );
const setGlobalData = globalData ? globalData.setData : null;
...
return(
<div>
<StyledSelect
onChange={(value) =>{
setGlobalData(props.valueField, value);
}} />
</div>
)
Component C) This is where it does not re-render. only want to re render one component under Section 2
import React, { useContext } from 'react';
import GlobalDataContext from '/GlobalDataContext';
const {data, setData} = useContext( GlobalDataContext );
...
return (
<>
<Helmet>
<title>Something - Home</title>
</Helmet>
<StencilResponsiveDesign sizes={[VIEWPORT_SIZES.S, VIEWPORT_SIZES.M]}>
...
{/* Section 1 */}
<FilterBar />
<Spacer height={50} />
{/* Section 2 */}
<GlobalDataContext.Provider value={globalData.data.kpi}>
<div>
<KpiWidget />
</div>
</GlobalDataContext.Provider>

Invalid hook call. Hooks can only be called inside of the body of a function component when using Hooks

I am not able to use React hooks. I have 4 components:
ComponentA
componentC
componentE
componentF
I need to pass value to componentF directly from componentA without having to pass from componentC and componentE. All components are in a single tree.
// componentA
import React from 'react';
import './App.css';
import ComponentC from "./components/ComponentC";
export const UserContext = React.useContext();
function App() {
return (
<div className="App">
<UserContext.Provider>
<ComponentC value={'My message'}/>
</UserContext.Provider>
</div>
);
}
export default componentA;
// componentF
import React from 'react';
import UserContext from '../App';
function ComponentF() {
return (
<div>
<UserContext.Consumer>
{
user => {
return (
<div>you are {user}</div>
)
}
}
</UserContext.Consumer>
</div>
)
}
export default ComponentF;
It is giving an error when I am trying to use context:
Invalid hook call. Hooks can only be called inside of the body of a function component.
The mistake is that you are using React.useContext()
You should be doing:
export const UserContext = React.createContext();
To pass to the child components of Component A, you can do it like this:
<UserContext.Provider value={100}>
<ComponentC .../>
</UserContext.Provider>
And instead of using UserContext.Consumer, you can get the value using React.useContext inside the component body of ComponentF.
// componentF
import React from 'react';
import {UserContext} from '../App';
function ComponentF() {
const value = React.useContext(UserContext);
return (
<div>
....
{value} // which will be equal to 100
</div>
)
}
export default ComponentF;

Is there a way to force multiple Context Consumers to share state?

I'm using the React Context API with the main intent of avoiding prop drilling. Right now my Context includes a useState and various functions that update the state - these are put into a const object that is passed as the value prop of ActionsContext.Provider. This is an abstraction of my current component hierarchy:
Header
---NavPanel
ContentContainer
---Content (Context.Consumer being returned in this component)
where Header and ContentContainer are sibling elements and NavPanel and ContentContainer are their respective children.
I initially put the Context.Consumer in Content because the other elements did not need it. However I'm building a feature now where NavPanel needs to know about the state that's managed by the Context. So I put another Consumer in NavPanel, only to find that a separate Consumer means a separate instance of the state.
Is there any smart workaround that gives NavPanel and Content access to the same state, that doesn't involve putting the Consumer in the parent component of Header and Content? That would result in a lot of prop drilling with the way my app is currently structured.
Codesandbox example of multiple instances: https://codesandbox.io/s/context-multiple-consumers-v2wte
Several things:
You should have only one provider for every state you want to share.
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
It is better to split your context in several contexts so you pass values instead of objects. This way when you update your state React will detect it is different instead of comparing the same object.
Your input should be a controlled component https://reactjs.org/docs/forms.html
Consider using the useContext API for better ergonomics if you are using React 16.8 instead of ContextConsumer.
With these changes, your code would be:
MyContext.js
import React, { useState } from "react";
export const MyItemContext = React.createContext();
export const MySetItemContext = React.createContext();
export const MyHandleKeyContext = React.createContext();
const ContextProvider = props => {
const [itemBeingEdited, setItemBeingEdited] = useState("");
const handleKey = event => {
if (event.key === "Enter") {
setItemBeingEdited("skittles");
} else if (event.key === "K") {
setItemBeingEdited("kilimanjaro");
} else {
setItemBeingEdited("");
}
};
const editFunctions = {
itemBeingEdited,
setItemBeingEdited,
handleKey
};
return (
<MyItemContext.Provider value={itemBeingEdited}>
<MyHandleKeyContext.Provider value={handleKey}>
<MySetItemContext.Provider value={setItemBeingEdited}>
{props.children}
</MySetItemContext.Provider>
</MyHandleKeyContext.Provider>
</MyItemContext.Provider>
);
};
export default ContextProvider;
PartOne.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartOne = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
placeholder="Type in me"
onKeyDown={handleKey}
value={itemBeingEdited}
onChange={e => setItem(e.target.value)}
/>
</React.Fragment>
);
};
export default PartOne;
PartTwo.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartTwo = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
value={itemBeingEdited}
type="text"
placeholder="Type in me"
onChange={e => setItem(e.target.value)}
onKeyDown={handleKey}
/>
</React.Fragment>
);
};
export default PartTwo;
index.js
import React from "react";
import ReactDOM from "react-dom";
import PartOne from "./PartOne";
import PartTwo from "./PartTwo";
import ContextProvider from "./MyContext";
import "./styles.css";
function App() {
return (
<div className="App">
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox: https://codesandbox.io/s/context-multiple-consumers-vb9oj?fontsize=14

React exporting withRouter and withStyles error

I am using react along with redux and material-ui to make a component. I am attempting to write an export statement export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
However, this doesn't seem to work I get an error that says
TypeError: Cannot set property 'props' of undefined
this.props = props;
This error is referencing one of my node_modules.
Here is my full code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {withRouter} from 'react-router-dom'
import { withStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
const styles = theme =>({
root: {
maxWidth: 345,
},
})
class FirstPage extends Component {
state = {
feeling: ''
}
//This function will dispatch the users response to index.js
//The dispatch type here is 'SET_FEELING'
submitData=(event) => {
event.preventDefault();
this.props.dispatch({type: 'SET_FEELING', payload: this.state})
this.changeLocation();
}
//This function will update the local state with the users response
handleChange= (event) => {
this.setState({
feeling: event.target.value
})
}
//This function will change the current url when a button is clicked
changeLocation= ()=> {
this.props.history.push('/secondPage')
}
render(){
const { classes } = this.props;
return(
<div>
<Card >
<CardContent className={classes.root}>
<form>
<input onChange={this.handleChange} placeholder='How are you feeling' value={this.state.feeling} />
</form>
</CardContent>
<CardActions>
<Button onClick={this.submitData}>Submit</Button>
</CardActions>
</Card>
</div>
)
}
}
//this export connects the component to the reduxStore as well as allowing us to use the history props
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
I believe the following code should work:
export default withRouter(connect()(withStyles(styles)(FirstPage)))
Instead of
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
First of all, connect() returns a function that only accepts an argument. Second, connect() should be wrapped inside withRouter(). This problem is stated in the github docs of React Router.
without using react-redux :
export default (withStyles(styles), withRouter)(FirstPage);

Resources