codesandbox link
I am using react-complex-tree to show my XML in tree format. I want to add button called ExpandAllTree
const [expandedItems,setExpandedItems] = useState([]);
I am holding my tree Items here
const traverseXml = (treeData, xmlObject) => {
treeData[xmlObject.name] = {
index: xmlObject.name,
canMove: false,
hasChildren: !!xmlObject.children.length,
children: xmlObject.children.map(c => c.name),
data: !xmlObject.children.length ? `${xmlObject.name}: ${xmlObject.value}` : xmlObject.name,
canRename: false
};
if (!xmlObject.children.isEmpty) {
xmlObject.children.forEach(c => {
setExpandedItems(oldArray => [...oldArray,xmlObject.name]);
traverseXml(treeData, c);
});
}
};
this code piece does travel all XML and create a data for react-complex-tree and also gets all Id's of tree element with
setExpandedItems(oldArray => [...oldArray,xmlObject.name]);
This part works perfect.
useEffect(() => {
setExpandedItems([]);
traverseXml(firstTreeData, DocumentXml);
traverseXml(secondTreeData, AppHdrXml);
}, [treeOpen]);
const handleOpenClick = () => {
setTreeOpen(!treeOpen);
}
whenever a button hits it should rerender. But It is not rendering. when I check the logs on first time page open
expandedItems is empty like an expected when I press the button expandedItems to get all tree IDs like expected again but in frontend nothings changes.
<UncontrolledTreeEnvironment
canDragAndDrop={true}
canDropOnItemWithChildren={true}
canReorderItems={true}
dataProvider={new StaticTreeDataProvider(secondTreeData, (item, data) => ({...item, data}))}
getItemTitle={item => item.data}
viewState={{
['Apphdr']: {
expandedItems:expandedItems
},
}}
>
<Tree treeId={"Apphdr"} rootItem={"AppHdr"}/>
</UncontrolledTreeEnvironment>
And there is no mistake on data type with expandedItems because when I give data manually which expandedItems gets, Tree shows as expanded.
When you changed to ControlledTreeEnviroment and set items like
items={firstTreeData}
Tree start to rerender
Related
I have a graphql subscription that my front end subscribes to and it updates every 15 seconds and passes about 30 different objects (not as an array, but individual).
I'm catching the data and trying to set state by pushing it into an array, but the output is unexpected, many items are missing after the first sets of data comes in.
ie i have a useEffect that is listening for updates on transactions state, and im console logging out the length. At first i receieve 5, and 5 are rendered out. Then it says the length is 10, but still only 5 have rendered out. After a while loads will suddenly render out.
const [transactions, setTransactions] = useState([])
const { data, loading } = useSubscription(SUBSCRIPTION_TXS, {
onData: (data) => {
console.log('completed data is: ', data) // 1 object
const newTransactions = data.data.data.createTransfer
// setTransactions([newTransactions, ...transactions]) // Tried just tacking on the transactions initially
setTransactions(prevTransactions => { // tried using prev state as suggested in another post to overcome delays
const modifiedArray = [newTransactions, ...prevTransactions]
modifiedArray.sort((a, b) => moment(b.timestamp).format('X') - moment(a.timestamp).format('X'))
modifiedArray.splice(50) // Only display max 50 transactions
return modifiedArray
});
},
onError: (error => {
console.log('[ERROR]: ', error)
})
})
useEffect(() => {
console.log('transactions are: ', transactions) // logs 5 - and 5 are displayed. then logs 10 but only 5 are still displyed.
}, [transactions])
Any suggestions on what I could try please?
UPDATE
Mapping out is simply
<ul className={`space-y-5`}>
{transactions && transactions.map((transaction) => (
<TxCard transaction={transaction} key={transaction.id} />
))}
</ul>
I'm trying to empty an array named Variables which is located inside alertdetails object in React as it is a fix for one of the bugs but by doing so the existing Test cases are failing in this case how do I re-write the test case so that it doesn't fail?
it('should open send template drawer when "send now" button is clicked and initiator form feature is turned on and there are variables', () => {
wrapper.setProps({
alertDetails: {
...props.alertDetails,
variables: [{ id: 1, group: 'placeholder' }]
}
});
refreshComponent('actions');
const sendButton = actions.find(CreateAlertsSendButton);
sendButton.prop('onSendNow')();
const drawer = wrapper.find(SendTemplate);
expect(drawer.prop('isOpen')).toBeTruthy();
});
it('should not open send template drawer when "send now" button is clicked and initiator form feature is turned off', () => {
const sendButton = actions.find(CreateAlertsSendButton);
sendButton.prop('onSendNow')();
const drawer = wrapper.find(SendTemplate);
expect(drawer.exists()).toBeFalsy();
});
it('should call sendAlert when send template drawer executes callback', () => {
wrapper.setProps({
alertDetails: {
...props.alertDetails,
variables: [{ id: 1, group: 'placeholder' }]
}
});
refreshComponent('actions');
const sendButton = actions.find(CreateAlertsSendButton);
sendButton.prop('onSendNow')();
wrapper
.find('SendTemplate')
.props()
.onSendNow({ placeholders: 'placeholders' });
expect(props.setVariableValues).toHaveBeenCalledWith('placeholders');
expect(props.sendAlert).toHaveBeenCalledWith(AlertSendType.SEND_ONE_STEP, { redirectUrl: '' });
});
Sorry for the extended code, the above three cases fail if I empty the Variables array
Currently, this test case checks whether we have a placeholder in alertDetails.Variables array but I need to implement it in a way that it should check from alertDetails.alertMessage so that emptying variables array doesn't affect these cases. I'd appreciate any help thanks!
I'm rendering some values of LocalStorage on the HTML page. But whenever I refresh the page, the rendered things get deleted even if the data is still stored in the localStorage. I would like the data to not be deleted on render. I have tried doing an if/else statement for this but have not had luck with it. Could anyone help me a way I could make this happen? Thank you!
This is my code:
let myLibrary = [];
const addBook = (event) => {
event.preventDefault()
var fields = {
title: document.getElementById("title").value,
author: document.getElementById("author").value,
pages: document.getElementById("pages").value,
checkbox: document.getElementById("checkbox").value
};
myLibrary.push(fields)
localStorage.setItem('books', JSON.stringify(myLibrary))
const data = JSON.parse(localStorage.getItem('books'))
if (localStorage.getItem('books')) {
document.getElementById('bookList').innerHTML = data.map(item => {
return `<div>
<div>Title: ${item.title}</div>
<div>Author: ${item.author}</div>
<div>Pages: ${item.pages}</div>
</div>`
console.log(data)
})
} else {
items = []
}
}
I have this page which shows a single post and I have a like button. if the post is liked, when the user clicks the button, it changes its state to unlike button, but if the post is not liked, then the like is getting registered and the id is getting pushed on to the array, but the button state is not getting updated and I have to reload the page to see the page. Can someone tell me how to resolve this issue?
This is the code:
const [liked, setLiked] = useState(false)
const [data, setData] = useState([]);
function likePosts(post, user) {
post.likes.push({ id: user });
setData(post);
axiosInstance.post('api/posts/' + post.slug + '/like/');
window.location.reload()
}
function unlikePosts(post, user) {
console.log('unliked the post');
data.likes = data.likes.filter(x => x.id !== user);
setData(data);
return (
axiosInstance.delete('api/posts/' + post.slug + '/like/')
)
}
For the button:
{data.likes && data.likes.find(x => x.id === user) ?
(<FavoriteRoundedIcon style={{ color: "red" }}
onClick={() => {
unlikePosts(data, user)
setLiked(() => liked === false)
}
}
/>)
: (<FavoriteBorderRoundedIcon
onClick={() => {
likePosts(data, user)
setLiked(() => liked === true)
}
}
/>)
}
Thanks and please do ask if more details are needed.
As #iz_ pointed out in the comments, your main problem is that you are directly mutating state rather than calling a setState function.
I'm renaming data to post for clarity since you have said that this is an object representing the data for one post.
const [post, setPost] = useState(initialPost);
You don't need to use liked as a state because we can already access this information from the post data by seeing if our user is in the post.likes array or not. This allows us to have a "single source of truth" and we only need to make updates in one place.
const isLiked = post.likes.some((like) => like.id === user.id);
I'm confused about the likes array. It seems like an array of objects which are just {id: number}, in which case you should just have an array of ids of the users who liked the post. But maybe there are other properties in the object (like a username or timestamp).
When designing a component for something complex like a blog post, you want to break out little pieces that you can use in other places of your app. We can define a LikeButton that shows our heart. This is a "presentation" component that doesn't handle any logic. All it needs to know is whether the post isLiked and what to do onClick.
export const LikeButton = ({ isLiked, onClick }) => {
const Icon = isLiked ? FavoriteRoundedIcon: FavoriteBorderRoundedIcon;
return (
<Icon
style={{ color: isLiked ? "red" : "gray" }}
onClick={onClick}
/>
);
};
A lot of our logic regarding liking and unliking could potentially be broken out into some sort of usePostLike hook, but I haven't fully optimized this because I don't know what your API is doing and how we should respond to the response that we get.
When a user clicks the like button we want the changes to be reflected in the UI immediately, so we call setPost and add or remove the current user from the likes array. We have to set the state with a new object, so we copy all of the post properties that are not changing with the spread operator ...post and then override the likes property with an edited version. filter() and concat() are both safe array functions which return a new copy of the array.
We also need to call the API to post the changes. You are using the same url in both the "like" and "unlike" scenarios, so instead of calling axios.post and axios.delete, we can call the generalized function axios.request and pass the method name 'post' or 'delete' as an argument to the config object. [axios docs] We could probably combine our two setPost calls in a similar way and change likePost() and unlikePost() into one toggleLikePost() function. But for now, here's what I've got:
export const Post = ({ initialPost, user }) => {
const [post, setPost] = useState(initialPost);
const isLiked = post.likes.some((like) => like.id === user.id);
function likePost() {
console.log("liked the post");
// immediately update local state to reflect changes
setPost({
...post,
likes: post.likes.concat({ id: user.id })
});
// push changes to API
apiUpdateLike("post");
}
function unlikePost() {
console.log("unliked the post");
// immediately update local state to reflect changes
setPost({
...post,
likes: post.likes.filter((like) => like.id !== user.id)
});
// push changes to API
apiUpdateLike("delete");
}
// generalize like and unlike actions by passing method name 'post' or 'delete'
async function apiUpdateLike(method) {
try {
// send request to API
await axiosInstance.request("api/posts/" + post.slug + "/like/", { method });
// handle API response somehow, but not with window.location.reload()
} catch (e) {
console.log(e);
}
}
function onClickLike() {
if (isLiked) {
unlikePost();
} else {
likePost();
}
}
return (
<div>
<h2>{post.title}</h2>
<div>{post.likes.length} Likes</div>
<LikeButton onClick={onClickLike} isLiked={isLiked} />
</div>
);
};
CodeSandbox Link
I try to navigate from home screen to detail screen using navigation.navigate(). When the first time i click button to navigate it works fine, but after that when i go back from detail to home and click another item to see the detail, the params on detail screen is not changed (its still the params of the 1st click). Here my code :
<TouchableNativeFeedback
key={index}
onPress={() => {
navigation.navigate('PenghuniScreen', {
screen: 'DetailPenghuni',
params: {
penghuni: item,
},
});
}}></TouchableNativeFeedback>;
item is object from map loop . i try to console log using react navigation usefocuseffect and the params not changin after 1st click.
useFocusEffect(
React.useCallback(() => {
console.log('My params:', route.params.penghuni);
setPenghuni(route.params.penghuni);
return () => {
// source.cancel('Api Canceled');
console.log('tutup detail penghuni');
};
}, []),
);
anyone got solution for this?
You forgot to add the route dependency to your useCallback, so it is getting the old value.
Try adding [route]:
React.useCallback(() => {
console.log('My params:', route.params.penghuni);
setPenghuni(route.params.penghuni);
return () => {
// source.cancel('Api Canceled');
console.log('tutup detail penghuni');
};
}, [route]),
I suggest you adding the eslint for hooks to your project, to detect missing dependecies.
Observation: Why don't you pass these values though the navigation params instead of using useFocusEffect?
navigation.navigate('DetailPenghuni', { penghuni: item1 }) // item1 press
navigation.navigate('DetailPenghuni', { penghuni: item2 }) // item2 press
If you need to cancel something you can always use the useEffect clean callback called automatically when you close a screen.
You can use route.params.penghuni directly instead of setting setPenghuni(route.params.penghuni)
useEffect(() => {
console.log('My params:', route.params.penghuni);
// setPenghuni(route.params.penghuni);
return () => {
// source.cancel('Api Canceled');
console.log('tutup detail penghuni');
};
}, [route.params?.penghuni]),