ReactJS Dynamically Replace Children - javascript

I have a fancy UI in which have a few 'panes' with dividers in between that let you change what each one does. Say I have two different components - a to-do list and a simple text editor. I want you to be able to change the component present in each pane to make a flexible UI. For example, I might want to change the pane on the left from a text editor to a to-do list. Assuming I have a parent element Pane, how could I replace one of its children with another?
<Pane>
<TextEditor /> /* I want to replace that with a <ToDoList /> when I press a button */
<SomeOtherComponentOnTheRight />
</Pane>
I've tried storing React.Children.toArray(this.props.children) in the <Pane />'s state (as this.state.currentChildren), and replacing the element there, but for some reason I can't find a way to get the index of <TextEditor /> in the <Pane />'s this.state.currentChildren because for some reason this.props.children does not preserve children's props, and so I can't transmit data through it.
Sorry if I've overcomplicated this, but I simply want to know how to change a component's children dynamically.

You could check the state in your JSX to change what is displayed such as:
<Pane>
{ this.state.showEditor ? <TextEditor/> : <ToDoList /> }
<SomeOtherComponentOnTheRight />
</Pane>
Elsewhere in your code you would have some button that invokes an onClick event handler that would set the state of 'showEditor' to true/false depending on the previous state.

You can store the selected components in an array or object, then assign the selected component to a variable (just make sure it starts with an uppercase letter) and then use it as a component:
const routes = {
a: TextEditor,
b: ToDoList
};
const ChosenComponent = routes['a']; // select your component and store in variable
return (
<Pane>
<ChosenComponent />
<SomeOtherComponentOnTheRight /> {/* render selected variable as component */}
</Pane>
);

Related

react-admin ReferenceField label not showing when used in a custom component

So I need a ReferenceField to access data from another table. Since I am doing this often i extracted this in a custom component
CustomField.tsx
const CustomField = (props: any) => {
const record = useRecordContext();
return (
<ReferenceField
record={record}
source="someId"
reference="table"
link="show"
label="some label"
{...props}
>
<TextField source="name" />
</ReferenceField>
);
};
now when i use the component:
<CustomField/>
everithing is working fine, data is displayed fine, except no label is shown.
So I am expecting some form of label at the top but no signs.
If I do it without creating a custom field, everything is working just fine, the label and sorting is there. But when I extract the code into a separate component it doesn't seem to work.
Looks like the attributes lose their default values and behavior when extracted to a separate component.
My current workaround
<OrganisationField label="Some label" sortBy="something" />
that is fine, it works but it's not practical (and it's annoying) to do this everytime I or someone else wants to use the component, since that should already be defined inside it.
When you say "no label is shown", I assume that's when you use your custom Field inside a Datagrid.
Datagrid inspects its children for their label prop to display the column header. To make your label inspect-able, declare it as defaultProp:
CustomField.defaultProps = {
label: "someId"
}
This is explained in the react-admin "writing a custom field" documentation: https://marmelab.com/react-admin/Fields.html#writing-your-own-field-component

Using map function to create React components

I want to map over values and return an instance of my <FirstRepeatAttributeLabelAssistant /> for every label (basically to render an label above each input, number of inputs can vary).
I've started off with this:
{object.attributeCollection.questions.map((question) => (
<FirstRepeatAttributeLabelAssistant />
))}
The output of the map above is like so:
[StringAttributeModel, MemoAttributeModel, LabelAttributeModel, MemoAttributeModel, StringAttributeModel, StringAttributeModel] I only care about the "StringAttributeModel" as each of those 3 include 3 different labels (the part I care about). Their structure is like so:
How can I update my logic to ensure all of the potential labels are covered and a <FirstRepeatAttributeLabelAssistant /> component is rendered for each of them?
Suppose all your elements are a component, then you can simply do.
{object.attributeCollection.questions.map(Comp => {
...
return <Comp label="..." />
})}
You can fill whatever your custom logic in the ... area.

