Lately I've noticed that I'm writing a lot of React code were I have 2 components:
Data filter
Data display
In the first component I might have N possible filters to apply to the data. These filters often complex components on their own. When the user defines the filters I need to pass it from 1st component to the 2nd for display.
Example: https://codesandbox.io/s/wonderful-sutherland-16zhp8?file=/src/App.js
What I see in many cases to happen is that I manage the state of the filter in multiple places.
In the example above, I have it in the Toolbar component and in the parent one.
What is the best way of managing states in such case?
You should definitely never duplicate the state in multiple places as this can create bugs where they get out of sync. The solution is to hoist state and then pass it down via props, see https://beta.reactjs.org/learn/sharing-state-between-components.
Here is your codesandbox modified as an example: https://codesandbox.io/s/keen-wilbur-0xyi8f
You are duplicating the sate multiple times, plus there are some unnecessary helper functions along the way.
The state can be held in one place, with its setter function passed to the toolbar component, like so:
export default function App() {
const [searchStr, setSearchStr] = useState("");
return (
<div className="App">
<h1>Tony Starks Nicknames</h1>
<Toolbar searchStr={searchStr} onSearchStrChange={setSearchStr} />
<List searchStr={searchStr} />
</div>
);
}
Then, the toolbar can look like this:
function Toolbar({ searchStr, onSearchStrChange }) {
const handleChange = (e) => {
onSearchStrChange(e.target.value);
};
return (
<div>
<input
type="text"
onChange={handleChange}
value={searchStr}
placeholder="search..."
/>
</div>
);
}
This way, there's only one place where the state is stored
If your hierarchy's depth is or might become greater than 2, I would suggest the Context API.
https://reactjs.org/docs/context.html
Related
I am wondering what is the standard way to add additional input field from onClick() on a button. A scenario might be adding a Todo-list item field on click.
My approach was having a state array that stores the actual component and concat the array.
const[ComponentList, setComponentList] = useState([<Component id={1}/>]);
And then...
function addAnotherQuestion() {
setComponentList(ComponentList.concat(generateComponent(currentID)));
}
I was told this is a very bad idea and I would end up with messed up inputs because of stale states (which I did, and then I solved by writing to Redux store directly from the child component). This is not an ideal solution, so I want to know what is the standard way to do this?
I would store only inputs data in array like so:
const [inputs, setInputs] = useState(["some_id_1", "some_id_2", "some_id_3"]);
function addAnotherQuestion() {
setInputs(inputs.concat(currentID));
}
and then render them separately:
<>
{ inputs.map((id) => <Component key={id} id={id}/>) }
</>
How about keeping an array of Id's instead of components?
const[ComponentListIds, setComponentListIds] = useState([1]);
Why do you need to generate the components? What you should probably do, is generating new Id's instead. And then render the components within the render part of you component with:
render(
...
ComponentListIds.map(id=> <Component key={id} id={id}>)
...
)
I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.
As the headline states: is something like the pseudo code below considered bad?
<Outer
a = { ComponentA }
b = { ComponentB }
/>
var Outer = (props) => {
var ComponentA = props.a;
var ComponentB = props.b;
// do fancy stuff
// ...
return (
<div>
<ComponentA { ...fancypropsForA } />
<ComponentB { ...fancypropsForB } />
</div>
);
}
As an example: I'm using it to display tree data in different ways by passing a component that will render the data of a single node.
EDIT
As requested, I will try to make my question a little more clear.
There is a component that has some logic and some markup that is the same every time you use this component. But there are two (or more) places in that markup that should be replacable.
Picture a calendar that displays a whole month. There is a component that renders an individual date, and one that renders the weekday (in the bar at the top).
You want to reuse that calendar in multiple places, but you need different markup for the date/weekday components each time.
One way to achieve this is:
<Calendar
data={ data }
weekdayComponent={ MyWeekDayComponent }
dateComponent={ MyDateComponent }
/>
<Calendar
data={ data }
weekdayComponent={ SomeOtherWeekDayComponent }
dateComponent={ SomeOtherDateComponent }
/>
So, i found that this works. But I'm not sure if that is actually bad.
As long as data flows only in one direction, you're generally OK. Your example is a little contrived, so it's hard to see what general problem you're trying to solve, but I think what you're actually looking for are Higher Order Components.
https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e
I have a navigation toolbar, with H1 in it.
I also various sub content for each page, as child component.
How can I pass the title information, from the child page component, to the parent navigation?
I've tried to use Context, but it only propagate from Parent to Childs.
Her is a simplified exemple:
const App = React.createClass({
render() {
return (
<div>
<toolbar>
<h1>{myTitleAccordingToPage}</h1>
</toolbar>
<main>
{this.props.children}
</main>
</div>
)
}
})
const A = React.createClass({
render() {
return (
<div>
Content for A page
</div>
)
}
})
const B = React.createClass({
render() {
return (
<div>
Content for B page
</div>
)
}
})
Extract state for your navigation either into a store, or somewhere else, and propagate that state to your application via props.
var page = {
title: 'foo'
otherData: ''
}
ReactDOM.render( <App page={ page }, element )
Now your top-level component knows about the state and can filter accordingly down to its children, for example, you might change your App render function to pass different bits of the application state down to different children i.e.
(I'm passing props explicitly here to show what is going on, and using a pure function to render, use a class and render method if that is more comfortable for you, the advantage of only ever passing props is that it is easier to make your functions pure, which has many advantages)
const App = props => {
<div>
<Toolbar title={ props.page.title } />
<Main otherData={ props.page.otherData />
</div>
}
Now you have created top-down data flow, making your application more predictable and possibly paving the way to use the pure render function to maybe give you performance boosts for free. The key with top-down data-flow is really making your application more predictable, which should translate into testability, and thus reliability, and makes hacking on your application easier.
The react-router module can help with creating structures like this. There is a small learning phase when adopting the module but the benefits for an app like the one you are creating should outweigh this and the speed of development once learnt may very well outweigh any learning outlay you invest into it.
Possible it's time to use Flex https://facebook.github.io/flux/docs/dispatcher.html#content
Usable approuch to two way data binding http://voidcanvas.com/react-tutorial-two-way-data-binding/
Original documentation to two way data binding, but mixin now is now supported https://facebook.github.io/react/docs/two-way-binding-helpers.html
( For mixin possible to use react-mixin npm package )
EDIT:
There is an opinion at comments that: "two-way data-binding is often a bad idea, and if your application makes more sense with two-way data binding then React should probably not be the framework to employ". So you need to rearrange your application architecture to avoid using two-way data binding if possible. Use flux instead.
I have been trying to figure out the best way to manage my react forms. I have tried to use the onChange to fire an action and update my redux store with my form data. I have also tried creating local state and when my form gets submitted I trigger and action and update the redux store.
How should i manage my controlled input state?
I like this answer from one of the Redux co-authors:
https://github.com/reactjs/redux/issues/1287
Use React for ephemeral state that doesn't matter to the app globally
and doesn't mutate in complex ways. For example, a toggle in some UI
element, a form input state. Use Redux for state that matters globally
or is mutated in complex ways. For example, cached users, or a post
draft.
Sometimes you'll want to move from Redux state to React state (when
storing something in Redux gets awkward) or the other way around (when
more components need to have access to some state that used to be
local).
The rule of thumb is: do whatever is less awkward.
That is, if you're sure that your form won't affect global state or need to be kept after your component is unmounted, then keep in the react state.
You can use the component's own state. And then take that state and give it as an argument to the action. That's pretty much the "React way" as described in the React Docs.
You can also check out Redux Form. It does basically what you described and links the form inputs with Redux State.
The first way basically implies that you're doing everything manually - maximum control and maximum boilerplate. The second way means that you're letting the higher order component do all the work for you. And then there is everything in between. There are multiple packages that I've seen that simplify a specific aspect of form management:
React Forms -
It provides a bunch of helper components to make form rendering and validation more simple.
React JSON schema -
Allows one to build an HTML form from a JSON schema.
Formsy React -
As the description says: "This extension to React JS aims to be that "sweet spot" between flexibility and reusability."
Update: seems these days Redux Form is being replaced with:
React Final Form
And one more important contender in the space that's worth checking out is:
Formik
Tried out React Hook Form in my last project - very simple, small footprint and just works:
React Hook Form
TL;DR
It's fine to use whatever as it seems fit to your app (Source: Redux docs)
Some common rules of thumb for determing what kind of data should be
put into Redux:
Do other parts of the application care about this data?
Do you need to be able to create further derived data based on this original data?
Is the same data being used to drive multiple components?
Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
These questions can easily help you identify the approach that would be a better fit for your app. Here are my views and approaches I use in my apps (for forms):
Local state
Useful when my form has no relation to other components of the UI. Just capture data from input(s) and submits. I use this most of the time for simple forms.
I don't see much use case in time-travel debugging the input flow of my form (unless some other UI component is dependent on this).
Redux state
Useful when the form has to update some other UI component in my app (much like two-way binding).
I use this when my form input(s) causes some other components to render depending on what is being input by the user.
Personally, I highly recommend keeping everything in the Redux state and going away from local component state. This is essentially because if you start looking at ui as a function of state you can do complete browserless testing and you can take advantage of keeping a reference of full state history (as in, what was in their inputs, what dialogs were open, etc, when a bug hit - not what was their state from the beginning of time) for the user for debugging purposes. Related tweet from the realm of clojure
edited to add: this is where we and our sister company are moving in terms of our production applications and how we handle redux/state/ui
Using the helper libraries is just more quick and avoid us all the boilerplate. They may be optimized, feature rich ...etc. As they make all different aspects more of a breeze. Testing them and making your arsenal as knowing what's useful and better for the different needs, is just the thing to do.
But if you already implemented everything yourself. Going the controlled way. And for a reason you need redux. In one of my projects. I needed to maintain the form states. So if i go to another page and come back it will stay in the same state. You only need redux, if it's a mean for communicating the change to multiple components. Or if it's a mean to store the state, that you need to restore.
You need redux, if the state need to be global. Otherwise you don't need it. For that matter you can check this great article here for a deep dive.
One of the problems that you may encounter! When using the controlled inputs. Is that you may just dispatch the changements at every keystroke. And your form will just start freezing. It became a snail.
You should never directly dispatch and use the redux flux, at every changement.
What you can do, is to have the inputs state stored on the component local state. And update using setState(). Once the state change, you set a timer with a delay. It get canceled at every keystroke. the last keystroke will be followed by the dispatching action after the specified delay. (a good delay may be 500ms).
(Know that setState, by default handle the multiple successive keystroke effectively. Otherwise we would have used the same technique as mentioned above (as we do in vanilla js) [but here we will rely on setState])
Here an example bellow:
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
// here how you implement the delay timer
clearTimeout(this.onInputsChangeTimeoutHandler); // we clear at ever keystroke
// this handler is declared in the constructor
this.onInputsChangeTimeoutHandler = setTimeout(() => {
// this will be executed only after the last keystroke (following the delay)
if (typeof onInputsChange === "function")
onInputsChange(this.state.inputs, roomId, evt);
}, 500);
})
}
You can use the anti-pattern for initializing the component using the props as follow:
constructor(props) {
super(props);
const {
name,
description
} = this.props;
this.state = {
inputs: {
name,
description
}
}
In the constructor or in the componentDidMount hook like bellow:
componentDidMount () {
const {
name,
description
} = this.props;
this.setState({
...this.state,
inputs: {
name,
description
}
});
}
The later allow us to restore the state from the store, at every component mounting.
Also if you need to change the form from a parent component, you can expose a function to that parent. By setting for setInputs() method that is binded. And in the construction, you execute the props (that is a getter method) getSetInputs(). (A useful case is when you want to reset the forms at some conditions or states).
constructor(props) {
super(props);
const {
getSetInputs
} = this.props;
// .....
if (typeof getSetInputs === 'function') getSetInputs(this.setInputs);
}
To understand better what i did above, here how i'm updating the inputs:
// inputs change handlers
onNameChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
name: value
},
evt
);
}
onDescriptionChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
description: value
},
evt
);
}
/**
* change = {
* name: value
* }
*/
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
clearTimeout(this.onInputsChangeTimeoutHandler);
this.onInputsChangeTimeoutHandler = setTimeout(() => {
if (typeof onInputsChange === "function")
onInputsChange(change, roomId, evt);
}, 500);
})
}
and here is my form:
const {
name='',
description=''
} = this.state.inputs;
// ....
<Form className="form">
<Row form>
<Col md={6}>
<FormGroup>
<Label>{t("Name")}</Label>
<Input
type="text"
value={name}
disabled={state === "view"}
onChange={this.onNameChange}
/>
{state !== "view" && (
<Fragment>
<FormFeedback
invalid={
errors.name
? "true"
: "false"
}
>
{errors.name !== true
? errors.name
: t(
"You need to enter a no existing name"
)}
</FormFeedback>
<FormText>
{t(
"Enter a unique name"
)}
</FormText>
</Fragment>
)}
</FormGroup>
</Col>
{/* <Col md={6}>
<div className="image">Image go here (one day)</div>
</Col> */}
</Row>
<FormGroup>
<Label>{t("Description")}</Label>
<Input
type="textarea"
value={description}
disabled={state === "view"}
onChange={this.onDescriptionChange}
/>
{state !== "view" && (
<FormFeedback
invalid={
errors.description
? "true"
: "false"
}
>
{errors.description}
</FormFeedback>
)}
</FormGroup>
</Form>