Updating Context from a Nested Component is not Working - javascript

I am new to react, I am trying to pass theme string and and toggleTheme function from parent to child using Context API in react.I am practicing example from React Doc with little modification https://reactjs.org/docs/context.html
my code is as following:
import React from 'react';
const ThemeContext = React.createContext({
theme: 'light',
toggleTheme: () => {}
})
class MouseTracker2 extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === 'dark'
? 'light'
: 'dark',
}));
};
this.state={
theme: 'dark',
toggleTheme: this.toggleTheme
}
}
render() {
return (
<div>
<ThemeContext.Provider value={this.state}>
<Abc />
</ThemeContext.Provider>
</div>
)
}
}
class Abc extends React.Component {
render() {
return (
<div>
<ThemeContext.Consumer>
{({theme,toggleTheme}) => {return(<Def theme={theme} onClick=
{toggleTheme} />)}}
</ThemeContext.Consumer>
</div>
)
}
}
class Def extends React.Component {
render() {
return (
<div>
<p>efgh</p>
<div>{this.props.theme}</div>
</div>
)
}
}
export default MouseTracker2
In above Code, Context is passing string from parent to child properly. However, it is not passing function from parent to child.
Thanks in Advance :)

The toggleTheme function is passed on to Def by the name onClick and hence this.props.toggleTheme is unavailable and can be accessed by this.props.onClick
class MouseTracker2 extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme: state.theme === "dark" ? "light" : "dark"
}));
};
this.state = {
theme: "dark",
toggleTheme: this.toggleTheme
};
}
render() {
return (
<div>
<ThemeContext.Provider value={this.state}>
<Abc />
</ThemeContext.Provider>
</div>
);
}
}
class Abc extends React.Component {
render() {
return (
<div>
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => {
return <Def theme={theme} onClick={toggleTheme} />;
}}
</ThemeContext.Consumer>
</div>
);
}
}
class Def extends React.Component {
render() {
return (
<div>
<p>efgh</p>
<div>{this.props.theme}</div>
<button onClick={this.props.onClick}>Toggle</button>
</div>
);
}
}
Working Codesandbox

Related

React ScrollIntoView not working from parent component

I have a parent component that has a button. When that button is clicked, it should scroll to a grid in the child component, but it is not working. There hasn't been any errors. This is what I have in the parent component:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
someState: undefined,
};
this.ref_grid = React.createRef();
}
handleClick = () => {
this.setState({
someState: newState,
}, () =>
{
if (this.ref_grid.current !== null) {
this.ref_grid.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}
);
}
render() {
return (
<>
<Button
variant="contained"
color="secondary"
size="small"
onClick={() => this.handleClick()}
>Click me!</Button>
<ChildComponent
forwardRef={this.ref_grid}
/>
</>
);
}
}
In the child component I have the ref:
class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
render() {
const {
classes
} = this.props;
return (
<Grid container spacing={3} ref={this.props.forwardRef}>
</Grid>
)
}
}
I am new to React, so I am not sure if this is the right approach. Would appreciate if anyone has any idea or example how to solve this.
Thank you in advance.

React Context api - Consumer Does Not re-render after context changed