Forkim's innerRef is updating every time a field in the form is changed

I am new with formik and I have a very annoying problem that I am stuck on with days. So basically I have a parent component in which I have a code like this:
const refs=[]
{data.map((v,i) =>
<Child formikRef={refs}}/> )}
And my child component uses formik:
...
<Formik
innerRef={i => props.formikRef.push(i)}
....../>
So my Child component is rendered a couple of times in my parent component, and I need to track the values of each in my parent component. To be more clear, when I click on button that is located in my parent component, I need to have the values from each (I need to pass data from child to parent component). That's why I am using innerRef. The problem is that I am using an array of refs to track values of each rendered , and a new value is added in the array every time I change a field in my form, so I have far more elements in my array than I should have. I think innderRef is triggered each time a change in a field is made, instead of only onSubmit. How I can solve this problem? Pls help.
You could try like below, Here we are passing index value to Child component to have Formik ref in the same place.
const refs=[]
{data.map((v,i) =>
<Child index={i} formikRef={refs}}/>
)}
<Formik
innerRef={props.formikRef[props.index]}
/>

Component re-render issue [Snippet Attached]

I am making a resume builder application and the whole structure was almost done.
Complete working codesandbox:
Here I have made components into stepper for each section,
index.js
<form onSubmit={handleSubmit}>
<Stepper
steps={sections}
activeStep={currentPage}
activeColor="red"
defaultBarColor="red"
completeColor="green"
completeBarColor="green"
/>
{currentPage === 1 && (
<>
<BasicDetails />
<button onClick={next}>Next</button>
</>
)}
{currentPage === 2 && (
<>
<EmploymentDetails />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<button onClick={prev}>Back</button>
<button onClick={next}>Next</button>
</div>
</>
)}
{currentPage === 3 && (
<>
<pre>{JSON.stringify(value, null, 2)}</pre>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<button onClick={prev}>Back</button>
<button onClick={handleSubmit}>Submit</button>
</div>
</>
)}
</form>
Steps to reproduce issue:
-> In Step 1 Enter First Name, Last Name, and Profile Summary
-> Click on Next button to move to next step.
-> Then click the back button to move backward to Step 1 (Currently in Step 2)
-> Here the values in First Name, Last Name are there but the value entered in text editor alone gets lost
Please refer the below image with text editor value entered for better understanding of the problem I am facing..
Text editor value alone gets lost if we switch forth/back the steps. But the entered value is stored in the form Context and not rendered in the Text Editor box.
Note:
Text editor is made as a component and it was used in Step 1 (For profile summary) and also in Step 2 (For employment description) and in both cases switching between steps, the value entered inside the text editor gets lost.
Analysis:
Based on my understanding, this happens because on navigating to other steps, the component get re-rendered and the EditorContainer component gets called and in text_editor.js it was given
this.state = {
editorState: EditorState.createEmpty(),
};
So it was created as empty.
So how can I control the component from getting re-rendered so that the data entered in text editor won't get lost.
Kindly please help me to retain the values entered inside the text editor. Big thanks in advance..
This happens because we're only saving our EditorContainer value to our Context, but we're not using it when we rerender the EditorContainer component.
The fix would be to pass the saved value as prop to our EditorContainer component.
Then before we render the EditorContainer, we'll convert that value to EditorState which can be done using convertFromHTML function, and set that as our editorState state.
Step 1: Pass value prop to EditorContainer
// basic_details.js
<EditorContainer
name="profileSummary"
value={basicDetails.profileSummary}
onChange={(event) => handleInputChange(event)}
/>
// employment_details.js
<EditorContainer
name="description"
value={inputField.description}
onChange={(event) => handleInputChange(index, event)}
/>
Step 2: Convert the value prop to EditorState
// text_editor.js
...
componentDidMount() {
// https://draftjs.org/docs/api-reference-data-conversion/#convertfromhtml
const { value } = this.props;
const blocksFromHTML = convertFromHTML(value);
const state = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap,
);
const editorState = EditorState.createWithContent(state);
this.setState({ editorState });
}
That's it! Check the demo below.
Edit Fix demo to check value if is string.
This is a great question. This is basically a design approach issue: For a scenario like yours, you need to design your component this way:
let's analyze the picture:
MAIN COMPONENT:
This is the component that should hold the state for the entire form-filling process. STE1-4 Are just views that allows you to input data that must all be updated in the main component. So This means, you must have state in main component and pass the state properties and props, including their update/setter methods.
STEP COMPONENT
This applies for all Step Components.
These components should do nothing except display the form step using state values received via props and update state by using setter methods, which are also received via props.
Conclusion:
Put your state in your main component, each step component should only display the form and update the main state. This means that by the time each step component is re-rendered, it will receive values updated in the main component. And you will achieve that via props.
It's very simple - you need to store editors state in your parent component. Try to use BasicDetails state for this.

