I'm new to reactJS, and to react-native. I need advice.
I'm having hard time with understanding how to create working action of element of a list.
So. I have parent component which creates tasks in scrollView.
<ScrollView>
{this.state.tasks.map(function(ele) {
return (
<Task
action={this.handler}
key={ele.id}
title={ele.title}
subtitle={ele.subtitle}
state={ele.state}
/>
);
})}
</ScrollView>
My parent component has state with variable tasks define in constructor.
this.state = {
tasks: [
{ title: "A1", subtitle: "xyz", state: 0, id: 0 },
{ title: "A2", subtitle: "zyx", state: 0, id: 1 }]
}
And I want to change state to 1 if specific Task is clicked. I've tried to achived by doing sth like here: https://ourcodeworld.com/articles/read/409/how-to-update-parent-state-from-child-component-in-react
But this is not working.
From what I have understood, you want to update the 'state' property in your tasks for particular task. In your handler, you could do something like this :
handler(ele) {
let updatedTasks = this.state.tasks.map((task)=> {
if(ele.id === task.id) {
return Object.assign({}, task, {
state: 1
});
} else {
return task;
}
})
//Set the new state
this.setState({
tasks: updatedTasks
})
}
In your ScrollView action should be :
action={(ele) => this.handler(ele)}
You didn't provide much context, but basically it should be enough to give the id of your specific task in the handler call, either by providing it at the lower Task component level, or binding it when you're creating the element (by doing something like:
action={this.handler.bind(this, ele.id)}
, after which calling handler() at the Task level already contains the correct id.
Then in your handler method you have the id at hand, and can then modify the proper task in your state. This is a bit annoying to do in a clean way - you have to take the old task lists and create a new list with the proper task modified. After that you can modify the state with this.setState(newTaskList).
Related
This is more of a brainstorming question as I can't really seem to come up with a good solution.
I have a component which renders a tree based on some passed JSON (stored at the top level). Each node of the tree can have 0..n children and maps to a component defined by the JSON of that node (can be basically anything is the idea). The following is just an example and the names don't mean anything specific. Don't pay too much attention to the names and why a UserList might have children that could be anything.
JSON: {
data: {}
children: [
{
data: {}
children: []
},
{
data: {}
children: []
},
{
data: {}
children: [
{
data: {}
children: []
},
...etc
]
},
]
}
const findComponent = (props) => {
if (props.data.name === "userSelector") {
return <UserSelectorNode {...props}>;
} else if (props.data.name === "userInformation") {
return <UserInformationNode{...props}>; // example of what might be under a userSelectorNode
}
...etc
};
// render a user selector and children
const UserSelectorNode = (props) => {
const [selected, setSelected] = React.useState([])
// other methods which can update the JSON when selected changes...
return (
<div>
<UserSelector selected={selected}/> // does a getUser() server request internally
<div>
{props.data.children.map((child) => findComponent(child))}
<div>
</div>
);
};
This tree can be modified at any level (add/remove/edit). Adding/Editing is easy. The problem is remove operations.
Some children components use existing components which do things like getting a list of users and displaying them in a list (stored in state I have no access to). When a node on the tree is removed new components are made for every node that has to shift (JSON at the index is now different), which can be a lot. This causes a bunch of requests to occur again and sometimes state can be lost entirely (say the page number of a table to view users).
I'm pretty sure there is no way for the "new" UserSelector created when the JSON shifts to keep the same state, but I figured I may as well ask if anyone has dealt with anything similar and how they went about designing it.
The only way I can think of is to not actually reuse any components and re implement them with state stored somewhere else (which would suck), or rewrite everything to be able to take internal state as well as an external state storage if required.
EDIT: Added Sandbox
https://codesandbox.io/s/focused-thunder-neyxf. Threw it together pretty quick to only get a single layer of remove working which shows the problem.
I have an array of objects that can be updated by appending new elements to the array with the action function addNewCategory(category)
After the new category appended to the array, I want to rebuild a tree view that I created. The tree view uses the redux store object tree.
The tree gets build inside the action function buildCategoriesTree
Here are the functions:
export function addNewCategory(category) {
return {
type: ADD_NEW_CATEGORY,
payload: category
}
}
export function buildCategoriesTree(feecategories) {
let cats = feecategories.map(x => ({...x}));
let categories = {
name: 'Kategorien',
toggled: true,
children:
buildTreeFromCategories(cats)
};
return {
type: GET_CATEGORIES_TREE,
payload: categories
}
}
Now the problem is: when I call the buildCategoriesTree(...) function after the addNewCategory(...) function. The tree doesn't update. When I add another category, the tree updates with the category I added before. So it is alway one step behind.
How is it possible that the second function "waits" until the store is up to date? I read about react-thunk, but I don't know if this is the correct solution for this kind of problem?
Thanks a lot!
I have a state that looks like:
entities: {
pageDict: {
['someId1']: { name: 'page1', id: 'someId1' },
['someId2']: { name: 'page2', id: 'someId2' },
['someId3']: { name: 'page3', id: 'someId3' }
}
}
lists: {
pageIdList: ['someId1', 'someId2', 'someId3']
}
And a Component that looks like:
const Pages = ( props ) => {
return (
<div>
props.pageIdList.map(( pageId, key ) => {
return (
<Page
key={ key }
pageObj={ somethingHere } // What to do here?
/>
);
})
</div>
);
}
To grab the object, I would need to do:
let pageObj = state.entities.pageDict[ pageId ];
I guess I can pass the state.entities.pageDict as props from the Containers.. but I'm trying to look at the selectors pattern and looking at this:
https://redux.js.org/docs/recipes/ComputingDerivedData.html
and I'm wondering if I'm doing this wrong, can someone give me some insight? I just want to make sure I'm doing this correctly.
EDIT: I got the idea of using Entities from the https://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html and the SoundCloud Redux project
EDIT2: I'm looking at things online like this https://github.com/reactjs/redux/issues/584 but I'm questioning if I'm using this incorrectly and I'm not sure how to apply this to my own project
EDIT3: I'm leaning on creating a selector that will get the pageIdList and return a list of the pageobjects from the pageDict.. that way I already have the object to pass into my Page component.
I think I follow what you're trying to do here. With Redux try thinking of your user interface as always displaying something immutable: rather than "passing something to it" it is "reading something from the state". That when when your state changes your user interface is updated (it isn't always this simple but it is a pretty good start).
If I read your answer correctly you have a Map of pages:
//data
{
id1: {...pageProperties} },
id2: {...pageProperties} },
id3: {...pageProperties} },
}
and your page list is the order these are displayed in:
ex:
[id2, id3, id1]
Your page object might look something like this:
//Page.js
class Page extends React.Component {
render() {
const { pageIdList, pageEntities } = this.props //note I'm using props because this is connected via redux
return (
<div>
{ pageIdList.map((pageId, index)=>{
return <Page key={index} pageEntity={pageEntities[pageId]} /> //I'm just pulling the object from the map here
}}
</div>
)
}
}
//connect is the Redux connect function
export default connect((store)=> { //most examples use "state" here, but it is the redux store
return {
pageEntities: store.pageEntities,
pageIdList: store.pageList
}
})(Page)
Now when we want to change something you update the state via a dispatch / action. That is reduced in the reducer to display the new state. There are a lot of example out there on how this works but the general idea is update the state and the components take care of displaying it.
The result of this code (and there might be some typeos) is that you should see:
Page id: 2, Page id: 3, Page id: 1 because the list of the pages in the state is 2, 3, 1 in the example I gave.
To answer your question specifically what the entity I'm pulling is the global Redux Store (not the internal component state). 'Map State to Props' is really 'Map Store to Props' as the 'state' is part of React and the 'store' is your Redux store.
Does that help? React+Redux is really nice once you figure it out, it took me a solid month to understand all the ins and outs but it really does simplify things in the long run.
I have a React design problem that I am trying to solve, and I am running into an issue counter-intuitive to the idea of encapsulation as I try to breakdown an existing component into parent-child to support an additional use case. I am using React 15.3.2 with Redux, ES6, and ImmutableJS. First, I will illustrate the design problem, then I will provide snippets to illustrate why I feel that I have the need to get data back from children and how that is good for encapsulation, and the roadblock I am hitting.
I have read this stackoverflow which has detailed explanation on why passing data from children to parent component is not a good idea,
Pass props to parent component in React.js
But I have some concerns, which I will discuss at the end.
Design:
I am starting with a CheckboxSelect component. The Title bar's text depends on the checked items.
Closed:
Open with selections (current implementation):
To support additional use-case, the dropdown will now open up with more stuff.
Open with selections (new update):
Initial Code:
I am starting with a CheckboxSelect controlled component with the following props interface:
CheckboxSelect.jsx:
CheckboxSelect.propTypes = {
// what title to display by default with no selection
defaultTitle: PropTypes.string.isRequired, ie. "Selected Scopes"
// array of selected options, ie. [{key: "comedy", name: "comedy", checked: false }, ...]
options: PropTypes.array.isRequired,
// handler for when the user checks a selection, this will update
// the reducer state, which causes the options prop to be refreshed and
// passed in from the outer container
onCheck: PropTypes.func.isRequired,
onUncheck: PropTypes.func.isRequired,
onCheckAll: PropTypes.func,
onUncheckAll: PropTypes.func,
className: PropTypes.string,
// controls the open state of the dropdown
open: PropTypes.bool,
// handler for when the user clicks on the dropdown button, this will update the reducer state,
// which causes the open prop to be refreshed and passed in from the outer container
onClick: PropTypes.func,
onCancel: PropTypes.func,
};
// there is currently some logic inside the component to generate the title to display
// produces "comedy, action"
getTitle() {
const { defaultTitle } = this.props;
const checked = this.getChecked();
let fullTitle;
if (this.allChecked() || this.allUnchecked()) {
fullTitle = `${defaultTitle } (${checked.length})`;
} else {
fullTitle = checked.map((option) => option.name).join(', ');
}
return fullTitle;
}
getChecked() {
const { options } = this.props;
return options.filter(option => option.checked);
}
allChecked() {
const { options } = this.props;
return this.getChecked().length === options.length;
}
allUnchecked() {
return this.getChecked().length === 0;
}
ApplicationContainer.jsx (where the component is being used):
scopeDropDownOptions = (currentMovie) => {
// returns [{key: "comedy", name: "comedy", checked: false }]
const applicableScopes = currentMovie.getIn(['attributes', 'applicable_scopes']);
return currentMovie.getIn(['attributes', 'available_scopes']).reduce((result, scope) => {
result.push({
key: scope,
name: scope,
checked: (applicableScopes.includes(scope)),
});
return result;
}, []);
}
onSelectScope = (scope) => {
const movieScopes = this.applicableScopes.push(scope.key);
this.onUpdateField('applicable_scopes', movieScopes);
}
render() {
...
<CheckboxSelect
defaultTitle="Selected Scopes"
options={this.scopeDropdownOptions(currentMovie)}
onCheck={this.onSelectScope}
onUncheck={this.onDeselectScope}
onCheckAll={this.onSelectAllScopes}
onUncheckAll={this.onDeselectAllScopes}
open={store.get('scopeDropdownOpen')}
</CheckboxSelect>
}
New Code:
To support the new layout, I would like to break the existing component into two: a DynamicDropdown that contains CheckboxSelect2 as one of the children, along with any other elements that may be dropped down. Here is how the new code will look inside the ApplicationContainer.
ApplicationContainer.jsx
scopeDropDownOptions = (currentMovie) => {
// returns [{key: "comedy", name: "comedy", checked: false }]
const applicableScopes = currentMovie.getIn(['attributes', 'applicable_scopes']);
return currentMovie.getIn(['attributes', 'available_scopes']).reduce((result, scope) => {
result.push({
key: scope,
name: scope,
checked: (applicableScopes.includes(scope)),
});
return result;
}, []);
}
onSelectScope = (scope) => {
const {store } = this.props;
const cachedApplicableScopes = store.get('cachedApplicableScopes').push(scope.key);
store.get('cachedApplicableScopes').push(scope.key);
this.actions.setCachedApplicableScopes(cachedApplicableScopes);
// wait until apply is clicked before update
}
render() {
...
<DynamicDropdown
className="scope-select"
title={this.scopeDropdownTitle()}
open={store.get('scopeDropdownOpen')}
onClick={this.onScopeDropdownClick}
onCancel={this.onScopeDropdownCancel}
>
<CheckboxSelect2
options={this.scopeDropdownOptions(currentMovie)}
onCheck={this.onSelectScope}
onUncheck={this.onDeselectScope}
onCheckAll={this.onSelectAllScopes}
onUncheckAll={this.onDeselectAllScopes}
visble={store.get('scopeDropdownOpen')}
/>
// ... other children like confirmation message and buttons
</DynamicDropdown>
}
// logic related to CheckboxSelect2 title generation moved to the ApplicationContainer. Not ideal in my opinion as it breaks encapsulation. Further discussions in the Choices section
getScopesChecked() {
const options = this.scopeDropdownOptions(this.currentMovie);
return options.filter(option => option.checked);
}
scopesAllChecked() {
const options = this.scopeDropdownOptions(this.currentMovie);
return this.getScopesChecked().length === options.length;
}
scopesAllUnchecked() {
return this.getScopesChecked().length === 0;
}
scopeDropdownTitle() {
const defaultTitle = "Selected Scopes";
const checked = this.getScopesChecked();
let fullTitle;
if (this.scopesAllChecked() || this.scopesAllUnchecked()) {
fullTitle = `${defaultTitle} (${checked.length})`;
} else {
fullTitle = checked.map((option) => option.name).join(', ');
}
return fullTitle;
}
Problem:
The problem I have is with populating the title props of the DynamicDropdown element with the New Code, since it depends on the result of the CheckboxSelect2 selection.
Keep in mind CheckboxSelect2 is a dumb controlled component, we pass an onCheck handler to it. The this.onSelectScope inside the ApplicationContainer, is responsible for updating the reducer state of what has been checked, which in turn refresh the props and causes the DynamicDropdown and CheckboxSelect2 to be re-rendered.
Choices:
In the old code, there is a group of logic used to figure out the title to display for the dropdown. Here are the choices I am presented with:
To keep encapsulation of letting the CheckboxSelect2 summarize the
title, I tried initially keeping the same title logic inside
CheckboxSelect2, and accessing it via a ref.
ApplicationContainer.jsx
<DynamicDropdown
title={this.childCheckboxSelect.getTitle()}
>
<CheckboxSelect2
ref={(childCheckboxSelect) => this.childCheckboxSelect = childCheckboxSelect}
>
</DynamicDropdown>
At the time that DynamicDropdown is re-rendered, CheckboxSelect2
hasn't received the new props yet from the parent via the one-way
data-flow, so as a child, it actually cannot generate the most
up-to-date title for the DynamicDropdown based on what has been
checked. With this implementation, my title is one-step behind what
was actually checked.
As shown in the ApplicationContainer.jsx for the New Code section
above, the logic for the scopeDropdownTitle could be moved out from
CheckboxSelect2 to ApplicationContainer.jsx, so it sits a level
above DynamicDropdown, enabling the DynamicDropdown to get the
updated title from the reducer state as it renders. While this
solution works, it totally breaks my view on encapsulation, where
the responsibility for determining what title to be displayed, is
something that the CheckboxSelect2 should know about. Basically the
title logic in ApplicationContainer.jsx, now also pre-generates the
options props meant to passed to CheckboxSelect2 to render that
component, the CheckboxSelect2 logic is bleeding into the outer
container.
Let's look at the argument in the stackoverflow post Pass props to parent component in React.js and how it relates to this design problem and an analogy:
"The parent already has that child prop. ... if the child has a
prop, then it is because its parent provided that prop to the
child!" Sure, the ApplicationContainer has all the knowledge it
needs to generate the title for the parent DynamicDropdown based on
the checked states of the child CheckboxSelect2, but then is it
really the responsibility of the ApplicationContainer?
Let me give an analogy of a Manager asking an Employee to produce a Report. You can say, the Manager already has all the info, he can surely produce the Report himself! Having a controlled component where the
container manages update to the props for the child via callbacks, seems to me like a Manager passing a bucket to the Employee, the Employee passes back bits and pieces of information in the bucket, and tell the Manager to do the work to summarize things instead of the Employee producing a good summary (which is good encapsulation).
"Additionally, you could have used a ref directly on the child"
I think in the Choices 1) I stated above, using ref does not seem to work when you want up-to-date information from the child as
there is a circular dependency issue with one-way dataflow (ie.
parent needs to get up-to-date summary information from the child,
but the child first depends on the up-to-date information from the
parent).
If you have read this end-to-end and understood the problem, I appreciate your effort! Let me know what you think.
I've just started on reactjs and am trying to figure out the right way to design my components. I get the concept of components, props, states but struggling a bit with the right way to design for a hierarchy.
I have a page that's driven by a big object array. Say
objArr = {
param1 : value1,
param2: value2,
dataArray: [
{ key: "keyvalue", a: "a", b: "b", c: "c" },
...
]
}
The entire page builds off of this. The page builds a series of UI components corresponding to the dataArray.
Now each time some of the icons are clicked in the UI, I want some changes, and the icons correspond to a value on this dataArray. What's a good way to ensure the dataArray values are changed as the UI is acted on? and vice versa, the UI changes as values are changed on the dataArray.
I've read "make components as stateless as possible," fine - then how do I do this handling at the parent component level and have it flow down?
I don't need code examples. Just a few pointers of the way to architect my ReactJS code would be great, I will figure out the code.
Thank you
What you can do is bind a method on the Parent component and pass it down to the child container like so:
// Our 'pages' that we want to be able to interact with
const ComponentWIthButtons = ({handleButtonClick, buttons}) => (
<div>
<p>Hey, click one of these</p>
{buttons.map(button => (
<div onClick={()=>handleButtonClick(button.name)}>
{button.text}
</div>
))}
</div>
)
// The Parent we want to interact with
class App extends React.Component {
constructor(props){
super(props)
this.state = {
buttons: [
{
name: 'a',
text: 'Awesome button'
},
{
name: 'b',
text: 'Beautiful button'
},
{
name: 'c',
text: 'Cool button'
}
]
}
}
// This is what you want to do with the information passed
handleButtonClick = (name) => {
// this could be updating this components' state, which
// would rerender the display component because we pass it
// our buttons from our state object
console.log(name)
};
render(){
return <div>
<h2>Below are some buttons!</h2>
// If you don't use the syntax above, you will run into
// errors trying to get a reference to this container through
// `this`. You can do this.handleButtonClick.bind(this)
// to get around that
<ComponentWIthButtons handleButtonClick={this.handleButtonClick} buttons={this.state.buttons} />
</div>
}
}