I’m working on a react app in which I want all of the user inputs to live inside a global json object. (This is a multi page form) This object lives inside the parent function (P) and uses useState (let’s call the functions obj and setObj). I wrap the children function with a useContext such that all children (a,b,c). <context.Provider>’s has value={{obj, setObj}}.
Function a grabs the context created in P and it consists:
Const {obj,setObj} = useContext(*context created in P*)
<TextField
…
OnChange= {((e)=>setObj((prev)=>({…prev, e.target.name: e.target.value}))}
name=“name”
Value = {obj[“name”]}
/>
(The syntax may be off here, but it works in my program.
The problem I have is that every time I try to type in the textField, it exits out of the textField (where I have to click the textField EVERYTIME to just type ONE character). I want to create a program where, if possible, children can update the obj in the global state as if the textField was in the same function.
I have implemented this in things like and components. It works perfectly, but I think it works here because those values are limited (To things like Y/N or elements in a list). I believe the error comes because we are always updating a variables from another function (which is what I want to happen).
Any suggestions would be of great help!
Thanks!
Related
I've been reading about the advantages of using Context in React and I am unconvinced. I'm wondering if there's something I've missed.
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
What's the hassle in creating a props object in the main component and just passing it around among the underlings? Something like:
// do this once at top level (I'm assuming [foo, foo_set] and [bar, bar_set] are state variables):
const props = {foo, foo_set, bar, bar_set, thisAndThat, theOther, whatever, etcAndEtc}
// including one component
<MyComponent1 {...props } />
// including another
<MyComponent2 {...props } />
(Maybe better to use another name than props for this object, as the components can have other properties. Anyway.)
Then in MyComponent1 you can access all the props you want, or not access them. Either:
...
const MyComponent1 = (props) => {
...
// here we can use any props we need, props.bar, props.bar_set, props.theOther for example
const localVar = props.bar * 2;
props.bar_set(localVar);
// this changes the value of bar throughout the app
...
}
the advantage of the above, as I see it, is that you can pass around the props object to other sub-sub-components and not worry about whether you have anything missing.
Or:
...
const MyComponent1 = ({bar, bar_set, theOther }) => {
...
// here we can use bar, bar_set, theOther in the same example
const localVar = bar * 2;
bar_set(localVar);
...
}
The advantage of this option being that the syntax is shorter.
So my point is why not just use the standard JavaScript syntax? Why introduce new concepts when there are plenty to assimilate to do all sorts of other things?
Consider a fairly common case for most applications: You have authentication information (eg, the current user), a routing library (eg, react-router), and a theme object (what colors to use). These are needed in components scattered throughout the app.
You want to render a button somewhere down at the tip of the component tree. It's going to show the user's avatar, so it needs the authentication data. It's going to navigate when clicked, so it needs the navigate function from the routing library. And it needs to style itself according to the theme.
This certainly can be done through props, but in order for the button to get the props, every component in the chain above it must get and forward those props too. This could be many components deep, like page component -> section component -> table -> row -> widget -> button, and most of them don't need that information for themselves, so they're just taking the props in order to forward it along.
And you can easily imagine cases where there are more than 3 pieces of data that are needed across the app.
What's the hassle
Most people find this "prop drilling" to be a hassle, but let's assume you don't. You still have the problem that it has bad performance. If every component must receive the full set of "global" values that the app might need, then any time anything changes, the entire app must rerender. Optimizations like react.memo become effectively useless. You will get much better performance if you only pass the props you need.
Easier to edit code (You don't have to delete for example unused variable)
Better redability (You dont see unnescesary variables, and You see which component is using variables)
Lesser performance waste (preventing from consuming unnescesarry variables)
Suppose You got 10 descendants in - You would have to pass one variable through 10 of components.
What if some could have the same variable name ? You would have to edit Your passed variable for a while, then edit back later.
To sum up:
Using Context more efficient than stuffing everything into a single object variable, because it avoids re-rendering the whole app when anything changes.
People think passing a single variable around is more hassle than introducing specific syntax.
Context also allows you to have different values for the same variable in different parts of the app. This is shown here (the best explanation IMHO) : https://beta.reactjs.org/learn/passing-data-deeply-with-context
The above article also specifies that sometimes passing props is the best solution. It gives a list of use cases for context, and the advantages provided in each case.
I'm having a little bit of difficulty figuring out how to reuse components in many places.
Obviously the reason why react is so good is because you can create common components and reuse them across many different pages, or just multiple times in the same page.
I have this component for displaying a dropdown that a user can use to select a new value, and change their settings. This component is reused in many different places on my website.
<ChannelSelect
channelID={
saveEnabled.saveEnabled.newSettings.verificationPanel.channelID
}
settings={
saveEnabled.saveEnabled.newSettings.verificationPanel.channelID
}
/>
What I am struggling with is telling this component which key: value in the state (object) to change. This is because simply passing in the code below, doesn't actually allow me to change the state of this specific key.
settings={saveEnabled.saveEnabled.newSettings.verificationPanel.channelID}
In my select menus onChange event I have the code snippet below. However, this doesn't actually allow me to change the specific value declare, it would actually just overwrite the whole object.
const { saveEnabled, setSaveEnabled } = useContext(SaveContext);
const onChange = (event) => {
props.settings = "newValue"
setSaveEnabled(props.settings)
Any idea how I can do this?
I simulated my Context + DND problem in https://codesandbox.io/s/adoring-booth-33vqo . I have other components which will be added to this example and I will use a Context hook to share values across the page.
After the initial render, everything looks fine. The idea of the list is to change the order within itself and when ones changes the order with drag-drop, it throws an "Invalid Hook" error.
So the (first) real question is, what is triggering this error which is linked to the line
const { lang1Library, updateLang1Library } = useContext(LangContext)
;
Thanks in advance for your help.
Geo
It's not a good approach to provide a link for the whole project even if it is small. But I had a quick look and there's at least one thing you're doing wrong:
// DragEndFct.js
export default function DragEndFct(result, libName) {
const { lang1Library, updateLang1Library } = useContext(LangContext);
This is not React component, but it uses a hook - and it is wrong. Hooks have a special meaning in React and should be used properly (Rules of Hooks).
You can't use hooks inside regular functions and expect them to work. That is why you are getting that error.
So, there are many ways you can try to fix this. For instance, DragEndFct is a regular function, you can declare more arguments and pass stuff you get from context:
// you are using it in components right ?
function DragEndFct(result, libName, param3, param4) {}
// so you probably have access to the context there
// and can pass data from the context when you call it.
// something like this
onDragEnd={function (result) {
console.log();
DragEndFct(result, StaticVars.LANG1_LIBRARY_NAME, lang1Library, updateLang1Library);
}}
You could even make DragEndFct to be a React component - it can just return null (which means no UI will be rendered) but in that case you will have hooks and all other stuff there. It really depends on what you need and how you will use it.
I have a React application using Material UI with a component (which we can call DatePicker) shown below, sneakily changed for demo purposes.
Material UI animates clicks and other interactions with its components. When clicking a radio button that has already been selected, or a "time button" which doesn't change state, this animation is visible above. However, when such a click changes the state, the animation get interrupted.
I can see why this happens from a technical perspective; the DatePicker component calls setMinutes, which is a property passed in from its parent (where the state lives). This is a React.useState variable, which then updates its corresponding minutes variable. Minutes is then passed into DatePicker, which re-renders due to a prop change.
If state lived within DatePicker then this problem shouldn't rear its head; however, DatePicker is one part of a much larger form which dictates the contents of a table in the parent. To generate rows for this table, the parent must have this information.
Below is a sample reconstruction of the parent:
const Parent = () => {
const [minutes, setMinutes] = React.useState(15);
const [radioOption, setRadioOption] = React.useState('Thank You');
// Many other state variables here to hold other filter information
return (<div>
<DatePicker minutes={minutes} setMinutes={setMinutes} radioOption={radioOption} setRadioOption={setRadioOption}/>
</div>);
};
And here a sample reconstruction of DatePicker:
const DatePicker: React.FC<DatePickerProps> = props => {
const {minutes, setMinutes, radioOption, setRadioOption} = props;
return (<div>
<Radios value={radioOption} onChange={val => setRadioOption(val)}/>
<Minutes value={minutes} onChange{val => setMinutes(val)}/>
</div>);
};
I'm not sure what the best practice is in this situation, but I get the distinct feeling that this is not it. Does anyone have any advice? Thanks in advance!
Thank you for your comment, Ryan Cogswell. I did create a code sandbox, and found that the problem was not about React state management as much as what I was doing beyond what I provided in my question.
I was using the withStyles HOC to wrap my component, in a way similar to const StyledDatePicker = withStyles(styles)(DatePicker). I then used that styled element and put properties (minutes, etc) on that.
It turns out that using the unstyled DatePicker resolves this issue. I troubleshooted this further, and found that I had created the "Styled" component within the "render" method of the parent, meaning every time a prop change was pushed up the chain, the parent would re-render and the entire "Styled" component type would be created again (or so I believe). This would break reference integrity, which explains the "drop and recreate" behaviour.
This teaches the valuable lesson of keeping components small and using code sandboxes for troubleshooting. Thanks again!
For anyone interested, here is the Code Sandbox used for testing.
Suppose I have something like this where both components A and D listen to changes in a global store :
import React from 'react'
import useStore from 'whatever-global-store-manager'
function A() {
const [store] = useStore()
if(!store.currentUser)
return <h1>You must log in</h1>
else return <B/>
}
function B() {
return <C/>
}
function C() {
return <D/>
}
function D() {
const [store] = useStore()
console.log(store.currentUser) // Can it be falsey ?
return <h1>{store.currentUser.name}</h1>
}
In A, when currentUser is falsey, B is not rendered, thus D is not rendered. But suppose this scenario :
At first, currentUser is defined as an object with a name property, so D renders, listens to changes in the store and renders the name.
Then, somewhere else in the app, currentUser is set to null.
In which order are the "listeners" processed ? Is there any chance that function D is executed with currentUser to null even when begin ultimately removed from the component tree ?
Another way to formulate the question : Should I check against currentUser in component D before accessing its name property ?
I was looking in the doc for a rule like "When two components listen to the same event, the one higher in the hierarchy is rendered first, and if it turns out the second one should be unmounted according the first's output, then the second one is never even called", but couldn't find anything. In practice, I know it works, but I would like to be sure that it's not just luck.
I believe this largely depends on the store observer mechanism, so it's hard to give a conclusive answer without knowing which store you're using. If observers are registered in order, that might affect how you need to deal with it.
If you wanna find out for sure, you could console.log your render methods, or use debugger while changing the value of currentUser.
Analysis of a hypotetical implementation: let's say an observer is registered when the component mounts, and unregistered when it unmounts. In this situation, the component A would trigger first (since it was registered first), and cause D to unmount, unregistering his trigger. In this hypothetical scenario, D wouldn't need to check for null.
Unrequested advice: a good thing for you might be centralizing the "data collection" in one parent component, while the children just receive that as props and render (without observing the store). I've found (both from lore and personal experience) that it simplifies a lot the development process.
Another way to formulate the question :
Should I check against currentUser in component D before accessing its name property ?
Yes, it is definitely a good decision: it is preferable that there is one redundant code line, instead of obtaining an error.
I was looking in the doc for a rule like
"When two components listen to the same event,
the one higher in the hierarchy is rendered first...
I think the opposite. Although I neither could find the specific documentation to explaine it, I remember that Components do not update like a cascade. That is the idea of the component oriented programming: each one is an independent entity.
Note: if I understand your example well, you could test this example by adding a setTimeout that wraps the return of function A, right? So this way you can then set currentUser as null and D wil be still rendered and you can see what happens.