How to access and use a value from a child component on redux-form to make a checkbox work and enable conditionally rendered extra options

I'm using redux-form. I need to conditionally render extra options on user click of a checkbox in a child component.
I am able to do this on the parent file (in the SOW Type section of the form) using 'formValueSelector' (see sandbox: https://codesandbox.io/s/currying-cloud-isu8c) but on the third option in this section ('Custom Professional Services SOW') on clicking the checkbox I need to further condionally render some options on clicking the new checkbox that is exposed inside ('Customised Professional Services').
The file containing the code for the child component is 'CustomProfExtOptions'. What code do I need in this child component and/or the parent component in order to get this nested checkbox to work/enable conditional rendering on checking the box
I understand I need to access the value of the field in this child component on the form in order to manipulate it's state (mapStateToProps?) and allow the conditional rendering to work. I have done this successfully in the parent (as the sandbox shows) but I don't know how to get his working in the child component.
I have googled extensively on this for three days and tried to tweak other examples I have found to fit this specific problem (the greyed out text at the bottom of 'CustomProfExtOptions.js' where the child component code is held) but I've yet to find anything that works.
What is working so far:
The 'formValueSelector' code (as per the Redux-Form docs which allows access the values in the fields of the form) I am using in the parent container for the form (a file called 'PdfGenFormContainerRedux.js') is as follows:
PdfGenFormContainerRedux = reduxForm({
form: "StatementOfWorkApplication"
})(PdfGenFormContainerRedux);
const selector = formValueSelector("StatementOfWorkApplication");
PdfGenFormContainerRedux = connect((state) => {
const hasProductSowValue = selector(state, "productSOW");
const hasTeradataExtOptionsValue = selector(state, "teradata");
const hasCustomProfExtOptions = selector(state, "customExtOptions"); //selector created
return {
hasProductSowValue,
hasTeradataExtOptionsValue,
hasCustomProfExtOptions
};
})(PdfGenFormContainerRedux);
export default PdfGenFormContainerRedux;
This allows me to conditionally render the second hidden field on the click of the checkbox by using a variable called props.hasCustomProfExtOptions as demonstrated below:
<div className="checkbox-group">
<div>
<label className="checkbox-group">
<Field
name="customExtOptions"
className="form-checkbox"
component="input"
type="checkbox"
/>
Custom Professional Services SOW
{props.hasCustomProfExtOptions && ( //conditional rendering bit here
<div>
<Field
name="custProfServices"
type="input"
component={CustomProfExtOptions}
label="Custom Options Info"
placeholder="Location"
formId={formId}
/* hasProfServ={props.hasProfServ} */
/>
</div>
)}
</label>
</div>
</div>
</div>
This works fine on the parent container but how do I get it working on the checkbox labelled 'Customised Professional Services' in the child component? (the file called 'CustomProfExtOptions')
Just to add, i need any input from this nested section to be included in the final submission of the form.
Any help would be greatly appreciated

Categories

Resources