I searched for an answer but could not find any, so I am asking here,
I have a consumer that updates the context,
and another consumer that should display the context.
I am using react with typescript(16.3)
The Context(AppContext.tsx):
export interface AppContext {
jsonTransactions: WithdrawTransactionsElement | null;
setJsonTran(jsonTransactions: WithdrawTransactionsElement | null): void;
}
export const appContextInitialState : AppContext = {
jsonTransactions: null,
setJsonTran : (data: WithdrawTransactionsElement) => {
return appContextInitialState.jsonTransactions = data;
}
};
export const AppContext = React.createContext(appContextInitialState);
The Producer(App.tsx):
interface Props {}
class App extends React.Component<Props, AppContext> {
state: AppContext = appContextInitialState;
constructor(props : Props) {
super(props);
}
render() {
return (
<AppContext.Provider value={this.state}>
<div className="App">
<header className="App-header">
<SubmitTransactionFile/>
<WithdrawTransactionsTable />
</header>
</div>
</AppContext.Provider>
);
}
}
export default App;
The updating context consumer(SubmitTransactionFile.tsx)
class SubmitTransactionFile extends React.Component {
private fileLoadedEvent(file: React.ChangeEvent<HTMLInputElement>, context: AppContext): void{
let files = file.target.files;
let reader = new FileReader();
if (files && files[0]) {
reader.readAsText(files[0]);
reader.onload = (json) => {
if (json && json.target) {
// #ts-ignore -> this is because result field is not recognized by typescript compiler
context.setJsonTran(JSON.parse(json.target.result))
}
}
}
}
render() {
return (
<AppContext.Consumer>
{ context =>
<div className="SubmitTransactionFile">
<label>Select Transaction File</label><br />
<input type="file" id="file" onChange={(file) =>
this.fileLoadedEvent(file, context)} />
<p>{context.jsonTransactions}</p>
</div>
}
</AppContext.Consumer>
)
}
}
export default SubmitTransactionFile;
and finaly the display consumer(WithdrawTransactionsTable.tsx):
class WithdrawTransactionsTable extends React.Component {
render() {
return (
<AppContext.Consumer>
{ context =>
<div>
<label>{context.jsonTransactions}</label>
</div>
}
</AppContext.Consumer>
)
}
}
export default WithdrawTransactionsTable;
It is my understanding that after fileLoadedEvent function is called the context.setJsonTran should re-render the other consumers and WithdrawTransactionsTable component should be re-rendered , but it does not.
what am I doing wrong?
When you update the state, you aren't triggering a re-render of the Provider and hence the consumer data doesn't change. You should update the state using setState and assign context value to provider like
class App extends React.Component<Props, AppContext> {
constructor(props : Props) {
super(props);
this.state = {
jsonTransactions: null,
setJsonTran: this.setJsonTran
};
}
setJsonTran : (data: WithdrawTransactionsElement) => {
this.setState({
jsonTransactions: data
});
}
render() {
return (
<AppContext.Provider value={this.state}>
<div className="App">
<header className="App-header">
<SubmitTransactionFile/>
<WithdrawTransactionsTable />
</header>
</div>
</AppContext.Provider>
);
}
}
export default App;
Your setJsonTran just mutates the default value of the context which will not cause the value given to the Provider to change.
You could instead keep the jsonTransactions in the topmost state and pass down a function that will change this state and in turn update the value.
Example
const AppContext = React.createContext();
class App extends React.Component {
state = {
jsonTransactions: null
};
setJsonTran = data => {
this.setState({ jsonTransactions: data });
};
render() {
const context = this.state;
context.setJsonTran = this.setJsonTran;
return (
<AppContext.Provider value={context}>
<div className="App">
<header className="App-header">
<SubmitTransactionFile />
<WithdrawTransactionsTable />
</header>
</div>
</AppContext.Provider>
);
}
}
const AppContext = React.createContext();
class App extends React.Component {
state = {
jsonTransactions: null
};
setJsonTran = data => {
this.setState({ jsonTransactions: data });
};
render() {
const context = this.state;
context.setJsonTran = this.setJsonTran;
return (
<AppContext.Provider value={context}>
<div className="App">
<header className="App-header">
<SubmitTransactionFile />
<WithdrawTransactionsTable />
</header>
</div>
</AppContext.Provider>
);
}
}
class SubmitTransactionFile extends React.Component {
fileLoadedEvent(file, context) {
let files = file.target.files;
let reader = new FileReader();
if (files && files[0]) {
reader.readAsText(files[0]);
reader.onload = json => {
if (json && json.target) {
// slice just to not output too much in this example
context.setJsonTran(json.target.result.slice(0, 10));
}
};
}
}
render() {
return (
<AppContext.Consumer>
{context => (
<div className="SubmitTransactionFile">
<label>Select Transaction File</label>
<br />
<input
type="file"
id="file"
onChange={file => this.fileLoadedEvent(file, context)}
/>
<p>{context.jsonTransactions}</p>
</div>
)}
</AppContext.Consumer>
);
}
}
class WithdrawTransactionsTable extends React.Component {
render() {
return (
<AppContext.Consumer>
{context => (
<div>
<label>{context.jsonTransactions}</label>
</div>
)}
</AppContext.Consumer>
);
}
}
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"></div>

How to change Parent component's state from Child component if it's rendering with map()

