React Context Consumer Does Not Update Attributes - reactjs

I can successfully see how React Context updates my values in the Consumer for the text, but it fails to update values inside any attributes. Basically, Consumer seems to keep old default values when inline, generated during the build, while it updates text inside tags just fine.
Here is what I have:
appcontext.js:
import React from 'react';
const AppContext = React.createContext({});
export default AppContext;
layout.js:
import AppContext from "./AppContext"
import PlaceHelper from "./placehelper"
//...
const placeHeloper = PlaceHelper();
const currentPlaceData = placeHeloper.getCurrentPlaceData();
let [placeData, setPlaceData] = useState({ ...currentPlaceData })
//...
return (
<>
<AppContext.Provider value={placeData}>
<main>{children}</main>
</AppContext.Provider>
</>
)
then I use Consumer in the component:
home.js:
import AppContext from "../AppContext"
//...
<div id="content">
<AppContext.Consumer>
{ value =>
<header noupdate={value.banner} style={{backgroundImage: "url(" + value.banner + ")"}}>
{/* UPDATES BELOW SUCCESSFULLY */}
{value.banner}
//...
In an example above, the value.banner gets updated when it is inside the header tag but it fails to be updated when it is inside the style attribute or noupdate attribute. I cannot understand why.

Related

why is my component getting rendered once but then failing on refresh

i am working on small react assignment,
following is my component code. So my component is getting rendered once but then it just fails.i'll attach the screenshots too, can some one please explain what is happening?is there an error in the code or is it because of some rate limiting in API i am using?
import React from 'react'
const Menu = ({events}) => {
console.log(events);
return (
<div>
{events.map((event)=>{
return( <div key={event.category}>
<h3>{event.category}</h3>
</div>)
})}
</div>
)
}
export default Menu
code working image
error on same code pic
parent component code
import React,{useState,useEffect} from 'react';
import './App.css';
import Menu from './components/Menu';
function App() {
const [isLoading,setISLoading] = useState(true);
const[events,setEvents] = useState()
const getEvents = async()=>{
const response = await fetch('https://allevents.s3.amazonaws.com/tests/categories.json');
const eventsData =await response.json()
setISLoading(false);
setEvents(eventsData);
}
useEffect(()=>getEvents(),[]);
return (
isLoading?<h1>Loading...</h1>:<Menu events = {events}/>
);
}
export default App;
May be the parent component of Menu which is supplying events is not using any loading state. So when the component is mounted and starts making ajax calls, events is undefined. You need to put a condition over there like this:
import React from 'react'
const Menu = ({events}) => {
console.log(events);
return events ? (
<div>
{events.map((event)=>{
return( <div key={event.category}>
<h3>{event.category}</h3>
</div>)
})}
</div>
) : null
}
export default Menu

Pass state onto {children} React hook

So I have a component that looks like this:
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
And then I have another component that looks like this:
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container ;
Now, the thing is the {children} of the 1st component is sometimes the 2nd component, and sometimes it's not. Therefore I can't just put the CSS and HTML from the 2ndcomponent into the 1st component - which in turn would fix my problem.
But as you might be able to see, there is an onClick event in the first component. I would like it so that when that is clicked, the state from the click is send to the 2nd component and toggles the className-toggle.
Can this be achieved by doing this, or do I have to set everything up differently ?
And yes, I am quite new to React, so please don't be harsh.
Css
I would look into better methods of applying styling with css. Not sure about your project scope/tools but typically all the css files are imported in the dom root and loaded in there. This avoids creating css files for every component.
Here's 9 ways of implementing css for react.
Passing HTML
In react if you want to render component in another component instead of passing it as a child you should import it as follows.
// replace container path with actual path of Container file
// ex './Container.js'
import Container from 'container_path.js';
Now Rendering the Component is as simple as including it in the html code.
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
<Container/>
</div>
</>
);
Here's a Stack Overflow post of users importing components using react + es6 + webpack. More information on importing components is available there.
State management
In react if you have a state that is being accessed by multiple components the standard is to keep the state in the parent component.
This way you can pass the state as a prop to any children components. You can also create a function which updates this state and pass that function as a prop to the children.
ex:
import React, { useState } from "react";
import Container from "./Container.js";
import Navigation from "./Navigation.js"
const Parent = props => {
const [toggle, toggleState] = useState(false);
return (
<div>
<Container toggleState={toggleState} toggle={toggle} />
<Navigation toggleState={toggleState} toggle={toggle} />
</div>
)
}
Before continuing working on your project I would recommend researching functional components vs class components. Here's a helpful article.
Try to wrap second component to function with state from first component as argument.
Wrapper for your second component and using for first component
const putInnerComponent = (stateFromOuterComponent) => <Container toggle={stateFromOuterComponent}/>;
<Navigation children={putInnerComponent}/>
Your first component
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children(toggle)}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
Your second component
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children, toggle }) => {
//const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container;

How to add {props.children} to a React component

i have many components which have {props.children} deeply nested inside.
considery DRY principle is there a way to add this using some React pattern.
example
let's say i have two components,
Comp1.js
import React from "react";
const Comp1 = props => {
return (
<div>
<h1>{props.children}</h1>
</div>
);
};
export default Comp1;
Comp2.js
import React from "react";
const Comp2 = props => {
return (
<div>
<div>
<h1>{props.children}</h1>
</div>
</div>
);
};
export default Comp2;
if you see above code we have both Comp1 and Comp2 have line of code {props.children} repeated inside.
what i want now is some function which will add this line of code, something like below,
const addPropsChildrenToComp = (Comp)=>{
return(
(props)=>{
///do somehting here
}
)
}
const Comp1 = props => {
return (
<div>
<h1></h1>
</div>
);
};
Comp1WithPropsChildren = addPropsChildrenToComp(Comp1)
using HOC doesn't work because, in HOC we never modify passed component.
anyway to aceve this.?
to get more idea of my problem see this demo: https://codesandbox.io/s/trusting-http-pd1yu
in there i woul like to see CompWithPropsChildren component render props.children inside it.
I think I see what you're trying to get to, and you can accomplish this just using another component.
import React from "react";
import ChildComp from "./ChildComp";
const Comp1 = props => {
return (
<div>
<ChildComp {...props} />
</div>
);
};
export default Comp1;
import React from "react";
const ChildComp = props => {
return <h1>{props.children}</h1>
}
Assuming your ChildComp has some complex logic you don't want to duplicate, this will make it reusable for you.

ReactJs: Prevent Rerender of wrapped component

I'm trying to prevent a re-render when using custom hook for hours now -.-, need some help ;O|
(Dont know if I should call this custom hook or functional hoc though)
I have a MessageList component that display a SimpleMessage wrapped in WithAvatarHeader.
Here is my profiler result:
Every time I add a message to the list, all messages are rendered again.
This isn't happening when I only use SimpleMessage in MessageList
Is there a way to memo(WithAvatarHeader) ?
MessageList :
import React from "react";
import SimpleMessage from "./SimpleMessage";
import WithAvatarHeader from "./WithAvatarHeader";
const MessageList = props => {
const Message = WithAvatarHeader(SimpleMessage);
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
SimpleMessage:
import React, { memo } from "react";
const SimpleMessage = props => {
return (
<div className="simple-message">
{props.message}
</div>
);
};
export default memo(SimpleMessage);
WithAvatarHeader:
import React from "react";
const WithAvatarHeader = WrappedComponent => props => {
return <WrappedComponent {...props} />;
};
export default WithAvatarHeader;
Thanks for the help :-)
You should not declare component inside another component.
Once you move declaration outside:
const Message = WithAvatarHeader(SimpleMessage);
const MessageList = props => {
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
you will be fine.
Reason is reconciliation process that decides what's to drop, what to create and what to update.
Besides your JSX says it still same element <Message> React checks component's constructor(it does not work with text representation from JSX). And it will referentially different(since you re-declare this constructor on next render). So React drops every <Message> and create them from scratch. Keeping declaration outside your MessageList means constructor is referentially the same so React will not re-create <Message> till key is the same.

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

Resources