I am working on react js app where I used "react-dragula" to drag and drop the list items. I am showing the preview of the child component html inside the parent wrapper component. After dropping an element my html is not render properly. I have no idea whether it is because of Dragula or there is some other issue.
After dropping the list item I am updating list values according to the element index and updating the state and the re rendering the child component. But it shows me old html it's not re rendering the html of child component using updated props return by parent component.
Here is my code::
class App extends React.Component {
drake = null;
socialContainers = [];
/** lifecycle method */
componentDidMount() {
this.drake = Dragula(this.socialContainers, {
isContainer: function (el) {
return (el.id === 'social-container');
},
moves: function (el, source, handle, sibling) {
return (handle.id === "socialSortBtn");
},
accepts: function (el, target, source, sibling) {
return (target.id === 'social-container' && source.id === 'social-container');
},
});
this.drake.on('drop', (el, target, source, sibling) => {
el.style.cursor = "grab";
let oldIndex = el.dataset.index
let type = el.dataset.type
let iconIndx = Array.prototype.indexOf.call(target.children, el);
let targetIndex = iconIndx > 0 ? iconIndx : 0;
if (type) {
let content = {}
content.reOrder = true;
content.oldIndex = oldIndex;
this.props.callback(content, 'content', targetIndex)
}
});
this.drake.on('drag', (el, source) => {
el.style.cursor="grabbing";
})
}
updateLinkText(val, index) {
const { data, callback } = this.props;
let textParsedHtml = new DOMParser().parseFromString(data.content[index].text, 'text/html');
if (textParsedHtml.getElementsByTagName("a")[0]) {
textParsedHtml.getElementsByTagName("a")[0].innerHTML = val;
}
let content = {}
content.changeLinkTxt = true
content.linkText = val
content.linkTextHtml = textParsedHtml.body.innerHTML
//update content
callback(content, 'content', index)
}
onSelectShareOpt(selectedVal, selectedIndx, event) {
event.stopPropagation();
let socialObj = socialShareArr.find((obj) => obj.value === selectedVal)
if (socialObj) {
let htmlObj = this.getHTMLObj(socialObj);
let content = {}
content.active_social_icons = socialObj
if(htmlObj) { content.content = htmlObj }
// update content
this.props.callback(content, 'content', selectedIndx)
}
}
isIconDisabled = (data, val) => {
let found = false;
found = data.some(function (obj) {
if (obj.value === val) {
return true;
}
return false;
});
return found;
}
renderSocialIcons(dragulaDecoratorRef) {
const { data } = this.props;
let socialIcons = data.activeIcons;
if (!socialIcons) {
return null
}
return (
<Fragment>
{socialIcons && socialIcons.length > 0 && socialIcons.map((activeIcon, index) => (
<li key={index} data-index={index} data-type={activeIcon.value} className="mb-30">
<div className="mr-2">
<button data-toggle="tooltip" title="Drag to reorder" className="btn btn-primary btn-sm btn-icon" id="dragBtn"><span id="socialSortBtn" className="material-icons m-0">drag_indicator</span>
</button>
</div>
<div className="mb-30">
<img className="mr-2" src={activeIcon.icon} alt="social-icon" width="36" height="36" />
<FormControl
value={activeIcon.value}
as="select"
onChange={e => this.onSelectShareOpt(e.target.value, index, e)}
custom
size=''
>
{socialShareArr && socialShareArr.map((option, index) => (
<option
key={index}
disabled={this.isIconDisabled(socialIcons, option.value)}
value={option.value}
>
{option.label}
</option>
))}
</FormControl>
</div>
<Form.Group>
<Form.Label>Link Text</Form.Label>
<TextInput
value={activeIcon.linkText}
onChange={(e) => this.updateLinkText(e.target.value, index)}
wrapperClass="mx-10 mb-0"
/>
</Form.Group>
</li>
))}
</Fragment>
);
}
// main function
render() {
const { data } = this.props;
const dragulaDecoratorRef = (componentBackingInstance) => {
if (componentBackingInstance) {
this.socialContainers.push(componentBackingInstance)
}
};
return (
<Fragment>
{data &&
<AppCard>
<Card.Body>
<div className="social-opt-container">
<ul id='social-container' ref={dragulaDecoratorRef}>
{this.renderSocialIcons(dragulaDecoratorRef)}
</ul>
</div>
</Card.Body>
</AppCard>
}
</Fragment>
)
}
}
export { App }
I have also tried to remove innerHTML of ""and then return new structure but in this case it returns nothing in html. Please check this once why this issue occurring.
Related
CodeSandbox
function disabledRow on click does not send changed data to child component.
I don't see any error in the code. Can't figure out how to display data in child component.
const UsersPage = () => {
const [dataState, setDataState] = useState<DataType[]>(dataInitial);
const [idState, setIdState] = useState<number[]>();
const disabledRow = () => {
if (idState) {
dataInitial.forEach((item) => {
if (idState.some((id) => id === item.id)) {
item.activeStatus = false;
item.date = <StopOutlined />;
item.status = "Not active";
}
});
return setDataState(dataState);
}
};
const idRow = (id: number[]) => {
return setIdState(id);
};
console.log("Hello", dataState);
return (
<div>
<div className={"wrapper"}>
<Button onClick={disabledRow}>
<StopOutlined /> Deactivate
</Button>
</div>
<UsersTable data={dataState} idRow={idRow} />
</div>
);
};
I want to call function when state data changes but not first loading.
This is my code.
const Page = (props) => {
const { data } = props;
const arrowDirection = (item) => {
if (item.arrow === 1) {
return "up";
} else {
return "down";
}
};
return (
<div>
{data &&
data.map((item, index) => (
<div key={index} className={arrowDirection(item)}>
{item.name}
</div>
))}
</div>
);
};
export default Page;
In here, props data changes automatically every few seconds.
So I want to change classname to up or down according to the status.
But when page loads, I don't want to call arrowDirection function so that the classname to be set as empty.
Eventually, I don't want to set classname for the first loaded data, but for the data from second changes.
How to do this?
I would try to update data in props and for first render let's say have item.arrow === null case for empty class. But if it is not possible, you may use useEffect+useRef hooks:
const Page = (props) => {
const { data } = props;
const d = useRef()
useEffect(() => {
d.current = true
}, []);
const arrowDirection = (item) => {
if (item.arrow === 1) {
return "up";
} else {
return "down";
}
};
return (
<div>
{data &&
data.map((item, index) => (
<div key={index} className={d.current ? arrowDirection(item) : ""}>
{item.name}
</div>
))}
</div>
);
};
This is my App.js
console.log(customer) shows the data on the console here, so I think there is no problem on my API.
let client = null;
let customer_id = null;
var customer = null;
const getCustomerId = () => {
client.get('ticket.requester.id').then(
function(data) {
customer_id = data['ticket.requester.id'].toString();
}
);
var settings = {
url:'/api/sunshine/objects/records?type=Customer',
type:'GET',
dataType: 'json',
};
client.request(settings).then(
function(data) {
var jsonCount = Object.keys(data.data).length;
var x = 0;
console.log(customer_id);
while(x < jsonCount) {
var cust = data.data[x];
if (cust.attributes.CustomerID == customer_id) {
customer = data.data[x];
// console.log(customer);
}
x = x + 1;
}
console.log(customer);
},
function(response) {
console.error(response.responseText);
}
);
}
const App = () => {
const [expandedSections, setExpandedSections] = useState([]);
const [expandedSections2, setExpandedSections2] = useState([]);
useEffect(() => {
client = window.ZAFClient.init();
getCustomerId();
}, []);
return (
<Row justifyContent="center">
<Col className="outer_column">
<Accordion className="first_accordion"
level={2}
expandedSections={expandedSections}
onChange={index => {
if (expandedSections.includes(index)) {
setExpandedSections(expandedSections.filter(n => n !== index));
} else {
setExpandedSections([index]);
}
}}
>
<Accordion.Section>
<Accordion.Header>
<Accordion.Label>Customer Management</Accordion.Label>
</Accordion.Header>
<Accordion.Panel>
<Row justifyContent="center">
<Col className="inner_column">
<Accordion
isCompact
level={5}
isExpandable
className="second_accordion"
>
<Accordion.Section>
<Accordion.Header>
<Accordion.Label>Customer Information</Accordion.Label>
</Accordion.Header>
<Accordion.Panel className="accordion_panel">
<Display jsonData = {JsonData} tryData = {customer}/>
</Accordion.Panel>
</Accordion.Section>
</Accordion>
</Col>
</Row>
</Accordion.Panel>
</Accordion.Section>
</Accordion>
</Col>
</Row>
)
}
export default App;
This is my Display.js:
function withMyHook(Component) {
return function WrappedComponent(props) {
const myHookValue = useZafClient();
return <Component {...props} myHookValue={myHookValue} />;
}
}
class Display extends React.Component {
constructor(props) {
super(props);
this.state = {
fields: this.props.jsonData.data[0].attributes,
errors: {},
customers: [],
flag: (this.props.jsonData.data[0].attributes.CustomerID === "") ? 'register' : 'view'
};
}
handleChange(field, e) {
let fields = this.state.fields;
fields[field] = e.target.value;
this.setState({ fields });
}
render() {
console.log("trydata");
console.log(this.props.tryData);
return(
<div>
{Object.keys(this.props.jsonData.data[0].attributes).map((key, i) => (
<p key={i}>
<span>{key}
<input value={this.state.fields[key] || ''}
placeholder={key}
disabled = {(this.state.flag === 'view') ? "disabled" : "" }
onChange={this.handleChange.bind(this,key)} /></span>
</p>
))}
{(this.state.flag === "register") ?
<Button onClick={() => this.setState({flag: 'view'})}> Register </Button> :
null
}
{(this.state.flag === "view") ?
<Button onClick={() => this.setState({flag: 'update'})}> Edit </Button> :
null
}
{(this.state.flag === "update") ?
<Button onClick={() => this.setState({flag: 'view'})}> Submit </Button> :
null
}
</div>
)
}
};
export default withMyHook(Display);
As you can see.. the API call on my App.js is being passed through tryData = {customer}, my problem is the data won't be pass to Display.js, not until I open the Accordion Customer Information.. you can see on the display.js that I am using this.props.jsonData.data[0].attributes instead of this.props.tryData.attributes, because I get type error Props error
Try to put variables inside of the App component that you pass as a props value
const App =()=>
{
let client=null,
....
}
So i've written a simple TODO with drag and drop and add, all the cards are generated using a function , my second column has an id to it and I now want any task added or dragged to it to have a css line through. How would I go about targeting it using the id
<div class="listContainer" id="y0zitt47gfb">
My code:
P.S Does not use create-react-app , just brought in through CDN and also uses react-dnd
let { DragDropContext, Draggable, Droppable } = window.ReactBeautifulDnd;
let lists = {};
let storeLists = () => {
console.log("Writing Lists to Storage");
console.log(lists);
window.localStorage.setItem("listStore", JSON.stringify(lists));
console.log("Set Correctly");
lists = JSON.parse(localStorage.getItem("listStore"));
};
class Task extends React.Component {
deleteTask = () => {
let listId = this.props.listId;
let index = lists[listId]["tasks"].findIndex((task) => {
return task["taskId"] == this.props.inputTask["taskId"];
});
lists[this.props.listId]["tasks"].splice(index, 1);
storeLists();
this.props.refresh();
};
taskContentChanged = (event) => {
let listId = this.props.listId;
console.log(listId);
console.log(lists[listId]);
console.log(this.props.inputTask);
let index = lists[listId]["tasks"].findIndex((task) => {
return task["taskId"] == this.props.inputTask["taskId"];
});
console.log(index);
lists[listId]["tasks"][index]["taskDescription"] = event.target.innerText;
};
keyPress = (event) => {
if (event.key === "Enter" && event.target.className == "taskCardContent") {
storeLists();
event.target.blur();
this.props.refresh();
}
};
render = () => {
return (
<Draggable
draggableId={this.props.inputTask["taskId"]}
index={this.props.index}
>
{(provided) => {
return (
<div
class="taskCard"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
id={this.props.inputTask["id"]}
>
<p
onKeyDown={this.keyPress}
onInput={this.taskContentChanged}
contentEditable="true"
class="taskCardContent"
>
{" "}
{this.props.inputTask["taskDescription"]}{" "}
</p>
<img
onClick={this.deleteTask}
class="deleteTaskIcon"
src="assets/delete.svg"
alt="Delete Task"
/>
</div>
);
}}
</Draggable>
);
};
}
class List extends React.Component {
addTask = () => {
let listId = this.props.inputList['listId'];
let newTaskInput = document.getElementById(listId + "Input");
if (newTaskInput.value != "") {
let newTask = {
taskId: Math.random().toString(36).substring(2, 15),
taskDescription: newTaskInput.value,
};
lists[listId]["tasks"].push(newTask);
storeLists();
}
newTaskInput.value = "";
this.props.refresh();
};
deleteList = () => {
let listId = this.props.inputList["listId"];
lists[listId] = undefined;
storeLists();
this.props.refresh();
};
listNameChanged = (event) => {
let listId = this.props.inputList["listId"];
lists[listId]["listName"] = event.target.innerText;
};
keyPress = (event) => {
if (event.target.className == "newListItemInput" && event.key == "Enter") {
this.addTask();
}
if (event.target.className == "listName" && event.key == "Enter") {
event.preventDefault();
event.stopPropagation();
event.target.blur();
storeLists();
this.props.refresh();
}
};
render = () => {
let list = this.props.inputList;
return (
<div class="body">
<div class="newListItem">
<input
onKeyDown={this.keyPress}
id={list["listId"] + "Input"}
type="text"
class="newListItemInput"
placeholder="Add To List"
/>
<img
class="addTaskIcon"
onClick={this.addTask}
src="assets/submit.svg"
alt="Add Task"
/>
</div>
<div class="listContainer" id={this.props.id}>
<div class="listHeader">
<h4 class="listName">{list["listName"]} </h4>
</div>
<Droppable droppableId={list["listId"]}>
{(provided) => {
return (
<div
class="taskList"
{...provided.droppableProps}
ref={provided.innerRef}
>
{Object.values(list["tasks"]).map((task, index) => {
return (
<Task
index={index}
inputTask={task}
listId={list["listId"]}
refresh={this.props.refresh}
/>
);
})}
{provided.placeholder}
</div>
);
}}
</Droppable>
</div>
</div>
);
};
}
class App extends React.Component {
state = {
appLists: lists,
};
onDragEnd = (result) => {
console.log(result);
if (result.reason === "DROP" && result.destination) {
let originListId = result.source.droppableId;
let originIndex = result.source.index;
let destinationListId = result.destination.droppableId;
let destinationIndex = result.destination.index;
let movedTask = lists[originListId]["tasks"][originIndex];
lists[originListId]["tasks"].splice(originIndex, 1);
lists[destinationListId]["tasks"].splice(destinationIndex, 0, movedTask);
}
storeLists();
this.refreshApp();
};
refreshApp = () => {
console.log("Refreshing...");
this.setState({
appLists: lists,
});
};
render = () => {
return (
<div id="app">
<DragDropContext onDragEnd={this.onDragEnd}>
{Object.values(this.state.appLists).map((list) => {
return (
<List
id={list["listId"]}
inputList={list}
refresh={this.refreshApp}
/>
);
})}
</DragDropContext>
</div>
);
};
}
let startUp = () => {
if (
localStorage.getItem("listStore") &&
!["undefined", "null"].includes(localStorage.getItem("listStore"))
) {
lists = JSON.parse(localStorage.getItem("listStore"));
} else {
console.log("Creating List in Storage");
lists = {
f0ziqq7gfn: {
listId: "f0ziqq7gfn",
listName: "To Do",
tasks: [
{ taskId: "fk8vs85v0wh", taskDescription: "Watch Regular Show" },
{ taskId: "kd3ve56f5fh", taskDescription: "" },
],
},
y0zitt47gfb: {
listId: "y0zitt47gfb",
listName: "Complete",
tasks: [
{ taskId: "v0zigt47whc", taskDescription: "Create React Todo" },
{ taskId: "b0zi5t4rwhc", taskDescription: "Somehow get a PS5" },
],
},
};
storeLists();
}
ReactDOM.render(<App />, document.body);
};
startUp();
Brainfart moment, just used that ID standardly , BURN OUT
I am trying to filter ResourceItems in my ResourceList by their tag. For exmaple, if a user searches for the tag "Sports", all items with the this tag should be returned.
I have been utilising this example to produce this, but it doesn't actually have any functionality when the user enters a tag to filter by.
This is my code so far, in which I don't get any items back:
const GetProductList = () => {
// State setup
const [taggedWith, setTaggedWith] = useState(null);
const [queryValue, setQueryValue] = useState(null);
// Handled TaggedWith filter
const handleTaggedChange = useCallback(
(value) => setTaggedWith(value),
[],
);
const handleTaggedRemove = useCallback(() => setTaggedWith(null), []);
const handleQueryRemove = useCallback(() => setQueryValue(null), []);
const handleFilterClear = useCallback(() => {
handleTaggedRemove();
handleQueryRemove();
}, [handleQueryRemove, handleTaggedRemove]);
const filters = [
{
key:'taggedWith',
label:'Tagged With',
filter: (
<TextField
label="Tagged With"
value={taggedWith}
onChange={handleTaggedChange}
labelHidden
/>
),
shortcut: true,
}
];
const appliedFilters = !isEmpty(taggedWith)
? [
{
key: 'taggedWith',
label: disambiguateLabel('taggedWith', taggedWith),
onRemove: handleTaggedRemove,
},
]
: [];
const filterControl = (
<Filters
queryValue={queryValue}
filters={filters}
appliedFilters={appliedFilters}
onQueryChange={setQueryValue}
onQueryClear={handleQueryRemove}
onClearAll={handleFilterClear}
children={() => {
<div>Hello World</div>
}}
>
<div>
<Button onClick={() => console.log('New Filter Saved')}>Save</Button>
</div>
</Filters>
)
// Execute GET_PRODUCTS GQL Query
const { loading, error, data } = useQuery(GET_PRODUCTS);
if (loading) return "Loading products...";
if (error) return `Error = ${error}`;
// Return dropdown menu of all products
return (
<Frame>
<Page>
<Layout>
<Layout.Section>
<DisplayText size="large">WeBuy Valuation Tool</DisplayText>
</Layout.Section>
<Layout.Section>
<Card>
<Card.Section>
<div>
<Card>
<ResourceList
resourceName={{singular: 'product', plural: 'products'}}
items={data ? data.products.edges : ""}
renderItem={renderItem}
filterControl={filterControl}
>
</ResourceList>
</Card>
</div>
</Card.Section>
</Card>
</Layout.Section>
</Layout>
</Page>
</Frame>
)
function renderItem(item) {
const { id, title, images, tags } = item.node;
const media = (
<Thumbnail
source={ images.edges[0] ? images.edges[0].node.originalSrc : '' }
alt={ images.edges[0] ? images.edges[0].node.altText : '' }
/>
);
const resourceItem = (
<ResourceItem
id={id}
accessibilityLabel={`View details for ${title}`}
media={media}
>
<Stack>
<Stack.Item fill>
<h3><TextStyle variation="strong">{title}</TextStyle></h3>
<h2>{tags}</h2>
</Stack.Item>
<Stack.Item>
<AddMetafield id={id} />
<DeleteMetafield id={id} />
</Stack.Item>
</Stack>
</ResourceItem>
);
tags ? tags.forEach(tag => {
if (tag == "Sports") {
console.log("has tag")
return resourceItem
}
}) : console.log("Return")
}
function disambiguateLabel (key, value) {
switch(key) {
case 'taggedWith' :
return `Tagged with ${value}`;
default:
return value;
}
}
function isEmpty(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else {
return value === '' || value == null;
}
}
}
I Couldn't find any documentation on how to implement this, and I expected it to be built-in, but I ended up doing something like this:
//add "filter.apply" function to the filter object
const filters = [{...apply:(c:Item)=>...},isApplied=...]
const appliedFilters = filters.filter(f=>f.isApplied);
let filteredItems = pageData.Items;
for (const filter of appliedFilters){
filteredItems = filteredItems.filter(filter.apply)
}
if(queryValue && queryValue.length > 0){
filteredItems = filteredItems.filter((c:FBComment)=>c.text.includes(queryValue))
}