i have a problem - i need to change parent component's state from child component. I tried standard variant with props, but it's not helping in my case.
Here is code of Parent component:
class ThemesBlock extends React.Component {
constructor(props){
super(props);
this.state = {
currentThemeId: 0
}
}
changeState(n){
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
data.map(function (item) {
return <Theme key={item.id} data={item} changeState=
{item.changeState}/>
})
)
}
}
And here is my code for Child Component:
class Theme extends React.Component {
constructor(props){
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const { id, themename } = this.props.data;
const link = '#themespeakers' + id;
return (
<li><a href={link} onClick={() => this.changeState(id)}
className="theme">{themename}</a></li>
)
}
}
The primary issue is that changeState should be bound to the ThemesBlock instance, not to the Theme instance (by ThemesBlock).
Here's an example where I've bound it in the ThemesBlock constructor (I've also updated it to show what theme ID is selected):
class ThemesBlock extends React.Component {
constructor(props) {
super(props);
this.state = {
currentThemeId: 0
}
this.changeState = this.changeState.bind(this);
}
changeState(n) {
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
<div>
<div>
Current theme ID: {this.state.currentThemeId}
</div>
{data.map(item => {
return <Theme key={item.id} data={item} changeState={this.changeState} />
})}
</div>
)
}
}
class Theme extends React.Component {
constructor(props) {
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const {
data: {
id,
themename
},
changeState
} = this.props;
const link = '#themespeakers' + id;
return (
<li>{themename}</li>
)
}
}
const data = [
{id: 1, themename: "One"},
{id: 2, themename: "Two"},
{id: 3, themename: "Three"}
];
ReactDOM.render(
<ThemesBlock data={data} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Did you try to move map() before return? Like this:
render() {
let { data } = this.props;
const outputTheme = data.map(function (item) {
return <Theme key={item.id} data={item} changeState= {item.changeState}/>
})
return (
{outputTheme}
)
}

React Handle interaction state between components

I have a simple component who show element onClick:
class MyComponent extends React.Component {
state = {
isVisible : false
}
render() {
const { isVisble } = this.state
return(
<div>
{isVisble ?
<div onClick={() => this.setState({isVisble: false})}>Hide</div> :
<div onClick={() => this.setState({isVisble: true})}>Show</div>}
</div>
)
}
}
I use this component three times in other component :
class MySuperComponent extends React.Component {
render() {
return(
<div>
<MyComponent />
<MyComponent />
<MyComponent />
</div>
)}
}
I need to pass isVisible at false for all other component if one of have isVisible to true
How to do that ?
Thanks
You should have your component controlled, so move isVisble to props and and then assign it from MySuperComponent.
Also pass MyComponent a callback so it can inform the parent if it wants to change the state.
You'd want some data structure to store that states.
https://codepen.io/mazhuravlev/pen/qxRGzE
class MySuperComponent extends React.Component {
constructor(props) {
super(props);
this.state = {children: [true, true, true]};
this.toggle = this.toggle.bind(this);
}
render() {
return (
<div>
{this.state.children.map((v, i) => <MyComponent visible={v} toggle={() => this.toggle(i)}/>)}
</div>
)
}
toggle(index) {
this.setState({children: this.state.children.map((v, i) => i !== index)});
}
}
class MyComponent extends React.Component {
render() {
const text = this.props.visible ? 'visible' : 'hidden';
return (<div onClick={this.props.toggle}>{text}</div>);
}
}
React.render(<MySuperComponent/>, document.getElementById('app'));
You can check your code here, is this what you want.
example

How can I change the fontweight of an item at click method - React

I have a react code below where I iterate over in an array of grocery list. I created a state change and a style variable that will change only one item at a time when clicked.
However it did not work. For some reason when I click on one item it turns all of them bold.
const App = () => (
<div><GroceryListItem /></div>
);
class GroceryListItem extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div><GroceryList groceryItems={['Cucumber', 'Kale']}/></div>
);
}
}
class GroceryList extends React.Component {
constructor(props){
super(props);
this.state = {
done: false
};
}
onClickItem(){
this.setState({
done: !this.state.done
});
}
render(){
var style = {
fontWeight: this.state.done ? 'bold' : 'normal'
};
return (
<ul>
{
this.props.groceryItems.map(item => <li style={style} onClick={this.onClickItem.bind(this)} key={item}>{item}</li>)
}
</ul>
);
}
}
Any idea why is this not working and how to fix it?
PS. Suggestions on how to improve my code is appreciated.
Store the css variable in state and change on onClick
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => (
<div><GroceryListItem /></div>
);
class GroceryListItem extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div><GroceryList groceryItems={['Cucumber', 'Kale']}/></div>
);
}
}
class GroceryList extends React.Component {
constructor(props){
super(props);
this.state = {
done: false,
style: "normal"
};
}
onClickItem(item){
let style = {
[item]: "bold"
}
this.setState({
done: !this.state.done,
style: style
},() => {});
}
render(){
return (
<ul>
{
this.props.groceryItems.map(item => {
{console.log(this.state.style[item],"this.state")}
return (<li style={{fontWeight: this.state.style[item] || "normal"}} id={item} onClick={this.onClickItem.bind(this,item)} key={item}>{item}</li>
)
})
}
</ul>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
You are changing the state onClick, and changing the style variable which is accessible to the complete render function. You just need to know which of the element is clicked and store it in your state. You can try the below method.
class GroceryList extends React.Component {
constructor(props){
super(props);
this.state = {
selected: null
};
}
onClickItem (item) {
this.setState({
selected: item
})
}
render(){
return (
<ul>
{
this.props.groceryItems.map(item => <li style={{'fontWeight': this.state.selected === item ? 'bold' : 'normal'}} onClick={this.onClickItem.bind(this, item)} key={item}>{item}</li>)
}
</ul>
)
}
}

Categories